1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-12 00:42:55 +08:00

Merge remote-tracking branch 'upstream/master' into taiko-classic

This commit is contained in:
vun 2024-03-09 19:31:34 +08:00
commit 8021f6bc6a
115 changed files with 1821 additions and 714 deletions

View File

@ -10,7 +10,7 @@
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk> <EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.223.0" /> <PackageReference Include="ppy.osu.Framework.Android" Version="2024.306.0" />
</ItemGroup> </ItemGroup>
<PropertyGroup> <PropertyGroup>
<!-- Fody does not handle Android build well, and warns when unchanged. <!-- Fody does not handle Android build well, and warns when unchanged.

View File

@ -7,7 +7,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.9" /> <PackageReference Include="BenchmarkDotNet" Version="0.13.12" />
<PackageReference Include="nunit" Version="3.14.0" /> <PackageReference Include="nunit" Version="3.14.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" /> <PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
</ItemGroup> </ItemGroup>

View File

@ -53,6 +53,7 @@ namespace osu.Game.Rulesets.Catch.Tests
[TestCase("3689906", new[] { typeof(CatchModDoubleTime), typeof(CatchModEasy) })] [TestCase("3689906", new[] { typeof(CatchModDoubleTime), typeof(CatchModEasy) })]
[TestCase("3949367", new[] { typeof(CatchModDoubleTime), typeof(CatchModEasy) })] [TestCase("3949367", new[] { typeof(CatchModDoubleTime), typeof(CatchModEasy) })]
[TestCase("112643")] [TestCase("112643")]
[TestCase("1041052", new[] { typeof(CatchModHardRock) })]
public new void Test(string name, params Type[] mods) => base.Test(name, mods); public new void Test(string name, params Type[] mods) => base.Test(name, mods);
protected override IEnumerable<ConvertValue> CreateConvertValue(HitObject hitObject) protected override IEnumerable<ConvertValue> CreateConvertValue(HitObject hitObject)

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,210 @@
osu file format v14
[General]
AudioFilename: audio.mp3
AudioLeadIn: 0
PreviewTime: 65316
Countdown: 0
SampleSet: Soft
StackLeniency: 0.7
Mode: 2
LetterboxInBreaks: 0
WidescreenStoryboard: 0
[Editor]
DistanceSpacing: 1.4
BeatDivisor: 4
GridSize: 8
TimelineZoom: 1.4
[Metadata]
Title:Nanairo Symphony -TV Size-
TitleUnicode:七色シンフォニー -TV Size-
Artist:Coalamode.
ArtistUnicode:コアラモード.
Creator:Ascendance
Version:Aru's Cup
Source:四月は君の嘘
Tags:shigatsu wa kimi no uso your lie in april opening arusamour tenshichan [superstar]
BeatmapID:1041052
BeatmapSetID:488149
[Difficulty]
HPDrainRate:3
CircleSize:2.5
OverallDifficulty:6
ApproachRate:6
SliderMultiplier:1.02
SliderTickRate:2
[Events]
//Background and Video events
Video,500,"forty.avi"
0,0,"cropped-1366-768-647733.jpg",0,0
//Break Periods
//Storyboard Layer 0 (Background)
//Storyboard Layer 1 (Fail)
//Storyboard Layer 2 (Pass)
//Storyboard Layer 3 (Foreground)
//Storyboard Sound Samples
[TimingPoints]
1155,387.096774193548,4,2,1,50,1,0
15284,-100,4,2,1,60,0,0
16638,-100,4,2,1,50,0,0
41413,-100,4,2,1,60,0,0
59993,-100,4,2,1,65,0,0
66187,-100,4,2,1,70,0,1
87284,-100,4,2,1,60,0,1
87864,-100,4,2,1,70,0,0
87961,-100,4,2,1,50,0,0
88638,-100,4,2,1,30,0,0
89413,-100,4,2,1,10,0,0
89800,-100,4,2,1,5,0,0
[Colours]
Combo1 : 255,128,64
Combo2 : 0,128,255
Combo3 : 255,128,192
Combo4 : 0,128,192
[HitObjects]
208,160,1155,6,0,L|45:160,1,153,2|2,0:0|0:0,0:0:0:0:
160,160,2122,1,0,0:0:0:0:
272,160,2509,1,2,0:0:0:0:
448,288,3284,6,0,P|480:240|480:192,1,102,2|0,0:0|0:0,0:0:0:0:
384,96,4058,1,2,0:0:0:0:
128,64,5025,6,0,L|32:64,2,76.5,2|0|0,0:0|0:0|0:0,0:0:0:0:
192,64,5800,1,2,0:0:0:0:
240,64,5993,1,2,0:0:0:0:
288,64,6187,1,2,0:0:0:0:
416,80,6574,6,0,L|192:80,1,204,0|2,0:0|0:0,0:0:0:0:
488,160,8122,2,0,L|376:160,1,102
457,288,8896,2,0,L|297:288,1,153,2|2,0:0|0:0,0:0:0:0:
400,288,10058,1,0,0:0:0:0:
304,288,10445,6,0,L|192:288,2,102,2|0|2,0:0|0:0|0:0,0:0:0:0:
400,288,11606,1,0,0:0:0:0:
240,288,11993,2,0,L|80:288,1,153,2|0,0:0|0:0,0:0:0:0:
0,288,13154,1,0,0:0:0:0:
112,240,13542,6,0,P|160:288|256:288,1,153,6|2,0:0|0:0,0:0:0:0:
288,288,14316,2,0,L|368:288,2,76.5,2|0|0,0:0|0:0|0:0,0:0:0:0:
192,288,15284,2,0,L|160:224,1,51,0|12,0:0|0:0,0:0:0:0:
312,208,15864,1,6,0:0:0:0:
128,176,16638,6,0,P|64:160|0:96,2,153,6|2|0,0:0|0:0|0:0,0:0:0:0:
224,176,18187,2,0,P|288:192|352:272,2,153,2|2|0,0:0|0:0|0:0,0:0:0:0:
128,176,19735,6,0,L|288:176,1,153,2|2,0:0|0:0,0:0:0:0:
432,176,20896,1,0,0:0:0:0:
328,176,21284,2,0,L|488:176,1,153,2|2,0:0|0:0,0:0:0:0:
328,176,22445,1,0,0:0:0:0:
224,176,22832,6,0,L|64:176,1,153,2|2,0:0|0:0,0:0:0:0:
224,176,23993,1,0,0:0:0:0:
112,176,24380,2,0,L|272:176,1,153,2|2,0:0|0:0,0:0:0:0:
416,176,25541,1,0,0:0:0:0:
304,256,25929,6,0,P|272:208|312:120,1,153,2|2,0:0|0:0,0:0:0:0:
480,112,27090,1,0,0:0:0:0:
384,112,27477,6,0,L|320:112,2,51,2|2|0,0:0|0:0|0:0,0:0:0:0:
432,112,28058,1,2,0:0:0:0:
333,112,28445,2,0,L|282:112,2,51,0|0|0,0:0|0:0|0:0,0:0:0:0:
384,112,29025,6,0,L|272:112,1,102,6|0,0:0|0:0,0:0:0:0:
224,112,29606,2,0,P|160:144|160:240,1,153,2|2,0:0|0:0,0:0:0:0:
272,272,30574,2,0,L|374:272,1,102
424,272,31154,2,0,P|414:344|348:378,1,153,0|0,0:0|0:0,0:0:0:0:
224,304,32122,6,0,P|176:320|144:368,1,102,2|0,0:0|0:0,0:0:0:0:
200,368,32703,1,2,0:0:0:0:
376,368,33284,1,0,0:0:0:0:
304,296,33671,2,0,L|240:296,2,51,2|2|0,0:0|0:0|0:0,0:0:0:0:
352,296,34251,2,0,P|400:248|384:168,1,153,2|0,0:0|0:0,0:0:0:0:
280,176,35219,6,0,L|216:80,1,102,2|0,0:0|0:0,0:0:0:0:
272,104,35800,2,0,L|336:8,1,102,2|0,0:0|0:0,0:0:0:0:
280,16,36380,1,2,0:0:0:0:
176,32,36767,6,0,L|112:128,1,102,2|0,0:0|0:0,0:0:0:0:
168,128,37348,2,0,L|232:224,1,102,2|0,0:0|0:0,0:0:0:0:
176,224,37928,1,2,0:0:0:0:
304,264,38316,6,0,L|200:264,1,102,2|0,0:0|0:0,0:0:0:0:
144,264,38896,1,2,0:0:0:0:
280,336,39477,2,0,L|336:336,1,51
424,336,39864,2,0,P|440:304|416:240,1,102,8|0,0:3|0:3,0:3:0:0:
352,232,40445,1,4,0:1:0:0:
160,224,41025,1,8,0:3:0:0:
256,48,41413,6,0,P|302:28|353:31,1,102,6|0,0:0|0:0,0:0:0:0:
400,40,41993,1,0,0:0:0:0:
440,80,42187,2,0,P|389:76|342:96,1,102,2|8,0:0|0:0,0:0:0:0:
248,128,42961,2,0,P|312:176|392:144,2,153,2|2|8,0:0|0:0|0:3,0:0:0:0:
144,136,44509,6,0,P|80:88|0:120,1,153,2|0,0:0|0:0,0:0:0:0:
56,136,45284,1,2,0:0:0:0:
160,144,45671,1,8,0:0:0:0:
264,144,46058,2,0,L|384:144,1,102,2|0,0:0|0:0,0:0:0:0:
416,152,46638,2,0,L|264:152,1,153,2|8,0:0|0:3,0:0:0:0:
360,120,47606,6,0,L|192:120,1,153,2|0,0:0|0:0,0:0:0:0:
160,128,48380,2,0,P|208:80|256:96,1,102,2|8,0:0|0:0,0:0:0:0:
144,136,49154,1,2,0:0:0:0:
248,144,49542,2,0,L|368:144,1,102,0|2,0:0|0:0,0:0:0:0:
256,192,50316,2,0,L|200:192,1,51,10|0,0:0|0:0,0:0:0:0:
256,184,50703,6,0,L|360:184,1,102,2|0,0:0|0:0,0:0:0:0:
400,208,51284,1,0,0:0:0:0:
352,240,51477,2,0,L|240:240,1,102
128,336,52251,6,0,P|64:336|0:256,1,153,2|2,0:0|0:0,0:0:0:0:
88,264,53025,1,2,0:0:0:0:
168,208,53413,2,0,L|152:144,1,51,8|8,0:0|0:3,0:0:0:0:
248,120,53800,6,0,P|328:152|392:120,1,153,6|0,0:0|0:0,0:0:0:0:
432,120,54574,1,2,0:0:0:0:
328,128,54961,1,8,0:0:0:0:
224,128,55348,6,0,L|112:144,1,102,2|0,0:0|0:0,0:0:0:0:
72,152,55929,2,0,L|192:176,1,102,2|0,0:0|0:0,0:0:0:0:
224,184,56509,1,8,0:3:0:0:
328,176,56896,6,0,P|376:208|472:192,1,153,2|0,0:0|0:0,0:0:0:0:
416,208,57671,2,0,L|304:240,1,102,2|8,0:0|0:0,0:0:0:0:
224,272,58445,5,2,0:0:0:0:
320,296,58832,1,0,0:0:0:0:
224,328,59219,1,2,0:0:0:0:
120,328,59606,1,8,0:3:0:0:
224,264,59993,6,0,P|224:200|192:152,1,102,6|0,0:0|0:0,0:0:0:0:
80,184,60767,2,0,P|76:133|97:87,1,102,2|8,0:0|0:0,0:0:0:0:
200,80,61542,2,0,P|232:112|296:112,1,102,2|0,0:0|0:0,0:0:0:0:
376,160,62316,2,0,P|344:192|280:192,1,102,2|8,0:0|0:0,0:0:0:0:
184,240,63090,6,0,L|200:128,1,102,2|8,0:0|0:0,0:0:0:0:
88,136,63864,2,0,L|8:152,2,76.5,6|2|2,0:0|0:0|0:0,0:0:0:0:
160,112,64638,1,8,0:0:0:0:
208,128,64832,1,8,0:0:0:0:
256,144,65025,1,8,0:0:0:0:
360,152,65413,6,0,L|424:152,1,51,8|0,0:0|0:0,0:0:0:0:
462,152,65800,2,0,L|398:152,1,51,8|8,0:0|0:3,0:0:0:0:
344,144,66187,6,0,L|232:144,1,102,12|8,0:0|0:0,0:0:0:0:
152,120,66961,2,0,P|148:169|107:196,1,102,8|8,0:0|0:0,0:0:0:0:
32,264,67735,6,0,L|144:216,1,102,8|8,0:0|0:0,0:0:0:0:
176,208,68316,1,0,0:0:0:0:
224,200,68509,2,0,L|317:240,1,102,8|8,0:0|0:0,0:0:0:0:
216,256,69284,6,0,P|184:304|200:352,1,102,8|8,0:0|0:0,0:0:0:0:
360,256,70058,2,0,P|368:207|337:167,1,102,8|8,0:0|0:0,0:0:0:0:
264,80,70832,6,0,L|152:96,1,102,8|8,0:0|0:0,0:0:0:0:
112,104,71413,2,0,L|11:89,1,102,8|0,0:0|0:0,0:0:0:0:
40,128,71993,2,0,L|72:176,1,51,8|8,0:0|0:3,0:0:0:0:
176,216,72380,6,0,P|144:280|64:280,1,153,12|0,0:0|0:0,0:0:0:0:
120,280,73154,2,0,P|191:299|216:328,1,102,8|8,0:0|0:0,0:0:0:0:
312,320,73929,6,0,L|424:304,1,102,8|8,0:0|0:0,0:0:0:0:
336,272,74703,2,0,L|312:216,1,51,8|0,0:0|0:0,0:0:0:0:
400,200,75090,2,0,L|424:136,1,51,8|0,0:0|0:0,0:0:0:0:
328,152,75477,6,0,P|280:184|200:136,1,153,12|0,0:0|0:0,0:0:0:0:
296,136,76251,2,0,P|360:136|408:168,1,102,8|8,0:0|0:0,0:0:0:0:
152,248,77219,6,0,L|96:248,2,51,0|12|0,0:0|0:0|0:0,0:0:0:0:
208,248,77800,1,8,0:0:0:0:
320,256,78187,2,0,L|369:243,1,51,8|8,0:0|0:3,0:0:0:0:
456,232,78574,6,0,L|408:136,1,102,12|8,0:0|0:0,0:0:0:0:
288,136,79348,2,0,L|336:40,1,102,8|8,0:0|0:0,0:0:0:0:
240,80,80122,6,0,P|144:80|128:64,1,102,8|8,0:0|0:0,0:0:0:0:
96,72,80703,1,0,0:0:0:0:
40,104,80896,2,0,P|136:104|152:88,1,102,8|8,0:0|0:0,0:0:0:0:
248,128,81671,6,0,L|296:224,1,102,12|8,0:0|0:0,0:0:0:0:
208,272,82445,1,10,0:0:0:0:
312,272,82832,1,8,0:0:0:0:
400,224,83219,6,0,L|416:160,1,51,8|2,0:0|0:0,0:0:0:0:
360,56,83606,2,0,L|336:120,1,51,8|0,0:0|0:0,0:0:0:0:
272,152,83993,2,0,P|192:152|176:136,1,102,0|8,0:0|0:0,0:0:0:0:
80,160,84767,6,0,L|96:208,1,51,8|0,0:0|0:0,0:0:0:0:
16,272,85154,2,0,L|16:328,1,51,8|0,0:0|0:0,0:0:0:0:
104,304,85542,2,0,L|208:304,1,102,2|8,0:0|0:0,0:0:0:0:
376,336,86316,6,0,L|472:304,1,102,4|0,0:0|0:0,0:0:0:0:
296,248,87090,2,0,P|312:168|312:136,1,102,2|8,0:0|0:3,0:0:0:0:
168,96,87864,1,4,0:0:0:0:
256,192,88251,12,0,89800,0:0:0:0:

View File

@ -118,7 +118,11 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
float offsetPosition = hitObject.OriginalX; float offsetPosition = hitObject.OriginalX;
double startTime = hitObject.StartTime; double startTime = hitObject.StartTime;
if (lastPosition == null) if (lastPosition == null ||
// some objects can get assigned position zero, making stable incorrectly go inside this if branch on the next object. to maintain behaviour and compatibility, do the same here.
// reference: https://github.com/peppy/osu-stable-reference/blob/3ea48705eb67172c430371dcfc8a16a002ed0d3d/osu!/GameplayElements/HitObjects/Fruits/HitFactoryFruits.cs#L45-L50
// todo: should be revisited and corrected later probably.
lastPosition == 0)
{ {
lastPosition = offsetPosition; lastPosition = offsetPosition;
lastStartTime = startTime; lastStartTime = startTime;

View File

@ -2,22 +2,21 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring; using osu.Game.Scoring;
using osu.Game.Scoring.Legacy;
namespace osu.Game.Rulesets.Catch.Difficulty namespace osu.Game.Rulesets.Catch.Difficulty
{ {
public class CatchPerformanceCalculator : PerformanceCalculator public class CatchPerformanceCalculator : PerformanceCalculator
{ {
private int fruitsHit; private int num300;
private int ticksHit; private int num100;
private int tinyTicksHit; private int num50;
private int tinyTicksMissed; private int numKatu;
private int misses; private int numMiss;
public CatchPerformanceCalculator() public CatchPerformanceCalculator()
: base(new CatchRuleset()) : base(new CatchRuleset())
@ -28,11 +27,11 @@ namespace osu.Game.Rulesets.Catch.Difficulty
{ {
var catchAttributes = (CatchDifficultyAttributes)attributes; var catchAttributes = (CatchDifficultyAttributes)attributes;
fruitsHit = score.Statistics.GetValueOrDefault(HitResult.Great); num300 = score.GetCount300() ?? 0; // HitResult.Great
ticksHit = score.Statistics.GetValueOrDefault(HitResult.LargeTickHit); num100 = score.GetCount100() ?? 0; // HitResult.LargeTickHit
tinyTicksHit = score.Statistics.GetValueOrDefault(HitResult.SmallTickHit); num50 = score.GetCount50() ?? 0; // HitResult.SmallTickHit
tinyTicksMissed = score.Statistics.GetValueOrDefault(HitResult.SmallTickMiss); numKatu = score.GetCountKatu() ?? 0; // HitResult.SmallTickMiss
misses = score.Statistics.GetValueOrDefault(HitResult.Miss); numMiss = score.GetCountMiss() ?? 0; // HitResult.Miss PLUS HitResult.LargeTickMiss
// We are heavily relying on aim in catch the beat // We are heavily relying on aim in catch the beat
double value = Math.Pow(5.0 * Math.Max(1.0, catchAttributes.StarRating / 0.0049) - 4.0, 2.0) / 100000.0; double value = Math.Pow(5.0 * Math.Max(1.0, catchAttributes.StarRating / 0.0049) - 4.0, 2.0) / 100000.0;
@ -45,7 +44,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
(numTotalHits > 2500 ? Math.Log10(numTotalHits / 2500.0) * 0.475 : 0.0); (numTotalHits > 2500 ? Math.Log10(numTotalHits / 2500.0) * 0.475 : 0.0);
value *= lengthBonus; value *= lengthBonus;
value *= Math.Pow(0.97, misses); value *= Math.Pow(0.97, numMiss);
// Combo scaling // Combo scaling
if (catchAttributes.MaxCombo > 0) if (catchAttributes.MaxCombo > 0)
@ -86,8 +85,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty
} }
private double accuracy() => totalHits() == 0 ? 0 : Math.Clamp((double)totalSuccessfulHits() / totalHits(), 0, 1); private double accuracy() => totalHits() == 0 ? 0 : Math.Clamp((double)totalSuccessfulHits() / totalHits(), 0, 1);
private int totalHits() => tinyTicksHit + ticksHit + fruitsHit + misses + tinyTicksMissed; private int totalHits() => num50 + num100 + num300 + numMiss + numKatu;
private int totalSuccessfulHits() => tinyTicksHit + ticksHit + fruitsHit; private int totalSuccessfulHits() => num50 + num100 + num300;
private int totalComboHits() => misses + ticksHit + fruitsHit; private int totalComboHits() => numMiss + num100 + num300;
} }
} }

View File

@ -7,6 +7,7 @@ using osu.Framework.Allocation;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mania.UI;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.Mania.Skinning; using osu.Game.Rulesets.Mania.Skinning;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
@ -101,6 +102,14 @@ namespace osu.Game.Rulesets.Mania.Mods
return base.GetHeight(coverage) * reference_playfield_height / availablePlayfieldHeight; return base.GetHeight(coverage) * reference_playfield_height / availablePlayfieldHeight;
} }
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (skin.IsNotNull())
skin.SourceChanged -= onSkinChanged;
}
} }
} }
} }

View File

@ -6,6 +6,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mania.UI;
@ -46,17 +47,18 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
Children = new Drawable[] Children = new Drawable[]
{ {
// Key images are placed side-to-side on the playfield, therefore ClampToEdge must be used to prevent any gaps between each key.
upSprite = new Sprite upSprite = new Sprite
{ {
Origin = Anchor.BottomCentre, Origin = Anchor.BottomCentre,
Texture = skin.GetTexture(upImage), Texture = skin.GetTexture(upImage, WrapMode.ClampToEdge, default),
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Width = 1 Width = 1
}, },
downSprite = new Sprite downSprite = new Sprite
{ {
Origin = Anchor.BottomCentre, Origin = Anchor.BottomCentre,
Texture = skin.GetTexture(downImage), Texture = skin.GetTexture(downImage, WrapMode.ClampToEdge, default),
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Width = 1, Width = 1,
Alpha = 0 Alpha = 0

View File

@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
PassCondition = () => PassCondition = () =>
{ {
var flashlightOverlay = Player.DrawableRuleset.Overlays var flashlightOverlay = Player.DrawableRuleset.Overlays
.OfType<ModFlashlight<OsuHitObject>.Flashlight>() .ChildrenOfType<ModFlashlight<OsuHitObject>.Flashlight>()
.First(); .First();
return Precision.AlmostEquals(mod.DefaultFlashlightSize * .5f, flashlightOverlay.GetSize()); return Precision.AlmostEquals(mod.DefaultFlashlightSize * .5f, flashlightOverlay.GetSize());

View File

@ -457,6 +457,33 @@ namespace osu.Game.Rulesets.Osu.Tests
assertMidSliderJudgementFail(); assertMidSliderJudgementFail();
} }
[Test]
public void TestRewindHandling()
{
performTest(new List<ReplayFrame>
{
new OsuReplayFrame { Position = new Vector2(0), Actions = { OsuAction.LeftButton }, Time = time_slider_start },
new OsuReplayFrame { Position = new Vector2(175, 0), Actions = { OsuAction.LeftButton }, Time = 3250 },
new OsuReplayFrame { Position = new Vector2(175, 0), Actions = { OsuAction.LeftButton }, Time = time_slider_end },
}, new Slider
{
StartTime = time_slider_start,
Position = new Vector2(0, 0),
Path = new SliderPath(PathType.PERFECT_CURVE, new[]
{
Vector2.Zero,
new Vector2(250, 0),
}, 250),
});
AddUntilStep("wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value);
AddAssert("no miss judgements recorded", () => judgementResults.All(r => r.Type.IsHit()));
AddStep("rewind to middle of slider", () => currentPlayer.Seek(time_during_slide_4));
AddUntilStep("wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value);
AddAssert("no miss judgements recorded", () => judgementResults.All(r => r.Type.IsHit()));
}
private void assertAllMaxJudgements() private void assertAllMaxJudgements()
{ {
AddAssert("All judgements max", () => AddAssert("All judgements max", () =>

View File

@ -58,9 +58,9 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
private void applyStacking(Beatmap<OsuHitObject> beatmap, int startIndex, int endIndex) private void applyStacking(Beatmap<OsuHitObject> beatmap, int startIndex, int endIndex)
{ {
if (startIndex > endIndex) throw new ArgumentOutOfRangeException(nameof(startIndex), $"{nameof(startIndex)} cannot be greater than {nameof(endIndex)}."); ArgumentOutOfRangeException.ThrowIfGreaterThan(startIndex, endIndex);
if (startIndex < 0) throw new ArgumentOutOfRangeException(nameof(startIndex), $"{nameof(startIndex)} cannot be less than 0."); ArgumentOutOfRangeException.ThrowIfNegative(startIndex);
if (endIndex < 0) throw new ArgumentOutOfRangeException(nameof(endIndex), $"{nameof(endIndex)} cannot be less than 0."); ArgumentOutOfRangeException.ThrowIfNegative(endIndex);
int extendedEndIndex = endIndex; int extendedEndIndex = endIndex;

View File

@ -334,7 +334,7 @@ namespace osu.Game.Rulesets.Osu.Edit
/// <returns>The <see cref="OsuDistanceSnapGrid"/> from a selected <see cref="HitObject"/> to a target <see cref="HitObject"/>.</returns> /// <returns>The <see cref="OsuDistanceSnapGrid"/> from a selected <see cref="HitObject"/> to a target <see cref="HitObject"/>.</returns>
private OsuDistanceSnapGrid createGrid(Func<HitObject, bool> sourceSelector, int targetOffset = 1) private OsuDistanceSnapGrid createGrid(Func<HitObject, bool> sourceSelector, int targetOffset = 1)
{ {
if (targetOffset < 1) throw new ArgumentOutOfRangeException(nameof(targetOffset)); ArgumentOutOfRangeException.ThrowIfNegativeOrZero(targetOffset);
int sourceIndex = -1; int sourceIndex = -1;

View File

@ -0,0 +1,20 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Osu.Judgements
{
public class OsuSliderJudgementResult : OsuJudgementResult
{
public readonly Stack<(double time, bool tracking)> TrackingHistory = new Stack<(double, bool)>();
public OsuSliderJudgementResult(HitObject hitObject, Judgement judgement)
: base(hitObject, judgement)
{
TrackingHistory.Push((double.NegativeInfinity, false));
}
}
}

View File

@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.Mods
// multiply the SPM by 1.01 to ensure that the spinner is completed. if the calculation is left exact, // multiply the SPM by 1.01 to ensure that the spinner is completed. if the calculation is left exact,
// some spinners may not complete due to very minor decimal loss during calculation // some spinners may not complete due to very minor decimal loss during calculation
float rotationSpeed = (float)(1.01 * spinner.HitObject.SpinsRequired / spinner.HitObject.Duration); float rotationSpeed = (float)(1.01 * spinner.HitObject.SpinsRequired / spinner.HitObject.Duration);
spinner.RotationTracker.AddRotation(MathUtils.RadiansToDegrees((float)rateIndependentElapsedTime * rotationSpeed * MathF.PI * 2.0f)); spinner.RotationTracker.AddRotation(float.RadiansToDegrees((float)rateIndependentElapsedTime * rotationSpeed * MathF.PI * 2.0f));
} }
} }
} }

View File

@ -14,8 +14,10 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Layout; using osu.Framework.Layout;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Rulesets.Osu.Skinning.Default; using osu.Game.Rulesets.Osu.Skinning.Default;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning; using osu.Game.Skinning;
@ -27,6 +29,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
public new Slider HitObject => (Slider)base.HitObject; public new Slider HitObject => (Slider)base.HitObject;
public new OsuSliderJudgementResult Result => (OsuSliderJudgementResult)base.Result;
public DrawableSliderHead HeadCircle => headContainer.Child; public DrawableSliderHead HeadCircle => headContainer.Child;
public DrawableSliderTail TailCircle => tailContainer.Child; public DrawableSliderTail TailCircle => tailContainer.Child;
@ -134,6 +138,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
}, true); }, true);
} }
protected override JudgementResult CreateResult(Judgement judgement) => new OsuSliderJudgementResult(HitObject, judgement);
protected override void OnApply() protected override void OnApply()
{ {
base.OnApply(); base.OnApply();

View File

@ -79,6 +79,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
base.OnApply(); base.OnApply();
Position = HitObject.Position - DrawableSlider.Position; Position = HitObject.Position - DrawableSlider.Position;
hasRotation = false;
} }
protected override void CheckForResult(bool userTriggered, double timeOffset) => DrawableSlider.SliderInputManager.TryJudgeNestedObject(this, timeOffset); protected override void CheckForResult(bool userTriggered, double timeOffset) => DrawableSlider.SliderInputManager.TryJudgeNestedObject(this, timeOffset);
@ -146,7 +147,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
break; break;
} }
float aimRotation = MathUtils.RadiansToDegrees(MathF.Atan2(aimRotationVector.Y - Position.Y, aimRotationVector.X - Position.X)); float aimRotation = float.RadiansToDegrees(MathF.Atan2(aimRotationVector.Y - Position.Y, aimRotationVector.X - Position.X));
while (Math.Abs(aimRotation - Arrow.Rotation) > 180) while (Math.Abs(aimRotation - Arrow.Rotation) > 180)
aimRotation += aimRotation < Arrow.Rotation ? 360 : -360; aimRotation += aimRotation < Arrow.Rotation ? 360 : -360;

View File

@ -5,11 +5,14 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Screens.Play;
using osuTK; using osuTK;
namespace osu.Game.Rulesets.Osu.Objects.Drawables namespace osu.Game.Rulesets.Osu.Objects.Drawables
@ -21,6 +24,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
/// </summary> /// </summary>
public bool Tracking { get; private set; } public bool Tracking { get; private set; }
[Resolved]
private IGameplayClock? gameplayClock { get; set; }
/// <summary> /// <summary>
/// The point in time after which we can accept any key for tracking. Before this time, we may need to restrict tracking to the key used to hit the head circle. /// The point in time after which we can accept any key for tracking. Before this time, we may need to restrict tracking to the key used to hit the head circle.
/// ///
@ -49,6 +55,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public SliderInputManager(DrawableSlider slider) public SliderInputManager(DrawableSlider slider)
{ {
this.slider = slider; this.slider = slider;
this.slider.HitObjectApplied += resetState;
} }
/// <summary> /// <summary>
@ -208,6 +215,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
/// <param name="isValidTrackingPosition">Whether the current mouse position is valid to begin tracking.</param> /// <param name="isValidTrackingPosition">Whether the current mouse position is valid to begin tracking.</param>
private void updateTracking(bool isValidTrackingPosition) private void updateTracking(bool isValidTrackingPosition)
{ {
if (gameplayClock?.IsRewinding == true)
{
var trackingHistory = slider.Result.TrackingHistory;
while (trackingHistory.TryPeek(out var historyEntry) && Time.Current < historyEntry.time)
trackingHistory.Pop();
Debug.Assert(trackingHistory.Count > 0);
Tracking = trackingHistory.Peek().tracking;
return;
}
bool wasTracking = Tracking;
// from the point at which the head circle is hit, this will be non-null. // from the point at which the head circle is hit, this will be non-null.
// it may be null if the head circle was missed. // it may be null if the head circle was missed.
OsuAction? headCircleHitAction = getInitialHitAction(); OsuAction? headCircleHitAction = getInitialHitAction();
@ -247,6 +268,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
&& isValidTrackingPosition && isValidTrackingPosition
// valid action // valid action
&& validTrackingAction; && validTrackingAction;
if (wasTracking != Tracking)
slider.Result.TrackingHistory.Push((Time.Current, Tracking));
} }
private OsuAction? getInitialHitAction() => slider.HeadCircle?.HitAction; private OsuAction? getInitialHitAction() => slider.HeadCircle?.HitAction;
@ -264,5 +288,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
return action == OsuAction.LeftButton || action == OsuAction.RightButton; return action == OsuAction.LeftButton || action == OsuAction.RightButton;
} }
private void resetState(DrawableHitObject obj)
{
Tracking = false;
timeToAcceptAnyKeyAfter = null;
lastPressedActions.Clear();
screenSpaceMousePosition = null;
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
slider.HitObjectApplied -= resetState;
}
} }
} }

View File

@ -342,7 +342,7 @@ namespace osu.Game.Rulesets.Osu.Replays
// 0.05 rad/ms, or ~477 RPM, as per stable. // 0.05 rad/ms, or ~477 RPM, as per stable.
// the redundant conversion from RPM to rad/ms is here for ease of testing custom SPM specs. // the redundant conversion from RPM to rad/ms is here for ease of testing custom SPM specs.
const float spin_rpm = 0.05f / (2 * MathF.PI) * 60000; const float spin_rpm = 0.05f / (2 * MathF.PI) * 60000;
float radsPerMillisecond = MathUtils.DegreesToRadians(spin_rpm * 360) / 60000; float radsPerMillisecond = float.DegreesToRadians(spin_rpm * 360) / 60000;
switch (h) switch (h)
{ {

View File

@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
Origin = Anchor.Centre, Origin = Anchor.Centre,
Colour = Color4.White.Opacity(0.25f), Colour = Color4.White.Opacity(0.25f),
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Current = { Value = arc_fill }, Progress = arc_fill,
Rotation = 90 - arc_fill * 180, Rotation = 90 - arc_fill * 180,
InnerRadius = arc_radius, InnerRadius = arc_radius,
RoundedCaps = true, RoundedCaps = true,
@ -71,9 +71,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
background.Alpha = spinner.Progress >= 1 ? 0 : 1; background.Alpha = spinner.Progress >= 1 ? 0 : 1;
fill.Alpha = (float)Interpolation.DampContinuously(fill.Alpha, spinner.Progress > 0 && spinner.Progress < 1 ? 1 : 0, 40f, (float)Math.Abs(Time.Elapsed)); fill.Alpha = (float)Interpolation.DampContinuously(fill.Alpha, spinner.Progress > 0 && spinner.Progress < 1 ? 1 : 0, 40f, (float)Math.Abs(Time.Elapsed));
fill.Current.Value = (float)Interpolation.DampContinuously(fill.Current.Value, spinner.Progress >= 1 ? 0 : arc_fill * spinner.Progress, 40f, (float)Math.Abs(Time.Elapsed)); fill.Progress = (float)Interpolation.DampContinuously(fill.Progress, spinner.Progress >= 1 ? 0 : arc_fill * spinner.Progress, 40f, (float)Math.Abs(Time.Elapsed));
fill.Rotation = (float)(90 - fill.Current.Value * 180); fill.Rotation = (float)(90 - fill.Progress * 180);
} }
private partial class ProgressFill : CircularProgress private partial class ProgressFill : CircularProgress

View File

@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Current = { Value = arc_fill }, Progress = arc_fill,
Rotation = -arc_fill * 180, Rotation = -arc_fill * 180,
InnerRadius = arc_radius, InnerRadius = arc_radius,
RoundedCaps = true, RoundedCaps = true,
@ -44,10 +44,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
{ {
base.Update(); base.Update();
fill.Current.Value = (float)Interpolation.DampContinuously(fill.Current.Value, spinner.Progress >= 1 ? arc_fill_complete : arc_fill, 40f, (float)Math.Abs(Time.Elapsed)); fill.Progress = (float)Interpolation.DampContinuously(fill.Progress, spinner.Progress >= 1 ? arc_fill_complete : arc_fill, 40f, (float)Math.Abs(Time.Elapsed));
fill.InnerRadius = (float)Interpolation.DampContinuously(fill.InnerRadius, spinner.Progress >= 1 ? arc_radius * 2.2f : arc_radius, 40f, (float)Math.Abs(Time.Elapsed)); fill.InnerRadius = (float)Interpolation.DampContinuously(fill.InnerRadius, spinner.Progress >= 1 ? arc_radius * 2.2f : arc_radius, 40f, (float)Math.Abs(Time.Elapsed));
fill.Rotation = (float)(-fill.Current.Value * 180); fill.Rotation = (float)(-fill.Progress * 180);
} }
} }
} }

View File

@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
if (mousePosition is Vector2 pos) if (mousePosition is Vector2 pos)
{ {
float thisAngle = -MathUtils.RadiansToDegrees(MathF.Atan2(pos.X - DrawSize.X / 2, pos.Y - DrawSize.Y / 2)); float thisAngle = -float.RadiansToDegrees(MathF.Atan2(pos.X - DrawSize.X / 2, pos.Y - DrawSize.Y / 2));
float delta = lastAngle == null ? 0 : thisAngle - lastAngle.Value; float delta = lastAngle == null ? 0 : thisAngle - lastAngle.Value;
// Normalise the delta to -180 .. 180 // Normalise the delta to -180 .. 180

View File

@ -246,7 +246,7 @@ namespace osu.Game.Rulesets.Osu.Statistics
// Likewise sin(pi/2)=1 and sin(3pi/2)=-1, whereas we actually want these values to appear on the bottom/top respectively, so the y-coordinate also needs to be inverted. // Likewise sin(pi/2)=1 and sin(3pi/2)=-1, whereas we actually want these values to appear on the bottom/top respectively, so the y-coordinate also needs to be inverted.
// //
// We also need to apply the anti-clockwise rotation. // We also need to apply the anti-clockwise rotation.
double rotatedAngle = finalAngle - MathUtils.DegreesToRadians(rotation); double rotatedAngle = finalAngle - float.DegreesToRadians(rotation);
var rotatedCoordinate = -1 * new Vector2((float)Math.Cos(rotatedAngle), (float)Math.Sin(rotatedAngle)); var rotatedCoordinate = -1 * new Vector2((float)Math.Cos(rotatedAngle), (float)Math.Sin(rotatedAngle));
Vector2 localCentre = new Vector2(points_per_dimension - 1) / 2; Vector2 localCentre = new Vector2(points_per_dimension - 1) / 2;

View File

@ -432,7 +432,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
CultureInfo.CurrentCulture = originalCulture; CultureInfo.CurrentCulture = originalCulture;
} }
private class TestLegacyScoreDecoder : LegacyScoreDecoder public class TestLegacyScoreDecoder : LegacyScoreDecoder
{ {
private readonly int beatmapVersion; private readonly int beatmapVersion;

View File

@ -0,0 +1,55 @@
// 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.IO;
using NUnit.Framework;
using osu.Game.Beatmaps.Formats;
using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Scoring.Legacy;
using osu.Game.Tests.Resources;
namespace osu.Game.Tests.Beatmaps.Formats
{
public class LegacyScoreEncoderTest
{
[TestCase(1, 3)]
[TestCase(1, 0)]
[TestCase(0, 3)]
public void CatchMergesFruitAndDropletMisses(int missCount, int largeTickMissCount)
{
var ruleset = new CatchRuleset().RulesetInfo;
var scoreInfo = TestResources.CreateTestScoreInfo(ruleset);
var beatmap = new TestBeatmap(ruleset);
scoreInfo.Statistics = new Dictionary<HitResult, int>
{
[HitResult.Great] = 50,
[HitResult.LargeTickHit] = 5,
[HitResult.Miss] = missCount,
[HitResult.LargeTickMiss] = largeTickMissCount
};
var score = new Score { ScoreInfo = scoreInfo };
var decodedAfterEncode = encodeThenDecode(LegacyBeatmapDecoder.LATEST_VERSION, score, beatmap);
Assert.That(decodedAfterEncode.ScoreInfo.GetCountMiss(), Is.EqualTo(missCount + largeTickMissCount));
}
private static Score encodeThenDecode(int beatmapVersion, Score score, TestBeatmap beatmap)
{
var encodeStream = new MemoryStream();
var encoder = new LegacyScoreEncoder(score, beatmap);
encoder.Encode(encodeStream);
var decodeStream = new MemoryStream(encodeStream.GetBuffer());
var decoder = new LegacyScoreDecoderTest.TestLegacyScoreDecoder(beatmapVersion);
var decodedAfterEncode = decoder.Parse(decodeStream);
return decodedAfterEncode;
}
}
}

View File

@ -60,6 +60,8 @@ namespace osu.Game.Tests.Skins
"Archives/modified-argon-20231106.osk", "Archives/modified-argon-20231106.osk",
// Covers "Argon" accuracy/score/combo counters, and wedges // Covers "Argon" accuracy/score/combo counters, and wedges
"Archives/modified-argon-20231108.osk", "Archives/modified-argon-20231108.osk",
// Covers "Argon" performance points counter
"Archives/modified-argon-20240305.osk",
}; };
/// <summary> /// <summary>

View File

@ -1,8 +1,8 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Skinning; using osu.Game.Skinning;
@ -13,8 +13,13 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset(); protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset();
[SetUp] [SetUpSteps]
public void SetUp() => Schedule(() => public virtual void SetUpSteps()
{
AddStep("setup components", SetUpComponents);
}
public void SetUpComponents()
{ {
SetContents(skin => SetContents(skin =>
{ {
@ -28,7 +33,7 @@ namespace osu.Game.Tests.Visual.Gameplay
implementation.Origin = Anchor.Centre; implementation.Origin = Anchor.Centre;
return implementation; return implementation;
}); });
}); }
protected abstract Drawable CreateDefaultImplementation(); protected abstract Drawable CreateDefaultImplementation();
protected virtual Drawable CreateArgonImplementation() => CreateDefaultImplementation(); protected virtual Drawable CreateArgonImplementation() => CreateDefaultImplementation();

View File

@ -1,26 +1,109 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using System.Linq;
using Moq;
using Newtonsoft.Json.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Online.API;
using osu.Game.Online.Notifications.WebSocket;
using osu.Game.Online.Notifications.WebSocket.Events;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Users; using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay namespace osu.Game.Tests.Visual.Gameplay
{ {
[TestFixture] [TestFixture]
public partial class TestSceneMedalOverlay : OsuTestScene public partial class TestSceneMedalOverlay : OsuManualInputManagerTestScene
{ {
public TestSceneMedalOverlay() private readonly Bindable<OverlayActivation> overlayActivationMode = new Bindable<OverlayActivation>(OverlayActivation.All);
private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;
private MedalOverlay overlay = null!;
[SetUpSteps]
public void SetUpSteps()
{ {
AddStep(@"display", () => var overlayManagerMock = new Mock<IOverlayManager>();
overlayManagerMock.Setup(mock => mock.OverlayActivationMode).Returns(overlayActivationMode);
AddStep("create overlay", () => Child = new DependencyProvidingContainer
{ {
LoadComponentAsync(new MedalOverlay(new Medal Child = overlay = new MedalOverlay(),
{ RelativeSizeAxes = Axes.Both,
Name = @"Animations", CachedDependencies =
InternalName = @"all-intro-doubletime", [
Description = @"More complex than you think.", (typeof(IOverlayManager), overlayManagerMock.Object)
}), Add); ]
}); });
} }
[Test]
public void TestBasicAward()
{
awardMedal(new UserAchievementUnlock
{
Title = "Time And A Half",
Description = "Having a right ol' time. One and a half of them, almost.",
Slug = @"all-intro-doubletime"
});
AddUntilStep("overlay shown", () => overlay.State.Value, () => Is.EqualTo(Visibility.Visible));
AddUntilStep("wait for load", () => this.ChildrenOfType<MedalAnimation>().Any());
AddRepeatStep("dismiss", () => InputManager.Key(Key.Escape), 2);
AddUntilStep("overlay hidden", () => overlay.State.Value, () => Is.EqualTo(Visibility.Hidden));
}
[Test]
public void TestMultipleMedalsInQuickSuccession()
{
awardMedal(new UserAchievementUnlock
{
Title = "Time And A Half",
Description = "Having a right ol' time. One and a half of them, almost.",
Slug = @"all-intro-doubletime"
});
awardMedal(new UserAchievementUnlock
{
Title = "S-Ranker",
Description = "Accuracy is really underrated.",
Slug = @"all-secret-rank-s"
});
awardMedal(new UserAchievementUnlock
{
Title = "500 Combo",
Description = "500 big ones! You're moving up in the world!",
Slug = @"osu-combo-500"
});
}
[Test]
public void TestDelayMedalDisplayUntilActivationModeAllowsIt()
{
AddStep("disable overlay activation", () => overlayActivationMode.Value = OverlayActivation.Disabled);
awardMedal(new UserAchievementUnlock
{
Title = "Time And A Half",
Description = "Having a right ol' time. One and a half of them, almost.",
Slug = @"all-intro-doubletime"
});
AddUntilStep("overlay hidden", () => overlay.State.Value, () => Is.EqualTo(Visibility.Hidden));
AddStep("re-enable overlay activation", () => overlayActivationMode.Value = OverlayActivation.All);
AddUntilStep("overlay shown", () => overlay.State.Value, () => Is.EqualTo(Visibility.Visible));
}
private void awardMedal(UserAchievementUnlock unlock) => AddStep("award medal", () => dummyAPI.NotificationsClient.Receive(new SocketMessage
{
Event = @"new",
Data = JObject.FromObject(new NewPrivateNotificationEvent
{
Name = @"user_achievement_unlock",
Details = JObject.FromObject(unlock)
})
}));
} }
} }

View File

@ -3,102 +3,89 @@
#nullable disable #nullable disable
using System; using System.Linq;
using System.Diagnostics;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Rulesets.Osu.Scoring;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD;
using osuTK; using osu.Game.Skinning.Triangles;
using osu.Game.Tests.Gameplay;
namespace osu.Game.Tests.Visual.Gameplay namespace osu.Game.Tests.Visual.Gameplay
{ {
public partial class TestScenePerformancePointsCounter : OsuTestScene public partial class TestScenePerformancePointsCounter : SkinnableHUDComponentTestScene
{ {
private DependencyProvidingContainer dependencyContainer; [Cached(typeof(ScoreProcessor))]
private readonly ScoreProcessor scoreProcessor = new OsuScoreProcessor();
private GameplayState gameplayState; [Cached]
private ScoreProcessor scoreProcessor; private readonly GameplayState gameplayState = TestGameplayState.Create(new OsuRuleset());
private int iteration; private int iteration;
private Bindable<JudgementResult> lastJudgementResult = new Bindable<JudgementResult>();
private PerformancePointsCounter counter;
[SetUpSteps] protected override Drawable CreateDefaultImplementation() => new TrianglesPerformancePointsCounter();
public void SetUpSteps() => AddStep("create components", () => protected override Drawable CreateArgonImplementation() => new ArgonPerformancePointsCounter();
protected override Drawable CreateLegacyImplementation() => Empty();
private Bindable<JudgementResult> lastJudgementResult => (Bindable<JudgementResult>)gameplayState.LastJudgementResult;
public override void SetUpSteps()
{ {
var ruleset = CreateRuleset(); AddStep("reset", () =>
Debug.Assert(ruleset != null);
var beatmap = CreateWorkingBeatmap(ruleset.RulesetInfo)
.GetPlayableBeatmap(ruleset.RulesetInfo);
lastJudgementResult = new Bindable<JudgementResult>();
gameplayState = new GameplayState(beatmap, ruleset);
gameplayState.LastJudgementResult.BindTo(lastJudgementResult);
scoreProcessor = new ScoreProcessor(ruleset);
Child = dependencyContainer = new DependencyProvidingContainer
{ {
RelativeSizeAxes = Axes.Both, var ruleset = new OsuRuleset();
CachedDependencies = new (Type, object)[] var beatmap = CreateWorkingBeatmap(ruleset.RulesetInfo)
{ .GetPlayableBeatmap(ruleset.RulesetInfo);
(typeof(GameplayState), gameplayState),
(typeof(ScoreProcessor), scoreProcessor)
}
};
iteration = 0; iteration = 0;
}); scoreProcessor.ApplyBeatmap(beatmap);
lastJudgementResult.SetDefault();
});
protected override Ruleset CreateRuleset() => new OsuRuleset(); base.SetUpSteps();
}
private void createCounter() => AddStep("Create counter", () => [Test]
public void TestDisplay()
{ {
dependencyContainer.Child = counter = new PerformancePointsCounter AddSliderStep("pp", 0, 2000, 0, v => this.ChildrenOfType<PerformancePointsCounter>().ForEach(c => c.Current.Value = v));
{ AddToggleStep("toggle validity", v => this.ChildrenOfType<PerformancePointsCounter>().ForEach(c => c.IsValid = v));
Anchor = Anchor.Centre, }
Origin = Anchor.Centre,
Scale = new Vector2(5),
};
});
[Test] [Test]
public void TestBasicCounting() public void TestBasicCounting()
{ {
int previousValue = 0; int previousValue = 0;
createCounter();
AddAssert("counter displaying zero", () => counter.Current.Value == 0); AddAssert("counter displaying zero", () => this.ChildrenOfType<PerformancePointsCounter>().All(c => c.Current.Value == 0));
AddRepeatStep("Add judgement", applyOneJudgement, 10); AddRepeatStep("Add judgement", applyOneJudgement, 10);
AddUntilStep("counter non-zero", () => counter.Current.Value > 0); AddUntilStep("counter non-zero", () => this.ChildrenOfType<PerformancePointsCounter>().All(c => c.Current.Value > 0));
AddUntilStep("counter opaque", () => counter.Child.Alpha == 1); AddUntilStep("counter valid", () => this.ChildrenOfType<PerformancePointsCounter>().All(c => c.IsValid));
AddStep("Revert judgement", () => AddStep("Revert judgement", () =>
{ {
previousValue = counter.Current.Value; previousValue = this.ChildrenOfType<PerformancePointsCounter>().First().Current.Value;
scoreProcessor.RevertResult(new JudgementResult(new HitObject(), new OsuJudgement())); scoreProcessor.RevertResult(new JudgementResult(new HitObject(), new OsuJudgement()));
}); });
AddUntilStep("counter decreased", () => counter.Current.Value < previousValue); AddUntilStep("counter decreased", () => this.ChildrenOfType<PerformancePointsCounter>().All(c => c.Current.Value < previousValue));
AddStep("Add judgement", applyOneJudgement); AddStep("Add judgement", applyOneJudgement);
AddUntilStep("counter non-zero", () => counter.Current.Value > 0); AddUntilStep("counter non-zero", () => this.ChildrenOfType<PerformancePointsCounter>().All(c => c.Current.Value > 0));
} }
[Test] [Test]
@ -106,10 +93,10 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
AddRepeatStep("Add judgement", applyOneJudgement, 10); AddRepeatStep("Add judgement", applyOneJudgement, 10);
createCounter(); AddStep("recreate counter", SetUpComponents);
AddUntilStep("counter non-zero", () => counter.Current.Value > 0); AddUntilStep("counter non-zero", () => this.ChildrenOfType<PerformancePointsCounter>().All(c => c.Current.Value > 0));
AddUntilStep("counter opaque", () => counter.Child.Alpha == 1); AddUntilStep("counter valid", () => this.ChildrenOfType<PerformancePointsCounter>().All(c => c.IsValid));
} }
private void applyOneJudgement() private void applyOneJudgement()

View File

@ -121,7 +121,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private TextureUpload upscale(TextureUpload textureUpload) private TextureUpload upscale(TextureUpload textureUpload)
{ {
var image = Image.LoadPixelData(textureUpload.Data.ToArray(), textureUpload.Width, textureUpload.Height); var image = Image.LoadPixelData(textureUpload.Data, textureUpload.Width, textureUpload.Height);
// The original texture upload will no longer be returned or used. // The original texture upload will no longer be returned or used.
textureUpload.Dispose(); textureUpload.Dispose();

View File

@ -4,7 +4,6 @@
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD;
@ -21,10 +20,11 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override Drawable CreateDefaultImplementation() => new DefaultAccuracyCounter(); protected override Drawable CreateDefaultImplementation() => new DefaultAccuracyCounter();
protected override Drawable CreateLegacyImplementation() => new LegacyAccuracyCounter(); protected override Drawable CreateLegacyImplementation() => new LegacyAccuracyCounter();
[SetUpSteps] public override void SetUpSteps()
public void SetUpSteps()
{ {
AddStep("Set initial accuracy", () => scoreProcessor.Accuracy.Value = 1); AddStep("Set initial accuracy", () => scoreProcessor.Accuracy.Value = 1);
base.SetUpSteps();
} }
[Test] [Test]

View File

@ -4,7 +4,6 @@
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Judgements;
@ -25,14 +24,15 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override Drawable CreateDefaultImplementation() => new DefaultHealthDisplay { Scale = new Vector2(0.6f) }; protected override Drawable CreateDefaultImplementation() => new DefaultHealthDisplay { Scale = new Vector2(0.6f) };
protected override Drawable CreateLegacyImplementation() => new LegacyHealthDisplay { Scale = new Vector2(0.6f) }; protected override Drawable CreateLegacyImplementation() => new LegacyHealthDisplay { Scale = new Vector2(0.6f) };
[SetUpSteps] public override void SetUpSteps()
public void SetUpSteps()
{ {
AddStep(@"Reset all", delegate AddStep(@"Reset all", delegate
{ {
healthProcessor.Health.Value = 1; healthProcessor.Health.Value = 1;
healthProcessor.Failed += () => false; // health won't be updated if the processor gets into a "fail" state. healthProcessor.Failed += () => false; // health won't be updated if the processor gets into a "fail" state.
}); });
base.SetUpSteps();
} }
protected override void Update() protected override void Update()

View File

@ -22,6 +22,7 @@ using osu.Game.Overlays;
using osu.Game.Overlays.Dialog; using osu.Game.Overlays.Dialog;
using osu.Game.Overlays.Mods; using osu.Game.Overlays.Mods;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Taiko; using osu.Game.Rulesets.Taiko;
@ -286,6 +287,41 @@ namespace osu.Game.Tests.Visual.Multiplayer
}); });
} }
[Test]
[FlakyTest] // See above
public void TestModSelectOverlay()
{
AddStep("add playlist item", () =>
{
SelectedRoom.Value.Playlist.Add(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
RequiredMods = new[]
{
new APIMod(new OsuModDoubleTime { SpeedChange = { Value = 2.0 } }),
new APIMod(new OsuModStrictTracking()),
},
AllowedMods = new[]
{
new APIMod(new OsuModFlashlight()),
}
});
});
ClickButtonWhenEnabled<MultiplayerMatchSettingsOverlay.CreateOrUpdateButton>();
AddUntilStep("wait for join", () => RoomJoined);
ClickButtonWhenEnabled<RoomSubScreen.UserModSelectButton>();
AddAssert("mod select shows unranked", () => screen.UserModsSelectOverlay.ChildrenOfType<RankingInformationDisplay>().Single().Ranked.Value == false);
AddAssert("score multiplier = 1.20", () => screen.UserModsSelectOverlay.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value, () => Is.EqualTo(1.2).Within(0.01));
AddStep("select flashlight", () => screen.UserModsSelectOverlay.ChildrenOfType<ModPanel>().Single(m => m.Mod is ModFlashlight).TriggerClick());
AddAssert("score multiplier = 1.35", () => screen.UserModsSelectOverlay.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value, () => Is.EqualTo(1.35).Within(0.01));
AddStep("change flashlight setting", () => ((OsuModFlashlight)screen.UserModsSelectOverlay.SelectedMods.Value.Single()).FollowDelay.Value = 1200);
AddAssert("score multiplier = 1.20", () => screen.UserModsSelectOverlay.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value, () => Is.EqualTo(1.2).Within(0.01));
}
private partial class TestMultiplayerMatchSubScreen : MultiplayerMatchSubScreen private partial class TestMultiplayerMatchSubScreen : MultiplayerMatchSubScreen
{ {
[Resolved(canBeNull: true)] [Resolved(canBeNull: true)]

View File

@ -6,6 +6,7 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Newtonsoft.Json.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Configuration; using osu.Framework.Configuration;
@ -24,6 +25,8 @@ using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Online.Leaderboards; using osu.Game.Online.Leaderboards;
using osu.Game.Online.Notifications.WebSocket;
using osu.Game.Online.Notifications.WebSocket.Events;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Overlays.BeatmapListing; using osu.Game.Overlays.BeatmapListing;
using osu.Game.Overlays.Mods; using osu.Game.Overlays.Mods;
@ -340,6 +343,28 @@ namespace osu.Game.Tests.Visual.Navigation
AddUntilStep("wait for results", () => Game.ScreenStack.CurrentScreen is ResultsScreen); AddUntilStep("wait for results", () => Game.ScreenStack.CurrentScreen is ResultsScreen);
} }
[Test]
public void TestShowMedalAtResults()
{
playToResults();
AddStep("award medal", () => ((DummyAPIAccess)API).NotificationsClient.Receive(new SocketMessage
{
Event = @"new",
Data = JObject.FromObject(new NewPrivateNotificationEvent
{
Name = @"user_achievement_unlock",
Details = JObject.FromObject(new UserAchievementUnlock
{
Title = "Time And A Half",
Description = "Having a right ol' time. One and a half of them, almost.",
Slug = @"all-intro-doubletime"
})
})
}));
AddUntilStep("medal overlay shown", () => Game.ChildrenOfType<MedalOverlay>().Single().State.Value, () => Is.EqualTo(Visibility.Visible));
}
[Test] [Test]
public void TestRetryFromResults() public void TestRetryFromResults()
{ {

View File

@ -0,0 +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.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Online.Leaderboards;
using osu.Game.Scoring;
using osuTK;
namespace osu.Game.Tests.Visual.Ranking
{
public partial class TestSceneDrawableRank : OsuTestScene
{
[Test]
public void TestAllRanks()
{
AddStep("create content", () => Child = new FillFlowContainer<DrawableRank>
{
AutoSizeAxes = Axes.X,
RelativeSizeAxes = Axes.Y,
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Direction = FillDirection.Vertical,
Padding = new MarginPadding(20),
Spacing = new Vector2(10),
ChildrenEnumerable = Enum.GetValues<ScoreRank>().OrderBy(v => v).Select(rank => new DrawableRank(rank)
{
RelativeSizeAxes = Axes.None,
Size = new Vector2(50, 25),
Anchor = Anchor.Centre,
Origin = Anchor.Centre
})
});
}
}
}

View File

@ -88,8 +88,20 @@ namespace osu.Game.Tests.Visual.Ranking
AddAssert("play time not displayed", () => !this.ChildrenOfType<ExpandedPanelMiddleContent.PlayedOnText>().Any()); AddAssert("play time not displayed", () => !this.ChildrenOfType<ExpandedPanelMiddleContent.PlayedOnText>().Any());
} }
private void showPanel(ScoreInfo score) => [Test]
Child = new ExpandedPanelMiddleContentContainer(score); public void TestFailedSDisplay([Values] bool withFlair)
{
AddStep("show failed S score", () =>
{
var score = TestResources.CreateTestScoreInfo(createTestBeatmap(new RealmUser()));
score.Rank = ScoreRank.A;
score.Accuracy = 0.975;
showPanel(score, withFlair);
});
}
private void showPanel(ScoreInfo score, bool withFlair = false) =>
Child = new ExpandedPanelMiddleContentContainer(score, withFlair);
private BeatmapInfo createTestBeatmap([NotNull] RealmUser author) private BeatmapInfo createTestBeatmap([NotNull] RealmUser author)
{ {
@ -107,7 +119,7 @@ namespace osu.Game.Tests.Visual.Ranking
private partial class ExpandedPanelMiddleContentContainer : Container private partial class ExpandedPanelMiddleContentContainer : Container
{ {
public ExpandedPanelMiddleContentContainer(ScoreInfo score) public ExpandedPanelMiddleContentContainer(ScoreInfo score, bool withFlair)
{ {
Anchor = Anchor.Centre; Anchor = Anchor.Centre;
Origin = Anchor.Centre; Origin = Anchor.Centre;
@ -119,7 +131,7 @@ namespace osu.Game.Tests.Visual.Ranking
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = Color4Extensions.FromHex("#444"), Colour = Color4Extensions.FromHex("#444"),
}, },
new ExpandedPanelMiddleContent(score) new ExpandedPanelMiddleContent(score, withFlair)
}; };
} }
} }

View File

@ -57,6 +57,7 @@ namespace osu.Game.Tests.Visual.UserInterface
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Mods = { BindTarget = SelectedMods },
}); });
AddStep("set beatmap", () => AddStep("set beatmap", () =>

View File

@ -859,6 +859,30 @@ namespace osu.Game.Tests.Visual.UserInterface
() => modSelectOverlay.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value, () => Is.EqualTo(0.3).Within(Precision.DOUBLE_EPSILON)); () => modSelectOverlay.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value, () => Is.EqualTo(0.3).Within(Precision.DOUBLE_EPSILON));
} }
[Test]
public void TestModSettingsOrder()
{
createScreen();
AddStep("select DT + HD + DF", () => SelectedMods.Value = new Mod[] { new OsuModDoubleTime(), new OsuModHidden(), new OsuModDeflate() });
AddAssert("mod settings order: DT, HD, DF", () =>
{
var columns = this.ChildrenOfType<ModSettingsArea>().Single().ChildrenOfType<ModSettingsArea.ModSettingsColumn>();
return columns.ElementAt(0).Mod is OsuModDoubleTime &&
columns.ElementAt(1).Mod is OsuModHidden &&
columns.ElementAt(2).Mod is OsuModDeflate;
});
AddStep("replace DT with NC", () => SelectedMods.Value = SelectedMods.Value.Where(m => m is not ModDoubleTime).Append(new OsuModNightcore()).ToList());
AddAssert("mod settings order: NC, HD, DF", () =>
{
var columns = this.ChildrenOfType<ModSettingsArea>().Single().ChildrenOfType<ModSettingsArea.ModSettingsColumn>();
return columns.ElementAt(0).Mod is OsuModNightcore &&
columns.ElementAt(1).Mod is OsuModHidden &&
columns.ElementAt(2).Mod is OsuModDeflate;
});
}
private void waitForColumnLoad() => AddUntilStep("all column content loaded", () => private void waitForColumnLoad() => AddUntilStep("all column content loaded", () =>
modSelectOverlay.ChildrenOfType<ModColumn>().Any() modSelectOverlay.ChildrenOfType<ModColumn>().Any()
&& modSelectOverlay.ChildrenOfType<ModColumn>().All(column => column.IsLoaded && column.ItemsLoaded) && modSelectOverlay.ChildrenOfType<ModColumn>().All(column => column.IsLoaded && column.ItemsLoaded)

View File

@ -80,6 +80,8 @@ namespace osu.Game.Audio
yield return $"Gameplay/{Bank}-{Name}{Suffix}"; yield return $"Gameplay/{Bank}-{Name}{Suffix}";
yield return $"Gameplay/{Bank}-{Name}"; yield return $"Gameplay/{Bank}-{Name}";
yield return $"Gameplay/{Name}";
} }
} }

View File

@ -64,7 +64,7 @@ namespace osu.Game.Beatmaps
// The original texture upload will no longer be returned or used. // The original texture upload will no longer be returned or used.
textureUpload.Dispose(); textureUpload.Dispose();
Size size = image.Size(); Size size = image.Size;
// Assume that panel backgrounds are always displayed using `FillMode.Fill`. // Assume that panel backgrounds are always displayed using `FillMode.Fill`.
// Also assume that all backgrounds are wider than they are tall, so the // Also assume that all backgrounds are wider than they are tall, so the

View File

@ -86,11 +86,15 @@ namespace osu.Game.Beatmaps.Drawables.Cards
Dimmed.BindValueChanged(_ => updateState()); Dimmed.BindValueChanged(_ => updateState());
playButton.Playing.BindValueChanged(_ => updateState(), true); playButton.Playing.BindValueChanged(_ => updateState(), true);
((IBindable<double>)progress.Current).BindTo(playButton.Progress);
FinishTransforms(true); FinishTransforms(true);
} }
protected override void Update()
{
base.Update();
progress.Progress = playButton.Progress.Value;
}
private void updateState() private void updateState()
{ {
bool shouldDim = Dimmed.Value || playButton.Playing.Value; bool shouldDim = Dimmed.Value || playButton.Playing.Value;

View File

@ -6,7 +6,6 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Utils;
using osu.Game.Beatmaps.Legacy; using osu.Game.Beatmaps.Legacy;
using osu.Game.IO; using osu.Game.IO;
using osu.Game.Storyboards; using osu.Game.Storyboards;
@ -230,7 +229,7 @@ namespace osu.Game.Beatmaps.Formats
{ {
float startValue = Parsing.ParseFloat(split[4]); float startValue = Parsing.ParseFloat(split[4]);
float endValue = split.Length > 5 ? Parsing.ParseFloat(split[5]) : startValue; float endValue = split.Length > 5 ? Parsing.ParseFloat(split[5]) : startValue;
timelineGroup?.Rotation.Add(easing, startTime, endTime, MathUtils.RadiansToDegrees(startValue), MathUtils.RadiansToDegrees(endValue)); timelineGroup?.Rotation.Add(easing, startTime, endTime, float.RadiansToDegrees(startValue), float.RadiansToDegrees(endValue));
break; break;
} }

View File

@ -23,8 +23,7 @@ namespace osu.Game.Beatmaps.Timing
public TimeSignature(int numerator) public TimeSignature(int numerator)
{ {
if (numerator < 1) ArgumentOutOfRangeException.ThrowIfNegativeOrZero(numerator);
throw new ArgumentOutOfRangeException(nameof(numerator), numerator, "The numerator of a time signature must be positive.");
Numerator = numerator; Numerator = numerator;
} }

View File

@ -489,8 +489,7 @@ namespace osu.Game.Database
/// <param name="action">The work to run.</param> /// <param name="action">The work to run.</param>
public Task WriteAsync(Action<Realm> action) public Task WriteAsync(Action<Realm> action)
{ {
if (isDisposed) ObjectDisposedException.ThrowIf(isDisposed, this);
throw new ObjectDisposedException(nameof(RealmAccess));
// Required to ensure the write is tracked and accounted for before disposal. // Required to ensure the write is tracked and accounted for before disposal.
// Can potentially be avoided if we have a need to do so in the future. // Can potentially be avoided if we have a need to do so in the future.
@ -675,8 +674,7 @@ namespace osu.Game.Database
private Realm getRealmInstance() private Realm getRealmInstance()
{ {
if (isDisposed) ObjectDisposedException.ThrowIf(isDisposed, this);
throw new ObjectDisposedException(nameof(RealmAccess));
bool tookSemaphoreLock = false; bool tookSemaphoreLock = false;
@ -1189,8 +1187,7 @@ namespace osu.Game.Database
if (!ThreadSafety.IsUpdateThread) if (!ThreadSafety.IsUpdateThread)
throw new InvalidOperationException(@$"{nameof(BlockAllOperations)} must be called from the update thread."); throw new InvalidOperationException(@$"{nameof(BlockAllOperations)} must be called from the update thread.");
if (isDisposed) ObjectDisposedException.ThrowIf(isDisposed, this);
throw new ObjectDisposedException(nameof(RealmAccess));
SynchronizationContext? syncContext = null; SynchronizationContext? syncContext = null;

View File

@ -415,7 +415,7 @@ namespace osu.Game.Database
// Calculate how many times the longest combo the user has achieved in the play can repeat // Calculate how many times the longest combo the user has achieved in the play can repeat
// without exceeding the combo portion in score V1 as achieved by the player. // without exceeding the combo portion in score V1 as achieved by the player.
// This is a pessimistic estimate; it intentionally does not operate on object count and uses only score instead. // This intentionally does not operate on object count and uses only score instead.
double maximumOccurrencesOfLongestCombo = Math.Floor(comboPortionInScoreV1 / comboPortionFromLongestComboInScoreV1); double maximumOccurrencesOfLongestCombo = Math.Floor(comboPortionInScoreV1 / comboPortionFromLongestComboInScoreV1);
double comboPortionFromRepeatedLongestCombosInScoreV1 = maximumOccurrencesOfLongestCombo * comboPortionFromLongestComboInScoreV1; double comboPortionFromRepeatedLongestCombosInScoreV1 = maximumOccurrencesOfLongestCombo * comboPortionFromLongestComboInScoreV1;
@ -426,13 +426,12 @@ namespace osu.Game.Database
// ...and then based on that raw combo length, we calculate how much this last combo is worth in standardised score. // ...and then based on that raw combo length, we calculate how much this last combo is worth in standardised score.
double remainingComboPortionInStandardisedScore = Math.Pow(remainingCombo, 1 + ScoreProcessor.COMBO_EXPONENT); double remainingComboPortionInStandardisedScore = Math.Pow(remainingCombo, 1 + ScoreProcessor.COMBO_EXPONENT);
double lowerEstimateOfComboPortionInStandardisedScore double scoreBasedEstimateOfComboPortionInStandardisedScore
= maximumOccurrencesOfLongestCombo * comboPortionFromLongestComboInStandardisedScore = maximumOccurrencesOfLongestCombo * comboPortionFromLongestComboInStandardisedScore
+ remainingComboPortionInStandardisedScore; + remainingComboPortionInStandardisedScore;
// Compute approximate upper estimate new score for that play. // Compute approximate upper estimate new score for that play.
// This time, divide the remaining combo among remaining objects equally to achieve longest possible combo lengths. // This time, divide the remaining combo among remaining objects equally to achieve longest possible combo lengths.
// There is no rigorous proof that doing this will yield a correct upper bound, but it seems to work out in practice.
remainingComboPortionInScoreV1 = comboPortionInScoreV1 - comboPortionFromLongestComboInScoreV1; remainingComboPortionInScoreV1 = comboPortionInScoreV1 - comboPortionFromLongestComboInScoreV1;
double remainingCountOfObjectsGivingCombo = maximumLegacyCombo - score.MaxCombo - score.Statistics.GetValueOrDefault(HitResult.Miss); double remainingCountOfObjectsGivingCombo = maximumLegacyCombo - score.MaxCombo - score.Statistics.GetValueOrDefault(HitResult.Miss);
// Because we assumed all combos were equal, `remainingComboPortionInScoreV1` // Because we assumed all combos were equal, `remainingComboPortionInScoreV1`
@ -449,7 +448,17 @@ namespace osu.Game.Database
// we can skip adding the 1 and just multiply by x ^ 0.5. // we can skip adding the 1 and just multiply by x ^ 0.5.
remainingComboPortionInStandardisedScore = remainingCountOfObjectsGivingCombo * Math.Pow(lengthOfRemainingCombos, ScoreProcessor.COMBO_EXPONENT); remainingComboPortionInStandardisedScore = remainingCountOfObjectsGivingCombo * Math.Pow(lengthOfRemainingCombos, ScoreProcessor.COMBO_EXPONENT);
double upperEstimateOfComboPortionInStandardisedScore = comboPortionFromLongestComboInStandardisedScore + remainingComboPortionInStandardisedScore; double objectCountBasedEstimateOfComboPortionInStandardisedScore = comboPortionFromLongestComboInStandardisedScore + remainingComboPortionInStandardisedScore;
// Enforce some invariants on both of the estimates.
// In rare cases they can produce invalid results.
scoreBasedEstimateOfComboPortionInStandardisedScore =
Math.Clamp(scoreBasedEstimateOfComboPortionInStandardisedScore, 0, maximumAchievableComboPortionInStandardisedScore);
objectCountBasedEstimateOfComboPortionInStandardisedScore =
Math.Clamp(objectCountBasedEstimateOfComboPortionInStandardisedScore, 0, maximumAchievableComboPortionInStandardisedScore);
double lowerEstimateOfComboPortionInStandardisedScore = Math.Min(scoreBasedEstimateOfComboPortionInStandardisedScore, objectCountBasedEstimateOfComboPortionInStandardisedScore);
double upperEstimateOfComboPortionInStandardisedScore = Math.Max(scoreBasedEstimateOfComboPortionInStandardisedScore, objectCountBasedEstimateOfComboPortionInStandardisedScore);
// Approximate by combining lower and upper estimates. // Approximate by combining lower and upper estimates.
// As the lower-estimate is very pessimistic, we use a 30/70 ratio // As the lower-estimate is very pessimistic, we use a 30/70 ratio

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Audio.Sample; using osu.Framework.Audio.Sample;
@ -20,10 +18,10 @@ namespace osu.Game.Graphics.Containers
[Cached(typeof(IPreviewTrackOwner))] [Cached(typeof(IPreviewTrackOwner))]
public abstract partial class OsuFocusedOverlayContainer : FocusedOverlayContainer, IPreviewTrackOwner, IKeyBindingHandler<GlobalAction> public abstract partial class OsuFocusedOverlayContainer : FocusedOverlayContainer, IPreviewTrackOwner, IKeyBindingHandler<GlobalAction>
{ {
private Sample samplePopIn; protected readonly IBindable<OverlayActivation> OverlayActivationMode = new Bindable<OverlayActivation>(OverlayActivation.All);
private Sample samplePopOut;
protected virtual string PopInSampleName => "UI/overlay-pop-in"; protected virtual string? PopInSampleName => @"UI/overlay-pop-in";
protected virtual string PopOutSampleName => "UI/overlay-pop-out"; protected virtual string? PopOutSampleName => @"UI/overlay-pop-out";
protected virtual double PopInOutSampleBalance => 0; protected virtual double PopInOutSampleBalance => 0;
protected override bool BlockNonPositionalInput => true; protected override bool BlockNonPositionalInput => true;
@ -34,19 +32,23 @@ namespace osu.Game.Graphics.Containers
/// </summary> /// </summary>
protected virtual bool DimMainContent => true; protected virtual bool DimMainContent => true;
[Resolved(CanBeNull = true)] [Resolved]
private IOverlayManager overlayManager { get; set; } private IOverlayManager? overlayManager { get; set; }
[Resolved] [Resolved]
private PreviewTrackManager previewTrackManager { get; set; } private PreviewTrackManager previewTrackManager { get; set; } = null!;
protected readonly IBindable<OverlayActivation> OverlayActivationMode = new Bindable<OverlayActivation>(OverlayActivation.All); private Sample? samplePopIn;
private Sample? samplePopOut;
[BackgroundDependencyLoader(true)] [BackgroundDependencyLoader]
private void load(AudioManager audio) private void load(AudioManager? audio)
{ {
samplePopIn = audio.Samples.Get(PopInSampleName); if (!string.IsNullOrEmpty(PopInSampleName))
samplePopOut = audio.Samples.Get(PopOutSampleName); samplePopIn = audio?.Samples.Get(PopInSampleName);
if (!string.IsNullOrEmpty(PopOutSampleName))
samplePopOut = audio?.Samples.Get(PopOutSampleName);
} }
protected override void LoadComplete() protected override void LoadComplete()

View File

@ -157,7 +157,7 @@ namespace osu.Game.Graphics.Cursor
if (dragRotationState == DragRotationState.Rotating && distance > 0) if (dragRotationState == DragRotationState.Rotating && distance > 0)
{ {
Vector2 offset = e.MousePosition - positionMouseDown; Vector2 offset = e.MousePosition - positionMouseDown;
float degrees = MathUtils.RadiansToDegrees(MathF.Atan2(-offset.X, offset.Y)) + 24.3f; float degrees = float.RadiansToDegrees(MathF.Atan2(-offset.X, offset.Y)) + 24.3f;
// Always rotate in the direction of least distance // Always rotate in the direction of least distance
float diff = (degrees - activeCursor.Rotation) % 360; float diff = (degrees - activeCursor.Rotation) % 360;

View File

@ -63,8 +63,12 @@ namespace osu.Game.Graphics
case ScoreRank.C: case ScoreRank.C:
return Color4Extensions.FromHex(@"ff8e5d"); return Color4Extensions.FromHex(@"ff8e5d");
default: case ScoreRank.D:
return Color4Extensions.FromHex(@"ff5a5a"); return Color4Extensions.FromHex(@"ff5a5a");
case ScoreRank.F:
default:
return Color4Extensions.FromHex(@"3f3f3f");
} }
} }

View File

@ -1,14 +1,12 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Extensions;
namespace osu.Game.Graphics.UserInterface namespace osu.Game.Graphics.UserInterface
{ {
public partial class OsuNumberBox : OsuTextBox public partial class OsuNumberBox : OsuTextBox
{ {
protected override bool AllowIme => false; protected override bool AllowIme => false;
protected override bool CanAddCharacter(char character) => character.IsAsciiDigit(); protected override bool CanAddCharacter(char character) => char.IsAsciiDigit(character);
} }
} }

View File

@ -116,18 +116,18 @@ namespace osu.Game.Graphics.UserInterface
} }
} }
private const float transition_length = 500; protected const float TRANSITION_LENGTH = 500;
protected void FadeHovered() protected virtual void FadeHovered()
{ {
Bar.FadeIn(transition_length, Easing.OutQuint); Bar.FadeIn(TRANSITION_LENGTH, Easing.OutQuint);
Text.FadeColour(Color4.White, transition_length, Easing.OutQuint); Text.FadeColour(Color4.White, TRANSITION_LENGTH, Easing.OutQuint);
} }
protected void FadeUnhovered() protected virtual void FadeUnhovered()
{ {
Bar.FadeTo(IsHovered ? 1 : 0, transition_length, Easing.OutQuint); Bar.FadeTo(IsHovered ? 1 : 0, TRANSITION_LENGTH, Easing.OutQuint);
Text.FadeColour(IsHovered ? Color4.White : AccentColour, transition_length, Easing.OutQuint); Text.FadeColour(IsHovered ? Color4.White : AccentColour, TRANSITION_LENGTH, Easing.OutQuint);
} }
protected override bool OnHover(HoverEvent e) protected override bool OnHover(HoverEvent e)

View File

@ -8,7 +8,6 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Microsoft.Toolkit.HighPerformance; using Microsoft.Toolkit.HighPerformance;
using osu.Framework.Extensions;
using osu.Framework.IO.Stores; using osu.Framework.IO.Stores;
using SharpCompress.Archives.Zip; using SharpCompress.Archives.Zip;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
@ -36,7 +35,7 @@ namespace osu.Game.IO.Archives
var owner = MemoryAllocator.Default.Allocate<byte>((int)entry.Size); var owner = MemoryAllocator.Default.Allocate<byte>((int)entry.Size);
using (Stream s = entry.OpenEntryStream()) using (Stream s = entry.OpenEntryStream())
s.ReadToFill(owner.Memory.Span); s.ReadExactly(owner.Memory.Span);
return new MemoryOwnerMemoryStream(owner); return new MemoryOwnerMemoryStream(owner);
} }

View File

@ -80,8 +80,7 @@ namespace osu.Game.IO
public override Storage GetStorageForDirectory(string path) public override Storage GetStorageForDirectory(string path)
{ {
if (string.IsNullOrEmpty(path)) ArgumentException.ThrowIfNullOrEmpty(path);
throw new ArgumentException("Must be non-null and not empty string", nameof(path));
if (!path.EndsWith(Path.DirectorySeparatorChar)) if (!path.EndsWith(Path.DirectorySeparatorChar))
path += Path.DirectorySeparatorChar; path += Path.DirectorySeparatorChar;

View File

@ -245,8 +245,8 @@ namespace osu.Game.Online.API.Requests.Responses
RulesetID = score.RulesetID, RulesetID = score.RulesetID,
Passed = score.Passed, Passed = score.Passed,
Mods = score.APIMods, Mods = score.APIMods,
Statistics = score.Statistics.Where(kvp => kvp.Value != 0).ToDictionary(kvp => kvp.Key, kvp => kvp.Value), Statistics = score.Statistics.Where(kvp => kvp.Value != 0).ToDictionary(),
MaximumStatistics = score.MaximumStatistics.Where(kvp => kvp.Value != 0).ToDictionary(kvp => kvp.Key, kvp => kvp.Value), MaximumStatistics = score.MaximumStatistics.Where(kvp => kvp.Value != 0).ToDictionary(),
}; };
} }
} }

View File

@ -13,6 +13,8 @@ using osu.Framework.Logging;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests;
using osu.Game.Online.Notifications.WebSocket; using osu.Game.Online.Notifications.WebSocket;
using osu.Game.Online.Notifications.WebSocket.Events;
using osu.Game.Online.Notifications.WebSocket.Requests;
namespace osu.Game.Online.Chat namespace osu.Game.Online.Chat
{ {

View File

@ -95,8 +95,12 @@ namespace osu.Game.Online.Leaderboards
case ScoreRank.C: case ScoreRank.C:
return Color4Extensions.FromHex(@"473625"); return Color4Extensions.FromHex(@"473625");
default: case ScoreRank.D:
return Color4Extensions.FromHex(@"512525"); return Color4Extensions.FromHex(@"512525");
case ScoreRank.F:
default:
return Color4Extensions.FromHex(@"CC3333");
} }
} }
} }

View File

@ -8,7 +8,7 @@ using Newtonsoft.Json;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Chat; using osu.Game.Online.Chat;
namespace osu.Game.Online.Notifications.WebSocket namespace osu.Game.Online.Notifications.WebSocket.Events
{ {
/// <summary> /// <summary>
/// A websocket message sent from the server when new messages arrive. /// A websocket message sent from the server when new messages arrive.

View File

@ -0,0 +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 Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace osu.Game.Online.Notifications.WebSocket.Events
{
/// <summary>
/// Reference: https://github.com/ppy/osu-web/blob/master/app/Events/NewPrivateNotificationEvent.php
/// </summary>
public class NewPrivateNotificationEvent
{
[JsonProperty("id")]
public ulong ID { get; set; }
[JsonProperty("name")]
public string Name { get; set; } = string.Empty;
[JsonProperty("created_at")]
public DateTimeOffset CreatedAt { get; set; }
[JsonProperty("object_type")]
public string ObjectType { get; set; } = string.Empty;
[JsonProperty("object_id")]
public ulong ObjectId { get; set; }
[JsonProperty("source_user_id")]
public uint SourceUserID { get; set; }
[JsonProperty("is_read")]
public bool IsRead { get; set; }
[JsonProperty("details")]
public JObject? Details { get; set; }
}
}

View File

@ -0,0 +1,34 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using Newtonsoft.Json;
namespace osu.Game.Online.Notifications.WebSocket.Events
{
/// <summary>
/// Reference: https://github.com/ppy/osu-web/blob/master/app/Jobs/Notifications/UserAchievementUnlock.php
/// </summary>
public class UserAchievementUnlock
{
[JsonProperty("achievement_id")]
public uint AchievementId { get; set; }
[JsonProperty("achievement_mode")]
public ushort? AchievementMode { get; set; }
[JsonProperty("cover_url")]
public string CoverUrl { get; set; } = string.Empty;
[JsonProperty("slug")]
public string Slug { get; set; } = string.Empty;
[JsonProperty("title")]
public string Title { get; set; } = string.Empty;
[JsonProperty("description")]
public string Description { get; set; } = string.Empty;
[JsonProperty("user_id")]
public uint UserId { get; set; }
}
}

View File

@ -3,7 +3,7 @@
using Newtonsoft.Json; using Newtonsoft.Json;
namespace osu.Game.Online.Notifications.WebSocket namespace osu.Game.Online.Notifications.WebSocket.Requests
{ {
/// <summary> /// <summary>
/// A websocket message notifying the server that the client no longer wants to receive chat messages. /// A websocket message notifying the server that the client no longer wants to receive chat messages.

View File

@ -3,7 +3,7 @@
using Newtonsoft.Json; using Newtonsoft.Json;
namespace osu.Game.Online.Notifications.WebSocket namespace osu.Game.Online.Notifications.WebSocket.Requests
{ {
/// <summary> /// <summary>
/// A websocket message notifying the server that the client wants to receive chat messages. /// A websocket message notifying the server that the client wants to receive chat messages.

View File

@ -1083,6 +1083,7 @@ namespace osu.Game
loadComponentSingleFile(new AccountCreationOverlay(), topMostOverlayContent.Add, true); loadComponentSingleFile(new AccountCreationOverlay(), topMostOverlayContent.Add, true);
loadComponentSingleFile<IDialogOverlay>(new DialogOverlay(), topMostOverlayContent.Add, true); loadComponentSingleFile<IDialogOverlay>(new DialogOverlay(), topMostOverlayContent.Add, true);
loadComponentSingleFile(new MedalOverlay(), topMostOverlayContent.Add);
loadComponentSingleFile(new BackgroundDataStoreProcessor(), Add); loadComponentSingleFile(new BackgroundDataStoreProcessor(), Add);

View File

@ -0,0 +1,312 @@
// 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 osuTK;
using osuTK.Graphics;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Sprites;
using osu.Game.Users;
using osu.Game.Graphics;
using osu.Game.Graphics.Backgrounds;
using osu.Game.Overlays.MedalSplash;
using osu.Framework.Allocation;
using osu.Framework.Audio.Sample;
using osu.Framework.Audio;
using osu.Framework.Graphics.Textures;
using osu.Framework.Graphics.Shapes;
using System;
using System.Diagnostics;
using osu.Framework.Graphics.Effects;
using osu.Framework.Utils;
namespace osu.Game.Overlays
{
public partial class MedalAnimation : VisibilityContainer
{
public const float DISC_SIZE = 400;
private const float border_width = 5;
private readonly Medal medal;
private readonly Box background;
private readonly Container backgroundStrip, particleContainer;
private readonly BackgroundStrip leftStrip, rightStrip;
private readonly CircularContainer disc;
private readonly Sprite innerSpin, outerSpin;
private DrawableMedal? drawableMedal;
private Sample? getSample;
private readonly Container content;
public MedalAnimation(Medal medal)
{
this.medal = medal;
RelativeSizeAxes = Axes.Both;
Child = content = new Container
{
Alpha = 0,
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
background = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black.Opacity(60),
},
outerSpin = new Sprite
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(DISC_SIZE + 500),
Alpha = 0f,
},
backgroundStrip = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.X,
Height = border_width,
Alpha = 0f,
Children = new[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.CentreRight,
Width = 0.5f,
Padding = new MarginPadding { Right = DISC_SIZE / 2 },
Children = new[]
{
leftStrip = new BackgroundStrip(0f, 1f)
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
},
},
},
new Container
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.CentreLeft,
Width = 0.5f,
Padding = new MarginPadding { Left = DISC_SIZE / 2 },
Children = new[]
{
rightStrip = new BackgroundStrip(1f, 0f),
},
},
},
},
particleContainer = new Container
{
RelativeSizeAxes = Axes.Both,
Alpha = 0f,
},
disc = new CircularContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Alpha = 0f,
Masking = true,
AlwaysPresent = true,
BorderColour = Color4.White,
BorderThickness = border_width,
Size = new Vector2(DISC_SIZE),
Scale = new Vector2(0.8f),
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4Extensions.FromHex(@"05262f"),
},
new Triangles
{
RelativeSizeAxes = Axes.Both,
TriangleScale = 2,
ColourDark = Color4Extensions.FromHex(@"04222b"),
ColourLight = Color4Extensions.FromHex(@"052933"),
},
innerSpin = new Sprite
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(1.05f),
Alpha = 0.25f,
},
},
},
}
};
Show();
}
[BackgroundDependencyLoader]
private void load(OsuColour colours, TextureStore textures, AudioManager audio)
{
getSample = audio.Samples.Get(@"MedalSplash/medal-get");
innerSpin.Texture = outerSpin.Texture = textures.Get(@"MedalSplash/disc-spin");
disc.EdgeEffect = leftStrip.EdgeEffect = rightStrip.EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Colour = colours.Blue.Opacity(0.5f),
Radius = 50,
};
}
protected override void LoadComplete()
{
base.LoadComplete();
LoadComponentAsync(drawableMedal = new DrawableMedal(medal)
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.Both,
}, loaded =>
{
disc.Add(loaded);
startAnimation();
});
}
protected override void Update()
{
base.Update();
particleContainer.Add(new MedalParticle(RNG.Next(0, 359)));
}
private const double initial_duration = 400;
private const double step_duration = 900;
private void startAnimation()
{
content.Show();
background.FlashColour(Color4.White.Opacity(0.25f), 400);
getSample?.Play();
innerSpin.Spin(20000, RotationDirection.Clockwise);
outerSpin.Spin(40000, RotationDirection.Clockwise);
using (BeginDelayedSequence(200))
{
disc.FadeIn(initial_duration)
.ScaleTo(1f, initial_duration * 2, Easing.OutElastic);
particleContainer.FadeIn(initial_duration);
outerSpin.FadeTo(0.1f, initial_duration * 2);
using (BeginDelayedSequence(initial_duration + 200))
{
backgroundStrip.FadeIn(step_duration);
leftStrip.ResizeWidthTo(1f, step_duration, Easing.OutQuint);
rightStrip.ResizeWidthTo(1f, step_duration, Easing.OutQuint);
Debug.Assert(drawableMedal != null);
this.Animate().Schedule(() =>
{
if (drawableMedal.State != DisplayState.Full)
drawableMedal.State = DisplayState.Icon;
}).Delay(step_duration).Schedule(() =>
{
if (drawableMedal.State != DisplayState.Full)
drawableMedal.State = DisplayState.MedalUnlocked;
}).Delay(step_duration).Schedule(() =>
{
if (drawableMedal.State != DisplayState.Full)
drawableMedal.State = DisplayState.Full;
});
}
}
}
protected override void PopIn()
{
this.FadeIn(200);
}
protected override void PopOut()
{
this.FadeOut(200);
}
public void Dismiss()
{
if (drawableMedal != null && drawableMedal.State != DisplayState.Full)
{
// if we haven't yet, play out the animation fully
drawableMedal.State = DisplayState.Full;
FinishTransforms(true);
return;
}
Hide();
Expire();
}
private partial class BackgroundStrip : Container
{
public BackgroundStrip(float start, float end)
{
RelativeSizeAxes = Axes.Both;
Width = 0f;
Colour = ColourInfo.GradientHorizontal(Color4.White.Opacity(start), Color4.White.Opacity(end));
Masking = true;
Children = new[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.White,
}
};
}
}
private partial class MedalParticle : CircularContainer
{
private readonly float direction;
private Vector2 positionForOffset(float offset) => new Vector2((float)(offset * Math.Sin(direction)), (float)(offset * Math.Cos(direction)));
public MedalParticle(float direction)
{
this.direction = direction;
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
Position = positionForOffset(DISC_SIZE / 2);
Masking = true;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Colour = colours.Blue.Opacity(0.5f),
Radius = 5,
};
this.MoveTo(positionForOffset(DISC_SIZE / 2 + 200), 500);
this.FadeOut(500);
Expire();
}
}
}
}

View File

@ -1,324 +1,130 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
#nullable disable using System.Collections.Generic;
using System.Linq;
using osuTK; using osu.Framework.Allocation;
using osuTK.Graphics; using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Sprites;
using osu.Game.Users;
using osu.Game.Graphics;
using osu.Game.Graphics.Backgrounds;
using osu.Game.Overlays.MedalSplash;
using osu.Framework.Allocation;
using osu.Framework.Audio.Sample;
using osu.Framework.Audio;
using osu.Framework.Graphics.Textures;
using osuTK.Input;
using osu.Framework.Graphics.Shapes;
using System;
using osu.Framework.Graphics.Effects;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Framework.Utils; using osu.Game.Graphics.Containers;
using osu.Game.Input.Bindings;
using osu.Game.Online.API;
using osu.Game.Online.Notifications.WebSocket;
using osu.Game.Online.Notifications.WebSocket.Events;
using osu.Game.Users;
namespace osu.Game.Overlays namespace osu.Game.Overlays
{ {
public partial class MedalOverlay : FocusedOverlayContainer public partial class MedalOverlay : OsuFocusedOverlayContainer
{ {
public const float DISC_SIZE = 400; protected override string? PopInSampleName => null;
protected override string? PopOutSampleName => null;
private const float border_width = 5; public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks;
private readonly Medal medal; protected override void PopIn() => this.FadeIn();
private readonly Box background;
private readonly Container backgroundStrip, particleContainer;
private readonly BackgroundStrip leftStrip, rightStrip;
private readonly CircularContainer disc;
private readonly Sprite innerSpin, outerSpin;
private DrawableMedal drawableMedal;
private Sample getSample; protected override void PopOut() => this.FadeOut();
private readonly Container content; private readonly Queue<MedalAnimation> queuedMedals = new Queue<MedalAnimation>();
public MedalOverlay(Medal medal) [Resolved]
{ private IAPIProvider api { get; set; } = null!;
this.medal = medal;
RelativeSizeAxes = Axes.Both;
Child = content = new Container private Container<Drawable> medalContainer = null!;
{ private MedalAnimation? lastAnimation;
Alpha = 0,
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
background = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black.Opacity(60),
},
outerSpin = new Sprite
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(DISC_SIZE + 500),
Alpha = 0f,
},
backgroundStrip = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.X,
Height = border_width,
Alpha = 0f,
Children = new[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.CentreRight,
Width = 0.5f,
Padding = new MarginPadding { Right = DISC_SIZE / 2 },
Children = new[]
{
leftStrip = new BackgroundStrip(0f, 1f)
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
},
},
},
new Container
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.CentreLeft,
Width = 0.5f,
Padding = new MarginPadding { Left = DISC_SIZE / 2 },
Children = new[]
{
rightStrip = new BackgroundStrip(1f, 0f),
},
},
},
},
particleContainer = new Container
{
RelativeSizeAxes = Axes.Both,
Alpha = 0f,
},
disc = new CircularContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Alpha = 0f,
Masking = true,
AlwaysPresent = true,
BorderColour = Color4.White,
BorderThickness = border_width,
Size = new Vector2(DISC_SIZE),
Scale = new Vector2(0.8f),
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4Extensions.FromHex(@"05262f"),
},
new Triangles
{
RelativeSizeAxes = Axes.Both,
TriangleScale = 2,
ColourDark = Color4Extensions.FromHex(@"04222b"),
ColourLight = Color4Extensions.FromHex(@"052933"),
},
innerSpin = new Sprite
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(1.05f),
Alpha = 0.25f,
},
},
},
}
};
Show();
}
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours, TextureStore textures, AudioManager audio) private void load()
{ {
getSample = audio.Samples.Get(@"MedalSplash/medal-get"); RelativeSizeAxes = Axes.Both;
innerSpin.Texture = outerSpin.Texture = textures.Get(@"MedalSplash/disc-spin");
disc.EdgeEffect = leftStrip.EdgeEffect = rightStrip.EdgeEffect = new EdgeEffectParameters api.NotificationsClient.MessageReceived += handleMedalMessages;
Add(medalContainer = new Container
{ {
Type = EdgeEffectType.Glow, RelativeSizeAxes = Axes.Both
Colour = colours.Blue.Opacity(0.5f), });
Radius = 50,
};
} }
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();
LoadComponentAsync(drawableMedal = new DrawableMedal(medal) OverlayActivationMode.BindValueChanged(val =>
{ {
Anchor = Anchor.TopCentre, if (val.NewValue == OverlayActivation.All && (queuedMedals.Any() || medalContainer.Any() || lastAnimation?.IsLoaded == false))
Origin = Anchor.TopCentre, Show();
RelativeSizeAxes = Axes.Both, }, true);
}, loaded => }
private void handleMedalMessages(SocketMessage obj)
{
if (obj.Event != @"new")
return;
var data = obj.Data?.ToObject<NewPrivateNotificationEvent>();
if (data == null || data.Name != @"user_achievement_unlock")
return;
var details = data.Details?.ToObject<UserAchievementUnlock>();
if (details == null)
return;
var medal = new Medal
{ {
disc.Add(loaded); Name = details.Title,
startAnimation(); InternalName = details.Slug,
}); Description = details.Description,
};
var medalAnimation = new MedalAnimation(medal);
queuedMedals.Enqueue(medalAnimation);
if (OverlayActivationMode.Value == OverlayActivation.All)
Scheduler.AddOnce(Show);
} }
protected override void Update() protected override void Update()
{ {
base.Update(); base.Update();
particleContainer.Add(new MedalParticle(RNG.Next(0, 359))); if (medalContainer.Any() || lastAnimation?.IsLoaded == false)
return;
if (!queuedMedals.TryDequeue(out lastAnimation))
{
Hide();
return;
}
LoadComponentAsync(lastAnimation, medalContainer.Add);
} }
protected override bool OnClick(ClickEvent e) protected override bool OnClick(ClickEvent e)
{ {
dismiss(); lastAnimation?.Dismiss();
return true; return true;
} }
protected override void OnFocusLost(FocusLostEvent e) public override bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
{ {
if (e.CurrentState.Keyboard.Keys.IsPressed(Key.Escape)) dismiss(); if (e.Action == GlobalAction.Back)
}
private const double initial_duration = 400;
private const double step_duration = 900;
private void startAnimation()
{
content.Show();
background.FlashColour(Color4.White.Opacity(0.25f), 400);
getSample.Play();
innerSpin.Spin(20000, RotationDirection.Clockwise);
outerSpin.Spin(40000, RotationDirection.Clockwise);
using (BeginDelayedSequence(200))
{ {
disc.FadeIn(initial_duration) lastAnimation?.Dismiss();
.ScaleTo(1f, initial_duration * 2, Easing.OutElastic); return true;
particleContainer.FadeIn(initial_duration);
outerSpin.FadeTo(0.1f, initial_duration * 2);
using (BeginDelayedSequence(initial_duration + 200))
{
backgroundStrip.FadeIn(step_duration);
leftStrip.ResizeWidthTo(1f, step_duration, Easing.OutQuint);
rightStrip.ResizeWidthTo(1f, step_duration, Easing.OutQuint);
this.Animate().Schedule(() =>
{
if (drawableMedal.State != DisplayState.Full)
drawableMedal.State = DisplayState.Icon;
}).Delay(step_duration).Schedule(() =>
{
if (drawableMedal.State != DisplayState.Full)
drawableMedal.State = DisplayState.MedalUnlocked;
}).Delay(step_duration).Schedule(() =>
{
if (drawableMedal.State != DisplayState.Full)
drawableMedal.State = DisplayState.Full;
});
}
}
}
protected override void PopIn()
{
this.FadeIn(200);
}
protected override void PopOut()
{
this.FadeOut(200);
}
private void dismiss()
{
if (drawableMedal.State != DisplayState.Full)
{
// if we haven't yet, play out the animation fully
drawableMedal.State = DisplayState.Full;
FinishTransforms(true);
return;
} }
Hide(); return base.OnPressed(e);
Expire();
} }
private partial class BackgroundStrip : Container protected override void Dispose(bool isDisposing)
{ {
public BackgroundStrip(float start, float end) base.Dispose(isDisposing);
{
RelativeSizeAxes = Axes.Both;
Width = 0f;
Colour = ColourInfo.GradientHorizontal(Color4.White.Opacity(start), Color4.White.Opacity(end));
Masking = true;
Children = new[] if (api.IsNotNull())
{ api.NotificationsClient.MessageReceived -= handleMedalMessages;
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.White,
}
};
}
}
private partial class MedalParticle : CircularContainer
{
private readonly float direction;
private Vector2 positionForOffset(float offset) => new Vector2((float)(offset * Math.Sin(direction)), (float)(offset * Math.Cos(direction)));
public MedalParticle(float direction)
{
this.direction = direction;
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
Position = positionForOffset(DISC_SIZE / 2);
Masking = true;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Colour = colours.Blue.Opacity(0.5f),
Radius = 5,
};
this.MoveTo(positionForOffset(DISC_SIZE / 2 + 200), 500);
this.FadeOut(500);
Expire();
}
} }
} }
} }

View File

@ -38,7 +38,7 @@ namespace osu.Game.Overlays.MedalSplash
public DrawableMedal(Medal medal) public DrawableMedal(Medal medal)
{ {
this.medal = medal; this.medal = medal;
Position = new Vector2(0f, MedalOverlay.DISC_SIZE / 2); Position = new Vector2(0f, MedalAnimation.DISC_SIZE / 2);
FillFlowContainer infoFlow; FillFlowContainer infoFlow;
Children = new Drawable[] Children = new Drawable[]
@ -174,7 +174,7 @@ namespace osu.Game.Overlays.MedalSplash
.ScaleTo(1); .ScaleTo(1);
this.ScaleTo(scale_when_unlocked, duration, Easing.OutExpo); this.ScaleTo(scale_when_unlocked, duration, Easing.OutExpo);
this.MoveToY(MedalOverlay.DISC_SIZE / 2 - 30, duration, Easing.OutExpo); this.MoveToY(MedalAnimation.DISC_SIZE / 2 - 30, duration, Easing.OutExpo);
unlocked.FadeInFromZero(duration); unlocked.FadeInFromZero(duration);
break; break;
@ -184,7 +184,7 @@ namespace osu.Game.Overlays.MedalSplash
.ScaleTo(1); .ScaleTo(1);
this.ScaleTo(scale_when_full, duration, Easing.OutExpo); this.ScaleTo(scale_when_full, duration, Easing.OutExpo);
this.MoveToY(MedalOverlay.DISC_SIZE / 2 - 60, duration, Easing.OutExpo); this.MoveToY(MedalAnimation.DISC_SIZE / 2 - 60, duration, Easing.OutExpo);
unlocked.Show(); unlocked.Show();
name.FadeInFromZero(duration + 100); name.FadeInFromZero(duration + 100);
description.FadeInFromZero(duration * 2); description.FadeInFromZero(duration * 2);

View File

@ -40,8 +40,7 @@ namespace osu.Game.Overlays.Mods
public Bindable<IBeatmapInfo?> BeatmapInfo { get; } = new Bindable<IBeatmapInfo?>(); public Bindable<IBeatmapInfo?> BeatmapInfo { get; } = new Bindable<IBeatmapInfo?>();
[Resolved] public Bindable<IReadOnlyList<Mod>> Mods { get; } = new Bindable<IReadOnlyList<Mod>>();
private Bindable<IReadOnlyList<Mod>> mods { get; set; } = null!;
public BindableBool Collapsed { get; } = new BindableBool(true); public BindableBool Collapsed { get; } = new BindableBool(true);
@ -53,7 +52,7 @@ namespace osu.Game.Overlays.Mods
[Resolved] [Resolved]
private OsuGameBase game { get; set; } = null!; private OsuGameBase game { get; set; } = null!;
private IBindable<RulesetInfo> gameRuleset = null!; protected IBindable<RulesetInfo> GameRuleset = null!;
private CancellationTokenSource? cancellationSource; private CancellationTokenSource? cancellationSource;
private IBindable<StarDifficulty?> starDifficulty = null!; private IBindable<StarDifficulty?> starDifficulty = null!;
@ -101,15 +100,15 @@ namespace osu.Game.Overlays.Mods
{ {
base.LoadComplete(); base.LoadComplete();
mods.BindValueChanged(_ => Mods.BindValueChanged(_ =>
{ {
modSettingChangeTracker?.Dispose(); modSettingChangeTracker?.Dispose();
modSettingChangeTracker = new ModSettingChangeTracker(mods.Value); modSettingChangeTracker = new ModSettingChangeTracker(Mods.Value);
modSettingChangeTracker.SettingChanged += _ => updateValues(); modSettingChangeTracker.SettingChanged += _ => updateValues();
updateValues(); updateValues();
}, true); }, true);
BeatmapInfo.BindValueChanged(_ => updateValues(), true); BeatmapInfo.BindValueChanged(_ => updateValues());
Collapsed.BindValueChanged(_ => Collapsed.BindValueChanged(_ =>
{ {
@ -118,11 +117,12 @@ namespace osu.Game.Overlays.Mods
updateCollapsedState(); updateCollapsedState();
}); });
gameRuleset = game.Ruleset.GetBoundCopy(); GameRuleset = game.Ruleset.GetBoundCopy();
gameRuleset.BindValueChanged(_ => updateValues()); GameRuleset.BindValueChanged(_ => updateValues());
BeatmapInfo.BindValueChanged(_ => updateValues(), true); BeatmapInfo.BindValueChanged(_ => updateValues());
updateValues();
updateCollapsedState(); updateCollapsedState();
} }
@ -166,17 +166,17 @@ namespace osu.Game.Overlays.Mods
}); });
double rate = 1; double rate = 1;
foreach (var mod in mods.Value.OfType<IApplicableToRate>()) foreach (var mod in Mods.Value.OfType<IApplicableToRate>())
rate = mod.ApplyToRate(0, rate); rate = mod.ApplyToRate(0, rate);
bpmDisplay.Current.Value = FormatUtils.RoundBPM(BeatmapInfo.Value.BPM, rate); bpmDisplay.Current.Value = FormatUtils.RoundBPM(BeatmapInfo.Value.BPM, rate);
BeatmapDifficulty originalDifficulty = new BeatmapDifficulty(BeatmapInfo.Value.Difficulty); BeatmapDifficulty originalDifficulty = new BeatmapDifficulty(BeatmapInfo.Value.Difficulty);
foreach (var mod in mods.Value.OfType<IApplicableToDifficulty>()) foreach (var mod in Mods.Value.OfType<IApplicableToDifficulty>())
mod.ApplyToDifficulty(originalDifficulty); mod.ApplyToDifficulty(originalDifficulty);
Ruleset ruleset = gameRuleset.Value.CreateInstance(); Ruleset ruleset = GameRuleset.Value.CreateInstance();
BeatmapDifficulty adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(originalDifficulty, rate); BeatmapDifficulty adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(originalDifficulty, rate);
TooltipContent = new AdjustedAttributesTooltip.Data(originalDifficulty, adjustedDifficulty); TooltipContent = new AdjustedAttributesTooltip.Data(originalDifficulty, adjustedDifficulty);
@ -195,7 +195,7 @@ namespace osu.Game.Overlays.Mods
RightContent.FadeTo(Collapsed.Value && !IsHovered ? 0 : 1, transition_duration, Easing.OutQuint); RightContent.FadeTo(Collapsed.Value && !IsHovered ? 0 : 1, transition_duration, Easing.OutQuint);
} }
private partial class BPMDisplay : RollingCounter<int> public partial class BPMDisplay : RollingCounter<int>
{ {
protected override double RollingDuration => 250; protected override double RollingDuration => 250;

View File

@ -43,6 +43,14 @@ namespace osu.Game.Overlays.Mods
[Cached] [Cached]
public Bindable<IReadOnlyList<Mod>> SelectedMods { get; private set; } = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>()); public Bindable<IReadOnlyList<Mod>> SelectedMods { get; private set; } = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
/// <summary>
/// Contains a list of mods which <see cref="ModSelectOverlay"/> should read from to display effects on the selected beatmap.
/// </summary>
/// <remarks>
/// This is different from <see cref="SelectedMods"/> in screens like online-play rooms, where there are required mods activated from the playlist.
/// </remarks>
public Bindable<IReadOnlyList<Mod>> ActiveMods { get; private set; } = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
/// <summary> /// <summary>
/// Contains a dictionary with the current <see cref="ModState"/> of all mods applicable for the current ruleset. /// Contains a dictionary with the current <see cref="ModState"/> of all mods applicable for the current ruleset.
/// </summary> /// </summary>
@ -97,6 +105,8 @@ namespace osu.Game.Overlays.Mods
protected virtual IReadOnlyList<Mod> ComputeNewModsFromSelection(IReadOnlyList<Mod> oldSelection, IReadOnlyList<Mod> newSelection) => newSelection; protected virtual IReadOnlyList<Mod> ComputeNewModsFromSelection(IReadOnlyList<Mod> oldSelection, IReadOnlyList<Mod> newSelection) => newSelection;
protected virtual IReadOnlyList<Mod> ComputeActiveMods() => SelectedMods.Value;
protected virtual IEnumerable<ShearedButton> CreateFooterButtons() protected virtual IEnumerable<ShearedButton> CreateFooterButtons()
{ {
if (AllowCustomisation) if (AllowCustomisation)
@ -279,7 +289,7 @@ namespace osu.Game.Overlays.Mods
{ {
Anchor = Anchor.BottomRight, Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight, Origin = Anchor.BottomRight,
BeatmapInfo = { Value = beatmap?.BeatmapInfo } BeatmapInfo = { Value = Beatmap?.BeatmapInfo },
}, },
} }
}); });
@ -316,20 +326,26 @@ namespace osu.Game.Overlays.Mods
SelectedMods.BindValueChanged(_ => SelectedMods.BindValueChanged(_ =>
{ {
updateRankingInformation();
updateFromExternalSelection(); updateFromExternalSelection();
updateCustomisation(); updateCustomisation();
ActiveMods.Value = ComputeActiveMods();
}, true);
ActiveMods.BindValueChanged(_ =>
{
updateOverlayInformation();
modSettingChangeTracker?.Dispose(); modSettingChangeTracker?.Dispose();
if (AllowCustomisation) if (AllowCustomisation)
{ {
// Importantly, use SelectedMods.Value here (and not the ValueChanged NewValue) as the latter can // Importantly, use ActiveMods.Value here (and not the ValueChanged NewValue) as the latter can
// potentially be stale, due to complexities in the way change trackers work. // potentially be stale, due to complexities in the way change trackers work.
// //
// See https://github.com/ppy/osu/pull/23284#issuecomment-1529056988 // See https://github.com/ppy/osu/pull/23284#issuecomment-1529056988
modSettingChangeTracker = new ModSettingChangeTracker(SelectedMods.Value); modSettingChangeTracker = new ModSettingChangeTracker(ActiveMods.Value);
modSettingChangeTracker.SettingChanged += _ => updateRankingInformation(); modSettingChangeTracker.SettingChanged += _ => updateOverlayInformation();
} }
}, true); }, true);
@ -341,12 +357,12 @@ namespace osu.Game.Overlays.Mods
column.SearchTerm = query.NewValue; column.SearchTerm = query.NewValue;
}, true); }, true);
// Start scrolled slightly to the right to give the user a sense that // Start scrolling from the end, to give the user a sense that
// there is more horizontal content available. // there is more horizontal content available.
ScheduleAfterChildren(() => ScheduleAfterChildren(() =>
{ {
columnScroll.ScrollTo(200, false); columnScroll.ScrollToEnd(false);
columnScroll.ScrollToStart(); columnScroll.ScrollTo(0);
}); });
} }
@ -454,18 +470,25 @@ namespace osu.Game.Overlays.Mods
modState.ValidForSelection.Value = modState.Mod.Type != ModType.System && modState.Mod.HasImplementation && IsValidMod.Invoke(modState.Mod); modState.ValidForSelection.Value = modState.Mod.Type != ModType.System && modState.Mod.HasImplementation && IsValidMod.Invoke(modState.Mod);
} }
private void updateRankingInformation() /// <summary>
/// Updates any information displayed on the overlay regarding the effects of the active mods.
/// This reads from <see cref="ActiveMods"/> instead of <see cref="SelectedMods"/>.
/// </summary>
private void updateOverlayInformation()
{ {
if (rankingInformationDisplay == null) if (rankingInformationDisplay != null)
return; {
double multiplier = 1.0;
double multiplier = 1.0; foreach (var mod in ActiveMods.Value)
multiplier *= mod.ScoreMultiplier;
foreach (var mod in SelectedMods.Value) rankingInformationDisplay.ModMultiplier.Value = multiplier;
multiplier *= mod.ScoreMultiplier; rankingInformationDisplay.Ranked.Value = ActiveMods.Value.All(m => m.Ranked);
}
rankingInformationDisplay.ModMultiplier.Value = multiplier; if (beatmapAttributesDisplay != null)
rankingInformationDisplay.Ranked.Value = SelectedMods.Value.All(m => m.Ranked); beatmapAttributesDisplay.Mods.Value = ActiveMods.Value;
} }
private void updateCustomisation() private void updateCustomisation()

View File

@ -86,7 +86,10 @@ namespace osu.Game.Overlays.Mods
{ {
modSettingsFlow.Clear(); modSettingsFlow.Clear();
foreach (var mod in SelectedMods.Value.AsOrdered()) // Importantly, the selected mods bindable is already ordered by the mod select overlay (following the order of mod columns and panels).
// Using AsOrdered produces a slightly different order (e.g. DT and NC no longer becoming adjacent),
// which breaks user expectations when interacting with the overlay.
foreach (var mod in SelectedMods.Value)
{ {
var settings = mod.CreateSettingsControls().ToList(); var settings = mod.CreateSettingsControls().ToList();
@ -110,10 +113,14 @@ namespace osu.Game.Overlays.Mods
protected override bool OnMouseDown(MouseDownEvent e) => true; protected override bool OnMouseDown(MouseDownEvent e) => true;
protected override bool OnHover(HoverEvent e) => true; protected override bool OnHover(HoverEvent e) => true;
private partial class ModSettingsColumn : CompositeDrawable public partial class ModSettingsColumn : CompositeDrawable
{ {
public readonly Mod Mod;
public ModSettingsColumn(Mod mod, IEnumerable<Drawable> settingsControls) public ModSettingsColumn(Mod mod, IEnumerable<Drawable> settingsControls)
{ {
Mod = mod;
Width = 250; Width = 250;
RelativeSizeAxes = Axes.Y; RelativeSizeAxes = Axes.Y;
Padding = new MarginPadding { Bottom = 7 }; Padding = new MarginPadding { Bottom = 7 };

View File

@ -10,6 +10,7 @@ using osu.Framework.Localisation;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Framework.Statistics;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Localisation; using osu.Game.Localisation;
using osu.Game.Overlays.Notifications; using osu.Game.Overlays.Notifications;
@ -107,6 +108,9 @@ namespace osu.Game.Overlays.Settings.Sections.General
try try
{ {
GlobalStatistics.OutputToLog();
Logger.Flush();
var logStorage = Logger.Storage; var logStorage = Logger.Storage;
using (var outStream = storage.CreateFileSafely(archive_filename)) using (var outStream = storage.CreateFileSafely(archive_filename))

View File

@ -13,7 +13,6 @@ using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Framework.Input.Handlers.Tablet; using osu.Framework.Input.Handlers.Tablet;
using osu.Framework.Utils;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osuTK; using osuTK;
@ -196,7 +195,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
var matrix = Matrix3.Identity; var matrix = Matrix3.Identity;
MatrixExtensions.TranslateFromLeft(ref matrix, offset); MatrixExtensions.TranslateFromLeft(ref matrix, offset);
MatrixExtensions.RotateFromLeft(ref matrix, MathUtils.DegreesToRadians(rotation.Value)); MatrixExtensions.RotateFromLeft(ref matrix, float.DegreesToRadians(rotation.Value));
usableAreaQuad *= matrix; usableAreaQuad *= matrix;

View File

@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
@ -69,7 +68,7 @@ namespace osu.Game.Overlays.Settings
{ {
protected override bool AllowIme => false; protected override bool AllowIme => false;
protected override bool CanAddCharacter(char character) => character.IsAsciiDigit(); protected override bool CanAddCharacter(char character) => char.IsAsciiDigit(character);
public new void NotifyInputError() => base.NotifyInputError(); public new void NotifyInputError() => base.NotifyInputError();
} }

View File

@ -235,7 +235,7 @@ namespace osu.Game.Overlays.Volume
Bindable.BindValueChanged(volume => { this.TransformTo(nameof(DisplayVolume), volume.NewValue, 400, Easing.OutQuint); }, true); Bindable.BindValueChanged(volume => { this.TransformTo(nameof(DisplayVolume), volume.NewValue, 400, Easing.OutQuint); }, true);
bgProgress.Current.Value = 0.75f; bgProgress.Progress = 0.75f;
} }
private int? displayVolumeInt; private int? displayVolumeInt;
@ -265,8 +265,8 @@ namespace osu.Game.Overlays.Volume
text.Text = intValue.ToString(CultureInfo.CurrentCulture); text.Text = intValue.ToString(CultureInfo.CurrentCulture);
} }
volumeCircle.Current.Value = displayVolume * 0.75f; volumeCircle.Progress = displayVolume * 0.75f;
volumeCircleGlow.Current.Value = displayVolume * 0.75f; volumeCircleGlow.Progress = displayVolume * 0.75f;
if (intVolumeChanged && IsLoaded) if (intVolumeChanged && IsLoaded)
Scheduler.AddOnce(playTickSound); Scheduler.AddOnce(playTickSound);

View File

@ -3,7 +3,6 @@
#nullable disable #nullable disable
using System;
using Markdig.Syntax; using Markdig.Syntax;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
@ -22,29 +21,61 @@ using osuTK.Graphics;
namespace osu.Game.Overlays.Wiki namespace osu.Game.Overlays.Wiki
{ {
public partial class WikiPanelContainer : Container public partial class WikiPanelContainer : CompositeDrawable
{ {
private WikiPanelMarkdownContainer panelContainer; private const float padding = 3;
private readonly string text; private readonly string text;
private readonly bool isFullWidth; private readonly bool isFullWidth;
public WikiPanelContainer(string text, bool isFullWidth = false) public WikiPanelContainer(string text, bool isFullWidth = false)
{ {
this.text = text; this.text = text;
this.isFullWidth = isFullWidth; this.isFullWidth = isFullWidth;
RelativeSizeAxes = Axes.X;
Padding = new MarginPadding(3);
} }
private PanelBackground background;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider, IAPIProvider api) private void load(IAPIProvider api)
{ {
Children = new Drawable[] RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
InternalChildren = new Drawable[]
{ {
background = new PanelBackground
{
BypassAutoSizeAxes = Axes.Both
},
new Container new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding(padding),
Child = new WikiPanelMarkdownContainer(isFullWidth)
{
CurrentPath = $@"{api.WebsiteRootUrl}/wiki/",
Text = text,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y
}
}
};
}
protected override void Update()
{
base.Update();
background.Size = Parent!.DrawSize * new Vector2(Size.X, 1);
}
private partial class PanelBackground : CompositeDrawable
{
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider)
{
Padding = new MarginPadding(padding);
InternalChild = new Container
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Masking = true, Masking = true,
@ -60,22 +91,9 @@ namespace osu.Game.Overlays.Wiki
{ {
Colour = colourProvider.Background4, Colour = colourProvider.Background4,
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
}, }
}, };
panelContainer = new WikiPanelMarkdownContainer(isFullWidth) }
{
CurrentPath = $@"{api.WebsiteRootUrl}/wiki/",
Text = text,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
}
};
}
protected override void Update()
{
base.Update();
Height = Math.Max(panelContainer.Height, Parent!.DrawHeight);
} }
private partial class WikiPanelMarkdownContainer : WikiMarkdownContainer private partial class WikiPanelMarkdownContainer : WikiMarkdownContainer

View File

@ -11,3 +11,6 @@ using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("osu.Game.Tests.Dynamic")] [assembly: InternalsVisibleTo("osu.Game.Tests.Dynamic")]
[assembly: InternalsVisibleTo("osu.Game.Tests.iOS")] [assembly: InternalsVisibleTo("osu.Game.Tests.iOS")]
[assembly: InternalsVisibleTo("osu.Game.Tests.Android")] [assembly: InternalsVisibleTo("osu.Game.Tests.Android")]
// intended for Moq usage
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]

View File

@ -27,8 +27,7 @@ namespace osu.Game.Rulesets.Difficulty.Utils
public ReverseQueue(int initialCapacity) public ReverseQueue(int initialCapacity)
{ {
if (initialCapacity <= 0) ArgumentOutOfRangeException.ThrowIfNegativeOrZero(initialCapacity);
throw new ArgumentOutOfRangeException(nameof(initialCapacity));
items = new T[initialCapacity]; items = new T[initialCapacity];
capacity = initialCapacity; capacity = initialCapacity;

View File

@ -7,6 +7,7 @@ using System.Runtime.InteropServices;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Rendering; using osu.Framework.Graphics.Rendering;
using osu.Framework.Graphics.Rendering.Vertices; using osu.Framework.Graphics.Rendering.Vertices;
@ -88,7 +89,13 @@ namespace osu.Game.Rulesets.Mods
flashlight.Combo.BindTo(Combo); flashlight.Combo.BindTo(Combo);
flashlight.GetPlayfieldScale = () => drawableRuleset.Playfield.Scale; flashlight.GetPlayfieldScale = () => drawableRuleset.Playfield.Scale;
drawableRuleset.Overlays.Add(flashlight); drawableRuleset.Overlays.Add(new Container
{
RelativeSizeAxes = Axes.Both,
// workaround for 1px gaps on the edges of the playfield which would sometimes show with "gameplay" screen scaling active.
Padding = new MarginPadding(-1),
Child = flashlight,
});
} }
protected abstract Flashlight CreateFlashlight(); protected abstract Flashlight CreateFlashlight();

View File

@ -40,8 +40,7 @@ namespace osu.Game.Rulesets.Objects.Types
public static PathType BSpline(int degree) public static PathType BSpline(int degree)
{ {
if (degree <= 0) ArgumentOutOfRangeException.ThrowIfNegativeOrZero(degree);
throw new ArgumentOutOfRangeException(nameof(degree), "The degree of a B-Spline path must be greater than zero.");
return new PathType { Type = SplineType.BSpline, Degree = degree }; return new PathType { Type = SplineType.BSpline, Degree = degree };
} }

View File

@ -135,8 +135,7 @@ namespace osu.Game.Rulesets.UI
protected DrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods = null) protected DrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods = null)
: base(ruleset) : base(ruleset)
{ {
if (beatmap == null) ArgumentNullException.ThrowIfNull(beatmap);
throw new ArgumentNullException(nameof(beatmap), "Beatmap cannot be null.");
if (!(beatmap is Beatmap<TObject> tBeatmap)) if (!(beatmap is Beatmap<TObject> tBeatmap))
throw new ArgumentException($"{GetType()} expected the beatmap to contain hitobjects of type {typeof(TObject)}.", nameof(beatmap)); throw new ArgumentException($"{GetType()} expected the beatmap to contain hitobjects of type {typeof(TObject)}.", nameof(beatmap));

View File

@ -189,7 +189,7 @@ namespace osu.Game.Rulesets.UI
double timeBehind = Math.Abs(proposedTime - referenceClock.CurrentTime); double timeBehind = Math.Abs(proposedTime - referenceClock.CurrentTime);
isCatchingUp.Value = timeBehind > 200; isCatchingUp.Value = timeBehind > 200;
waitingOnFrames.Value = state == PlaybackState.NotValid; waitingOnFrames.Value = hasReplayAttached && state == PlaybackState.NotValid;
manualClock.CurrentTime = proposedTime; manualClock.CurrentTime = proposedTime;
manualClock.Rate = Math.Abs(referenceClock.Rate) * direction; manualClock.Rate = Math.Abs(referenceClock.Rate) * direction;

View File

@ -42,8 +42,8 @@ namespace osu.Game.Scoring.Legacy
{ {
OnlineID = score.OnlineID, OnlineID = score.OnlineID,
Mods = score.APIMods, Mods = score.APIMods,
Statistics = score.Statistics.Where(kvp => kvp.Value != 0).ToDictionary(kvp => kvp.Key, kvp => kvp.Value), Statistics = score.Statistics.Where(kvp => kvp.Value != 0).ToDictionary(),
MaximumStatistics = score.MaximumStatistics.Where(kvp => kvp.Value != 0).ToDictionary(kvp => kvp.Key, kvp => kvp.Value), MaximumStatistics = score.MaximumStatistics.Where(kvp => kvp.Value != 0).ToDictionary(),
ClientVersion = score.ClientVersion, ClientVersion = score.ClientVersion,
}; };
} }

View File

@ -45,9 +45,10 @@ namespace osu.Game.Scoring.Legacy
/// </description></item> /// </description></item>
/// <item><description>30000013: All local scores will use lazer definitions of ranks for consistency. Recalculates the rank of all scores.</description></item> /// <item><description>30000013: All local scores will use lazer definitions of ranks for consistency. Recalculates the rank of all scores.</description></item>
/// <item><description>30000014: Fix edge cases in conversion for osu! scores on selected beatmaps. Reconvert all scores.</description></item> /// <item><description>30000014: Fix edge cases in conversion for osu! scores on selected beatmaps. Reconvert all scores.</description></item>
/// <item><description>30000015: Fix osu! standardised score estimation algorithm violating basic invariants. Reconvert all scores.</description></item>
/// </list> /// </list>
/// </remarks> /// </remarks>
public const int LATEST_VERSION = 30000014; public const int LATEST_VERSION = 30000015;
/// <summary> /// <summary>
/// The first stable-compatible YYYYMMDD format version given to lazer usage of replays. /// The first stable-compatible YYYYMMDD format version given to lazer usage of replays.

View File

@ -198,10 +198,25 @@ namespace osu.Game.Scoring.Legacy
} }
} }
public static int? GetCountMiss(this ScoreInfo scoreInfo) => public static int? GetCountMiss(this ScoreInfo scoreInfo)
getCount(scoreInfo, HitResult.Miss); {
switch (scoreInfo.Ruleset.OnlineID)
{
case 0:
case 1:
case 3:
return getCount(scoreInfo, HitResult.Miss);
case 2:
return (getCount(scoreInfo, HitResult.Miss) ?? 0) + (getCount(scoreInfo, HitResult.LargeTickMiss) ?? 0);
}
return null;
}
public static void SetCountMiss(this ScoreInfo scoreInfo, int value) => public static void SetCountMiss(this ScoreInfo scoreInfo, int value) =>
// this does not match the implementation of `GetCountMiss()` for catch,
// but we physically cannot recover that data anymore at this point.
scoreInfo.Statistics[HitResult.Miss] = value; scoreInfo.Statistics[HitResult.Miss] = value;
private static int? getCount(ScoreInfo scoreInfo, HitResult result) private static int? getCount(ScoreInfo scoreInfo, HitResult result)

View File

@ -9,6 +9,7 @@ using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays; using osu.Game.Overlays;
using osuTK; using osuTK;
using osuTK.Graphics;
namespace osu.Game.Screens.Edit.Components.Menus namespace osu.Game.Screens.Edit.Components.Menus
{ {
@ -21,7 +22,7 @@ namespace osu.Game.Screens.Edit.Components.Menus
TabContainer.RelativeSizeAxes &= ~Axes.X; TabContainer.RelativeSizeAxes &= ~Axes.X;
TabContainer.AutoSizeAxes = Axes.X; TabContainer.AutoSizeAxes = Axes.X;
TabContainer.Padding = new MarginPadding(10); TabContainer.Spacing = Vector2.Zero;
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -42,30 +43,51 @@ namespace osu.Game.Screens.Edit.Components.Menus
private partial class TabItem : OsuTabItem private partial class TabItem : OsuTabItem
{ {
private const float transition_length = 250; private readonly Box background;
private Color4 backgroundIdleColour;
private Color4 backgroundHoverColour;
public TabItem(EditorScreenMode value) public TabItem(EditorScreenMode value)
: base(value) : base(value)
{ {
Text.Margin = new MarginPadding(); Text.Margin = new MarginPadding(10);
Text.Anchor = Anchor.CentreLeft; Text.Anchor = Anchor.CentreLeft;
Text.Origin = Anchor.CentreLeft; Text.Origin = Anchor.CentreLeft;
Text.Font = OsuFont.TorusAlternate; Text.Font = OsuFont.TorusAlternate;
Add(background = new Box
{
RelativeSizeAxes = Axes.Both,
Depth = float.MaxValue,
});
Bar.Expire(); Bar.Expire();
} }
protected override void OnActivated() [BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider)
{ {
base.OnActivated(); backgroundIdleColour = colourProvider.Background2;
Bar.ScaleTo(new Vector2(1, 5), transition_length, Easing.OutQuint); backgroundHoverColour = colourProvider.Background1;
} }
protected override void OnDeactivated() protected override void LoadComplete()
{ {
base.OnDeactivated(); base.LoadComplete();
Bar.ScaleTo(Vector2.One, transition_length, Easing.OutQuint); background.Colour = backgroundIdleColour;
}
protected override void FadeHovered()
{
base.FadeHovered();
background.FadeColour(backgroundHoverColour, TRANSITION_LENGTH, Easing.OutQuint);
}
protected override void FadeUnhovered()
{
base.FadeUnhovered();
background.FadeColour(backgroundIdleColour, TRANSITION_LENGTH, Easing.OutQuint);
} }
} }
} }

View File

@ -140,7 +140,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
Colour = this.baseColour = baseColour; Colour = this.baseColour = baseColour;
Current.Value = 1; Progress = 1;
} }
protected override void Update() protected override void Update()

View File

@ -6,6 +6,7 @@ using JetBrains.Annotations;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
@ -265,6 +266,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
return !Precision.AlmostIntersects(maskingBounds, rect); return !Precision.AlmostIntersects(maskingBounds, rect);
} }
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (skin.IsNotNull())
skin.SourceChanged -= updateColour;
}
private partial class Tick : Circle private partial class Tick : Circle
{ {
public Tick() public Tick()

View File

@ -366,7 +366,7 @@ namespace osu.Game.Screens.Edit.Timing
new CircularProgress new CircularProgress
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Current = { Value = 1f / light_count - angular_light_gap }, Progress = 1f / light_count - angular_light_gap,
Colour = colourProvider.Background2, Colour = colourProvider.Background2,
}, },
fillContent = new Container fillContent = new Container
@ -379,7 +379,7 @@ namespace osu.Game.Screens.Edit.Timing
new CircularProgress new CircularProgress
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Current = { Value = 1f / light_count - angular_light_gap }, Progress = 1f / light_count - angular_light_gap,
Blending = BlendingParameters.Additive Blending = BlendingParameters.Additive
}, },
// Please do not try and make sense of this. // Please do not try and make sense of this.
@ -388,7 +388,7 @@ namespace osu.Game.Screens.Edit.Timing
Glow = new CircularProgress Glow = new CircularProgress
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Current = { Value = 1f / light_count - 0.01f }, Progress = 1f / light_count - 0.01f,
Blending = BlendingParameters.Additive Blending = BlendingParameters.Additive
}.WithEffect(new GlowEffect }.WithEffect(new GlowEffect
{ {

View File

@ -14,7 +14,6 @@ using osu.Framework.Graphics.Rendering;
using osu.Framework.Graphics.Rendering.Vertices; using osu.Framework.Graphics.Rendering.Vertices;
using osu.Framework.Graphics.Shaders; using osu.Framework.Graphics.Shaders;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Framework.Utils;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
@ -209,13 +208,13 @@ namespace osu.Game.Screens.Menu
if (audioData[i] < amplitude_dead_zone) if (audioData[i] < amplitude_dead_zone)
continue; continue;
float rotation = MathUtils.DegreesToRadians(i / (float)bars_per_visualiser * 360 + j * 360 / visualiser_rounds); float rotation = float.DegreesToRadians(i / (float)bars_per_visualiser * 360 + j * 360 / visualiser_rounds);
float rotationCos = MathF.Cos(rotation); float rotationCos = MathF.Cos(rotation);
float rotationSin = MathF.Sin(rotation); float rotationSin = MathF.Sin(rotation);
// taking the cos and sin to the 0..1 range // taking the cos and sin to the 0..1 range
var barPosition = new Vector2(rotationCos / 2 + 0.5f, rotationSin / 2 + 0.5f) * size; var barPosition = new Vector2(rotationCos / 2 + 0.5f, rotationSin / 2 + 0.5f) * size;
var barSize = new Vector2(size * MathF.Sqrt(2 * (1 - MathF.Cos(MathUtils.DegreesToRadians(360f / bars_per_visualiser)))) / 2f, bar_length * audioData[i]); var barSize = new Vector2(size * MathF.Sqrt(2 * (1 - MathF.Cos(float.DegreesToRadians(360f / bars_per_visualiser)))) / 2f, bar_length * audioData[i]);
// The distance between the position and the sides of the bar. // The distance between the position and the sides of the bar.
var bottomOffset = new Vector2(-rotationSin * barSize.X / 2, rotationCos * barSize.X / 2); var bottomOffset = new Vector2(-rotationSin * barSize.X / 2, rotationCos * barSize.X / 2);
// The distance between the bottom side of the bar and the top side. // The distance between the bottom side of the bar and the top side.

View File

@ -0,0 +1,53 @@
// 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.Diagnostics;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Game.Online.Rooms;
using osu.Game.Overlays;
using osu.Game.Overlays.Mods;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Screens.OnlinePlay.Match
{
public partial class RoomModSelectOverlay : UserModSelectOverlay
{
[Resolved]
private IBindable<PlaylistItem> selectedItem { get; set; } = null!;
[Resolved]
private RulesetStore rulesets { get; set; } = null!;
private readonly List<Mod> roomRequiredMods = new List<Mod>();
public RoomModSelectOverlay()
: base(OverlayColourScheme.Plum)
{
}
protected override void LoadComplete()
{
base.LoadComplete();
selectedItem.BindValueChanged(v =>
{
roomRequiredMods.Clear();
if (v.NewValue is PlaylistItem item)
{
var rulesetInstance = rulesets.GetRuleset(item.RulesetID)?.CreateInstance();
Debug.Assert(rulesetInstance != null);
roomRequiredMods.AddRange(item.RequiredMods.Select(m => m.ToMod(rulesetInstance)));
}
ActiveMods.Value = ComputeActiveMods();
}, true);
}
protected override IReadOnlyList<Mod> ComputeActiveMods() => roomRequiredMods.Concat(base.ComputeActiveMods()).ToList();
}
}

View File

@ -241,7 +241,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
} }
}; };
LoadComponent(UserModsSelectOverlay = new UserModSelectOverlay(OverlayColourScheme.Plum) LoadComponent(UserModsSelectOverlay = new RoomModSelectOverlay
{ {
SelectedMods = { BindTarget = UserMods }, SelectedMods = { BindTarget = UserMods },
IsValidMod = _ => false IsValidMod = _ => false

View File

@ -122,8 +122,17 @@ namespace osu.Game.Screens.Play
StopGameplayClock(); StopGameplayClock();
} }
protected virtual void StartGameplayClock() => GameplayClock.Start(); protected virtual void StartGameplayClock()
protected virtual void StopGameplayClock() => GameplayClock.Stop(); {
Logger.Log($"{nameof(GameplayClockContainer)} started via call to {nameof(StartGameplayClock)}");
GameplayClock.Start();
}
protected virtual void StopGameplayClock()
{
Logger.Log($"{nameof(GameplayClockContainer)} stopped via call to {nameof(StopGameplayClock)}");
GameplayClock.Stop();
}
/// <summary> /// <summary>
/// Resets this <see cref="GameplayClockContainer"/> and the source to an initial state ready for gameplay. /// Resets this <see cref="GameplayClockContainer"/> and the source to an initial state ready for gameplay.

View File

@ -19,7 +19,7 @@ namespace osu.Game.Screens.Play.HUD
{ {
protected override double RollingDuration => 250; protected override double RollingDuration => 250;
[SettingSource("Wireframe opacity", "Controls the opacity of the wire frames behind the digits.")] [SettingSource("Wireframe opacity", "Controls the opacity of the wireframes behind the digits.")]
public BindableFloat WireframeOpacity { get; } = new BindableFloat(0.25f) public BindableFloat WireframeOpacity { get; } = new BindableFloat(0.25f)
{ {
Precision = 0.01f, Precision = 0.01f,

View File

@ -23,7 +23,7 @@ namespace osu.Game.Screens.Play.HUD
protected override double RollingDuration => 250; protected override double RollingDuration => 250;
[SettingSource("Wireframe opacity", "Controls the opacity of the wire frames behind the digits.")] [SettingSource("Wireframe opacity", "Controls the opacity of the wireframes behind the digits.")]
public BindableFloat WireframeOpacity { get; } = new BindableFloat(0.25f) public BindableFloat WireframeOpacity { get; } = new BindableFloat(0.25f)
{ {
Precision = 0.01f, Precision = 0.01f,

View File

@ -0,0 +1,81 @@
// 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.Bindables;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Game.Configuration;
using osu.Game.Localisation.SkinComponents;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Skinning;
namespace osu.Game.Screens.Play.HUD
{
public partial class ArgonPerformancePointsCounter : PerformancePointsCounter, ISerialisableDrawable
{
private ArgonCounterTextComponent text = null!;
protected override double RollingDuration => 250;
private const float alpha_when_invalid = 0.3f;
[SettingSource("Wireframe opacity", "Controls the opacity of the wireframes behind the digits.")]
public BindableFloat WireframeOpacity { get; } = new BindableFloat(0.25f)
{
Precision = 0.01f,
MinValue = 0,
MaxValue = 1,
};
[SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.ShowLabel), nameof(SkinnableComponentStrings.ShowLabelDescription))]
public Bindable<bool> ShowLabel { get; } = new BindableBool(true);
public override bool IsValid
{
get => base.IsValid;
set
{
if (value == IsValid)
return;
base.IsValid = value;
text.FadeTo(value ? 1 : alpha_when_invalid, 1000, Easing.OutQuint);
}
}
public override int DisplayedCount
{
get => base.DisplayedCount;
set
{
base.DisplayedCount = value;
updateWireframe();
}
}
private void updateWireframe()
{
int digitsRequiredForDisplayCount = Math.Max(3, getDigitsRequiredForDisplayCount());
if (digitsRequiredForDisplayCount != text.WireframeTemplate.Length)
text.WireframeTemplate = new string('#', digitsRequiredForDisplayCount);
}
private int getDigitsRequiredForDisplayCount()
{
int digitsRequired = 1;
long c = DisplayedCount;
while ((c /= 10) > 0)
digitsRequired++;
return digitsRequired;
}
protected override IHasText CreateText() => text = new ArgonCounterTextComponent(Anchor.TopRight, BeatmapsetsStrings.ShowScoreboardHeaderspp.ToUpper())
{
WireframeOpacity = { BindTarget = WireframeOpacity },
ShowLabel = { BindTarget = ShowLabel },
};
}
}

View File

@ -20,7 +20,7 @@ namespace osu.Game.Screens.Play.HUD
protected override double RollingDuration => 250; protected override double RollingDuration => 250;
[SettingSource("Wireframe opacity", "Controls the opacity of the wire frames behind the digits.")] [SettingSource("Wireframe opacity", "Controls the opacity of the wireframes behind the digits.")]
public BindableFloat WireframeOpacity { get; } = new BindableFloat(0.25f) public BindableFloat WireframeOpacity { get; } = new BindableFloat(0.25f)
{ {
Precision = 0.01f, Precision = 0.01f,

View File

@ -198,9 +198,14 @@ namespace osu.Game.Screens.Play.HUD
bind(); bind();
} }
protected override void Update()
{
base.Update();
circularProgress.Progress = Progress.Value;
}
private void bind() private void bind()
{ {
((IBindable<double>)circularProgress.Current).BindTo(Progress);
Progress.ValueChanged += progress => Progress.ValueChanged += progress =>
{ {
icon.Scale = new Vector2(1 + (float)progress.NewValue * 0.2f); icon.Scale = new Vector2(1 + (float)progress.NewValue * 0.2f);

View File

@ -13,16 +13,9 @@ using JetBrains.Annotations;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio.Track; using osu.Framework.Audio.Track;
using osu.Framework.Extensions; using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Framework.Localisation;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
@ -31,20 +24,13 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring; using osu.Game.Scoring;
using osu.Game.Skinning; using osu.Game.Skinning;
using osuTK;
namespace osu.Game.Screens.Play.HUD namespace osu.Game.Screens.Play.HUD
{ {
public partial class PerformancePointsCounter : RollingCounter<int>, ISerialisableDrawable public abstract partial class PerformancePointsCounter : RollingCounter<int>
{ {
public bool UsesFixedAnchor { get; set; } public bool UsesFixedAnchor { get; set; }
protected override bool IsRollingProportional => true;
protected override double RollingDuration => 500;
private const float alpha_when_invalid = 0.3f;
[Resolved] [Resolved]
private ScoreProcessor scoreProcessor { get; set; } private ScoreProcessor scoreProcessor { get; set; }
@ -60,18 +46,11 @@ namespace osu.Game.Screens.Play.HUD
private PerformanceCalculator performanceCalculator; private PerformanceCalculator performanceCalculator;
private ScoreInfo scoreInfo; private ScoreInfo scoreInfo;
public PerformancePointsCounter()
{
Current.Value = DisplayedCount = 0;
}
private Mod[] clonedMods; private Mod[] clonedMods;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours, BeatmapDifficultyCache difficultyCache) private void load(BeatmapDifficultyCache difficultyCache)
{ {
Colour = colours.BlueLighter;
if (gameplayState != null) if (gameplayState != null)
{ {
performanceCalculator = gameplayState.Ruleset.CreatePerformanceCalculator(); performanceCalculator = gameplayState.Ruleset.CreatePerformanceCalculator();
@ -107,19 +86,7 @@ namespace osu.Game.Screens.Play.HUD
onJudgementChanged(gameplayState.LastJudgementResult.Value); onJudgementChanged(gameplayState.LastJudgementResult.Value);
} }
private bool isValid; public virtual bool IsValid { get; set; }
protected bool IsValid
{
set
{
if (value == isValid)
return;
isValid = value;
DrawableCount.FadeTo(isValid ? 1 : alpha_when_invalid, 1000, Easing.OutQuint);
}
}
private void onJudgementChanged(JudgementResult judgement) private void onJudgementChanged(JudgementResult judgement)
{ {
@ -151,13 +118,6 @@ namespace osu.Game.Screens.Play.HUD
return timedAttributes[Math.Clamp(attribIndex, 0, timedAttributes.Count - 1)].Attributes; return timedAttributes[Math.Clamp(attribIndex, 0, timedAttributes.Count - 1)].Attributes;
} }
protected override LocalisableString FormatCount(int count) => count.ToString(@"D");
protected override IHasText CreateText() => new TextComponent
{
Alpha = alpha_when_invalid
};
protected override void Dispose(bool isDisposing) protected override void Dispose(bool isDisposing)
{ {
base.Dispose(isDisposing); base.Dispose(isDisposing);
@ -171,45 +131,6 @@ namespace osu.Game.Screens.Play.HUD
loadCancellationSource?.Cancel(); loadCancellationSource?.Cancel();
} }
private partial class TextComponent : CompositeDrawable, IHasText
{
public LocalisableString Text
{
get => text.Text;
set => text.Text = value;
}
private readonly OsuSpriteText text;
public TextComponent()
{
AutoSizeAxes = Axes.Both;
InternalChild = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Spacing = new Vector2(2),
Children = new Drawable[]
{
text = new OsuSpriteText
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Font = OsuFont.Numeric.With(size: 16, fixedWidth: true)
},
new OsuSpriteText
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Text = BeatmapsetsStrings.ShowScoreboardHeaderspp,
Font = OsuFont.Numeric.With(size: 8),
Padding = new MarginPadding { Bottom = 1.5f }, // align baseline better
}
}
};
}
}
// TODO: This class shouldn't exist, but requires breaking changes to allow DifficultyCalculator to receive an IBeatmap. // TODO: This class shouldn't exist, but requires breaking changes to allow DifficultyCalculator to receive an IBeatmap.
private class GameplayWorkingBeatmap : WorkingBeatmap private class GameplayWorkingBeatmap : WorkingBeatmap
{ {

View File

@ -31,6 +31,11 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
/// </summary> /// </summary>
public partial class AccuracyCircle : CompositeDrawable public partial class AccuracyCircle : CompositeDrawable
{ {
/// <summary>
/// The total duration of the animation.
/// </summary>
public const double TOTAL_DURATION = APPEAR_DURATION + ACCURACY_TRANSFORM_DELAY + ACCURACY_TRANSFORM_DURATION;
/// <summary> /// <summary>
/// Duration for the transforms causing this component to appear. /// Duration for the transforms causing this component to appear.
/// </summary> /// </summary>
@ -147,7 +152,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
Colour = OsuColour.Gray(47), Colour = OsuColour.Gray(47),
Alpha = 0.5f, Alpha = 0.5f,
InnerRadius = accuracy_circle_radius + 0.01f, // Extends a little bit into the circle InnerRadius = accuracy_circle_radius + 0.01f, // Extends a little bit into the circle
Current = { Value = 1 }, Progress = 1,
}, },
accuracyCircle = new CircularProgress accuracyCircle = new CircularProgress
{ {
@ -189,11 +194,11 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
rankText = new RankText(score.Rank) rankText = new RankText(score.Rank)
}; };
if (isFailedSDueToMisses)
AddInternal(failedSRankText = new RankText(ScoreRank.S));
if (withFlair) if (withFlair)
{ {
if (isFailedSDueToMisses)
AddInternal(failedSRankText = new RankText(ScoreRank.S));
var applauseSamples = new List<string> { applauseSampleName }; var applauseSamples = new List<string> { applauseSampleName };
if (score.Rank >= ScoreRank.B) if (score.Rank >= ScoreRank.B)
// when rank is B or higher, play legacy applause sample on legacy skins. // when rank is B or higher, play legacy applause sample on legacy skins.
@ -268,7 +273,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
if (targetAccuracy < 1 && targetAccuracy >= visual_alignment_offset) if (targetAccuracy < 1 && targetAccuracy >= visual_alignment_offset)
targetAccuracy -= visual_alignment_offset; targetAccuracy -= visual_alignment_offset;
accuracyCircle.FillTo(targetAccuracy, ACCURACY_TRANSFORM_DURATION, ACCURACY_TRANSFORM_EASING); accuracyCircle.ProgressTo(targetAccuracy, ACCURACY_TRANSFORM_DURATION, ACCURACY_TRANSFORM_EASING);
if (withFlair) if (withFlair)
{ {
@ -321,24 +326,25 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
{ {
rankText.Appear(); rankText.Appear();
if (!withFlair) return; if (withFlair)
Schedule(() =>
{
isTicking = false;
rankImpactSound.Play();
});
const double applause_pre_delay = 545f;
const double applause_volume = 0.8f;
using (BeginDelayedSequence(applause_pre_delay))
{ {
Schedule(() => Schedule(() =>
{ {
rankApplauseSound.VolumeTo(applause_volume); isTicking = false;
rankApplauseSound.Play(); rankImpactSound.Play();
}); });
const double applause_pre_delay = 545f;
const double applause_volume = 0.8f;
using (BeginDelayedSequence(applause_pre_delay))
{
Schedule(() =>
{
rankApplauseSound.VolumeTo(applause_volume);
rankApplauseSound.Play();
});
}
} }
} }
@ -359,7 +365,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
.FadeOut(800, Easing.Out); .FadeOut(800, Easing.Out);
accuracyCircle accuracyCircle
.FillTo(accuracyS - GRADE_SPACING_PERCENTAGE / 2 - visual_alignment_offset, 70, Easing.OutQuint); .ProgressTo(accuracyS - GRADE_SPACING_PERCENTAGE / 2 - visual_alignment_offset, 70, Easing.OutQuint);
badges.Single(b => b.Rank == getRank(ScoreRank.S)) badges.Single(b => b.Rank == getRank(ScoreRank.S))
.FadeOut(70, Easing.OutQuint); .FadeOut(70, Easing.OutQuint);

View File

@ -67,7 +67,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
{ {
public double RevealProgress public double RevealProgress
{ {
set => Current.Value = Math.Clamp(value, startProgress, endProgress) - startProgress; set => Progress = Math.Clamp(value, startProgress, endProgress) - startProgress;
} }
private readonly double startProgress; private readonly double startProgress;

View File

@ -25,8 +25,10 @@ using osu.Game.Input.Bindings;
using osu.Game.Localisation; using osu.Game.Localisation;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Online.Placeholders; using osu.Game.Online.Placeholders;
using osu.Game.Overlays;
using osu.Game.Scoring; using osu.Game.Scoring;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osu.Game.Screens.Ranking.Expanded.Accuracy;
using osu.Game.Screens.Ranking.Statistics; using osu.Game.Screens.Ranking.Statistics;
using osuTK; using osuTK;
@ -41,6 +43,8 @@ namespace osu.Game.Screens.Ranking
public override bool? AllowGlobalTrackControl => true; public override bool? AllowGlobalTrackControl => true;
protected override OverlayActivation InitialOverlayActivationMode => OverlayActivation.UserTriggered;
public readonly Bindable<ScoreInfo> SelectedScore = new Bindable<ScoreInfo>(); public readonly Bindable<ScoreInfo> SelectedScore = new Bindable<ScoreInfo>();
[CanBeNull] [CanBeNull]
@ -172,6 +176,10 @@ namespace osu.Game.Screens.Ranking
bool shouldFlair = player != null && !Score.User.IsBot; bool shouldFlair = player != null && !Score.User.IsBot;
ScorePanelList.AddScore(Score, shouldFlair); ScorePanelList.AddScore(Score, shouldFlair);
// this is mostly for medal display.
// we don't want the medal animation to trample on the results screen animation, so we (ab)use `OverlayActivationMode`
// to give the results screen enough time to play the animation out before the medals can be shown.
Scheduler.AddDelayed(() => OverlayActivationMode.Value = OverlayActivation.All, shouldFlair ? AccuracyCircle.TOTAL_DURATION + 1000 : 0);
} }
if (AllowWatchingReplay) if (AllowWatchingReplay)

View File

@ -33,8 +33,7 @@ namespace osu.Game.Screens.Ranking.Statistics
/// <param name="items">The <see cref="SimpleStatisticItem"/>s to display in this row.</param> /// <param name="items">The <see cref="SimpleStatisticItem"/>s to display in this row.</param>
public SimpleStatisticTable(int columnCount, [ItemNotNull] IEnumerable<SimpleStatisticItem> items) public SimpleStatisticTable(int columnCount, [ItemNotNull] IEnumerable<SimpleStatisticItem> items)
{ {
if (columnCount < 1) ArgumentOutOfRangeException.ThrowIfNegativeOrZero(columnCount);
throw new ArgumentOutOfRangeException(nameof(columnCount));
this.columnCount = columnCount; this.columnCount = columnCount;
this.items = items.ToArray(); this.items = items.ToArray();

Some files were not shown because too many files have changed in this diff Show More