mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 09:27:29 +08:00
Merge branch 'master' into slider-late-hit-lenience
This commit is contained in:
commit
c0e96927aa
@ -52,6 +52,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
[TestCase("3644427", new[] { typeof(CatchModEasy), typeof(CatchModFlashlight) })]
|
||||
[TestCase("3689906", new[] { typeof(CatchModDoubleTime), typeof(CatchModEasy) })]
|
||||
[TestCase("3949367", new[] { typeof(CatchModDoubleTime), typeof(CatchModEasy) })]
|
||||
[TestCase("112643")]
|
||||
public new void Test(string name, params Type[] mods) => base.Test(name, mods);
|
||||
|
||||
protected override IEnumerable<ConvertValue> CreateConvertValue(HitObject hitObject)
|
||||
|
@ -0,0 +1,52 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class CatchRateAdjustedDisplayDifficultyTest
|
||||
{
|
||||
private static IEnumerable<float> difficultyValuesToTest()
|
||||
{
|
||||
for (float i = 0; i <= 10; i += 0.5f)
|
||||
yield return i;
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(difficultyValuesToTest))]
|
||||
public void TestApproachRateIsUnchangedWithRateEqualToOne(float originalApproachRate)
|
||||
{
|
||||
var ruleset = new CatchRuleset();
|
||||
var difficulty = new BeatmapDifficulty { ApproachRate = originalApproachRate };
|
||||
|
||||
var adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(difficulty, 1);
|
||||
|
||||
Assert.That(adjustedDifficulty.ApproachRate, Is.EqualTo(originalApproachRate));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRateBelowOne()
|
||||
{
|
||||
var ruleset = new CatchRuleset();
|
||||
var difficulty = new BeatmapDifficulty();
|
||||
|
||||
var adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(difficulty, 0.75);
|
||||
|
||||
Assert.That(adjustedDifficulty.ApproachRate, Is.EqualTo(1.67).Within(0.01));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRateAboveOne()
|
||||
{
|
||||
var ruleset = new CatchRuleset();
|
||||
var difficulty = new BeatmapDifficulty();
|
||||
|
||||
var adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(difficulty, 1.5);
|
||||
|
||||
Assert.That(adjustedDifficulty.ApproachRate, Is.EqualTo(7.67).Within(0.01));
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,582 @@
|
||||
osu file format v9
|
||||
|
||||
[General]
|
||||
StackLeniency: 0.7
|
||||
Mode: 0
|
||||
|
||||
[Difficulty]
|
||||
HPDrainRate:7
|
||||
CircleSize:5
|
||||
OverallDifficulty:8
|
||||
ApproachRate:8
|
||||
SliderMultiplier:3.2
|
||||
SliderTickRate:2
|
||||
|
||||
[Events]
|
||||
//Background and Video events
|
||||
//Break Periods
|
||||
2,16325,17625
|
||||
2,32325,33875
|
||||
2,66325,67375
|
||||
2,120135,127375
|
||||
//Storyboard Layer 0 (Background)
|
||||
//Storyboard Layer 1 (Fail)
|
||||
//Storyboard Layer 2 (Pass)
|
||||
//Storyboard Layer 3 (Foreground)
|
||||
//Storyboard Sound Samples
|
||||
//Background Colour Transformations
|
||||
3,100,163,162,255
|
||||
|
||||
[TimingPoints]
|
||||
125,500,4,1,0,50,1,0
|
||||
36125,-100,4,1,0,50,0,1
|
||||
66125,-100,4,1,0,50,0,0
|
||||
88125,-100,4,1,0,50,0,1
|
||||
120125,-100,4,1,0,50,0,0
|
||||
170125,-100,4,2,0,5,0,0
|
||||
170250,-100,4,1,0,50,0,0
|
||||
172125,-100,4,1,0,50,0,1
|
||||
200125,-100,4,1,0,50,0,0
|
||||
|
||||
[HitObjects]
|
||||
64,80,2375,5,0
|
||||
172,192,2625,1,2
|
||||
152,36,2875,1,0
|
||||
80,176,3125,1,2
|
||||
224,112,3375,1,0
|
||||
192,256,3625,1,8
|
||||
136,116,3875,1,0
|
||||
272,32,4125,2,2,B|376:0|408:56|412:125|320:144|304:176|328:216|368:272|496:208,1,400,6|0
|
||||
504,216,4875,2,2,B|376:232|288:280|248:384,1,320
|
||||
384,344,5625,1,8
|
||||
272,216,5875,1,0
|
||||
272,216,6000,1,0
|
||||
272,216,6125,1,4
|
||||
92,280,6375,5,0
|
||||
124,108,6625,1,8
|
||||
256,8,6875,1,0
|
||||
388,108,7125,1,2
|
||||
420,280,7375,1,8
|
||||
256,296,7625,1,8
|
||||
256,120,7875,1,0
|
||||
443,152,8125,2,2,B|397:202|305:219|256:192|203:163|114:181|68:231,1,400,2|0
|
||||
24,256,8875,2,2,B|112:227|141:134|122:36|37:1,1,320
|
||||
16,132,9625,1,8
|
||||
136,280,9875,1,0
|
||||
136,280,10000,1,0
|
||||
136,280,10125,1,4
|
||||
256,172,10375,5,0
|
||||
368,56,10625,1,8
|
||||
196,116,10875,1,0
|
||||
316,116,11125,1,2
|
||||
144,56,11375,1,0
|
||||
256,0,11625,1,8
|
||||
112,128,11875,1,0
|
||||
164,280,12125,6,0,B|256:316,1,80,4|2
|
||||
100,348,12500,2,0,B|8:312,1,80,0|2
|
||||
144,212,12875,2,0,B|52:176,1,80,0|2
|
||||
208,144,13250,2,0,B|300:180,1,80,0|2
|
||||
332,324,13625,1,8
|
||||
180,324,13875,1,0
|
||||
256,240,14125,5,4
|
||||
256,240,14250,1,2
|
||||
324,112,14500,1,0
|
||||
324,112,14625,1,2
|
||||
192,56,14875,1,4
|
||||
192,56,15000,1,2
|
||||
256,164,15250,1,0
|
||||
256,164,15375,1,2
|
||||
256,20,15625,1,8
|
||||
120,56,15875,1,0
|
||||
256,92,16125,1,6
|
||||
20,152,18375,5,0
|
||||
180,136,18625,1,8
|
||||
52,228,18875,1,0
|
||||
120,84,19125,1,2
|
||||
128,244,19375,1,0
|
||||
48,84,19625,1,8
|
||||
192,212,19875,1,0
|
||||
300,72,20125,2,4,B|396:36|444:84|396:144|352:184|372:224|416:260|532:224|528:164,1,320,4|0
|
||||
472,40,20875,2,2,B|376:72|304:164|272:260|280:320,1,320
|
||||
404,352,21625,1,8
|
||||
432,196,21875,1,0
|
||||
432,196,22000,1,0
|
||||
432,196,22125,1,4
|
||||
296,100,22375,5,0
|
||||
168,196,22625,2,0,B|32:296,1,160,8|0
|
||||
268,212,23125,2,0,B|168:76,1,160,2|8
|
||||
252,312,23625,2,0,B|388:212,1,160,8|0
|
||||
484,96,24125,2,2,B|412:0|320:36|288:120|240:136|200:132|156:116|132:96|80:44,1,400,2|0
|
||||
72,24,24875,2,2,B|158:66|148:177|67:253|-19:210,1,320
|
||||
56,108,25625,1,8
|
||||
176,200,25875,1,0
|
||||
176,200,26000,1,0
|
||||
176,200,26125,1,4
|
||||
316,92,26375,5,0
|
||||
464,164,26625,2,0,B|394:224|412:336,1,160,2|0
|
||||
232,316,27125,2,0,B|306:256|284:144,1,160,2|8
|
||||
136,88,27625,1,8
|
||||
60,224,27875,1,0
|
||||
212,132,28125,6,0,B|256:32,1,80,4|2
|
||||
340,228,28500,2,0,B|384:128,1,80,0|2
|
||||
256,284,28875,2,0,B|212:184,1,80,4|2
|
||||
128,380,29250,2,0,B|84:280,1,80,0|2
|
||||
238,383,29625,2,0,B|406:379,1,160,8|0
|
||||
512,267,30125,5,4
|
||||
512,267,30250,1,2
|
||||
416,152,30500,1,0
|
||||
416,152,30625,1,2
|
||||
300,264,30875,1,4
|
||||
300,264,31000,1,2
|
||||
236,100,31250,1,0
|
||||
236,100,31375,1,2
|
||||
152,256,31625,1,8
|
||||
300,160,31875,1,0
|
||||
256,332,32125,1,6
|
||||
52,52,34625,5,0
|
||||
152,164,34875,1,0
|
||||
256,56,35125,1,4
|
||||
256,56,35625,1,2
|
||||
256,56,36125,2,4,B|331:63|364:136|320:224,1,160,4|0
|
||||
320,312,36625,1,8
|
||||
204,228,36875,1,0
|
||||
104,328,37125,2,2,B|24:287|44:188,1,160
|
||||
92,60,37625,1,8
|
||||
212,148,37875,1,0
|
||||
268,104,38000,1,0
|
||||
324,60,38125,2,0,B|452:184,1,160,4|0
|
||||
504,300,38625,1,8
|
||||
364,340,38875,1,0
|
||||
232,280,39125,6,2,B|150:282|69:198|105:87|179:53,2,320,2|2|6
|
||||
280,148,40375,1,0
|
||||
400,228,40625,2,0,B|520:368,1,160,8|0
|
||||
480,192,41125,1,2
|
||||
324,220,41375,1,2
|
||||
168,256,41625,1,8
|
||||
72,148,41875,1,2
|
||||
48,84,42000,1,2
|
||||
96,36,42125,2,0,B|164:108|256:44,1,160,6|0
|
||||
400,72,42625,1,2
|
||||
440,236,42875,1,2
|
||||
464,300,43000,1,2
|
||||
416,348,43125,2,0,B|348:276|256:340,1,160,6|0
|
||||
112,312,43625,1,2
|
||||
140,188,43875,1,0
|
||||
52,64,44125,5,6
|
||||
208,48,44375,1,0
|
||||
344,132,44625,1,8
|
||||
448,256,44875,2,2,B|401:321|285:337|217:242|233:163,2,320,2|2|0
|
||||
326,211,46125,2,2,B|279:146|163:130|95:225|111:304,1,320,6|0
|
||||
230,287,46875,2,2,B|277:352|393:368|461:273|445:194,1,320,6|8
|
||||
376,80,47625,1,8
|
||||
376,80,48125,6,0,B|304:128|216:96,1,160,4|0
|
||||
84,56,48625,1,8
|
||||
152,200,48875,1,0
|
||||
44,320,49125,2,0,B|121:364|204:320,1,160,4|0
|
||||
336,240,49625,5,8
|
||||
256,148,49875,1,0
|
||||
176,240,50125,1,0
|
||||
340,144,50625,1,0
|
||||
420,236,50875,1,0
|
||||
500,144,51125,1,2
|
||||
172,144,51625,1,2
|
||||
92,236,51875,1,0
|
||||
12,144,52125,6,0,B|160:48,1,160,4|0
|
||||
304,76,52625,1,8
|
||||
256,228,52875,1,0
|
||||
216,112,53125,2,0,B|364:208,1,160,2|0
|
||||
508,180,53625,1,8
|
||||
460,28,53875,1,0
|
||||
344,96,54125,1,2
|
||||
228,8,54375,1,0
|
||||
153,116,54625,1,2
|
||||
72,220,54875,1,0
|
||||
180,295,55125,1,2
|
||||
284,376,55375,1,0
|
||||
359,268,55625,1,2
|
||||
440,164,55875,1,0
|
||||
352,160,56125,6,0,B|466:294,1,160,4|0
|
||||
312,228,56625,1,8
|
||||
200,300,56875,1,0
|
||||
160,160,57125,2,0,B|46:294,1,160,4|0
|
||||
200,228,57625,1,8
|
||||
312,300,57875,1,0
|
||||
444,208,58125,2,0,B|362:164|380:56,1,160,2|0
|
||||
344,12,58500,1,0
|
||||
272,4,58625,2,0,B|232:88|120:68,1,160,2|0
|
||||
68,176,59125,2,0,B|148:220|132:328,1,160,2|0
|
||||
168,372,59500,1,0
|
||||
240,380,59625,2,0,B|280:296|392:316,1,160,2|0
|
||||
456,176,60125,5,6
|
||||
328,80,60375,1,0
|
||||
216,196,60625,1,8
|
||||
72,136,60875,2,2,B|54:209|91:305|191:336|269:306,2,320,2|2|0
|
||||
200,224,62125,2,2,B|182:150|219:54|319:23|397:53,1,320,2|0
|
||||
480,179,62875,2,2,B|499:252|462:348|362:379|284:349,1,320,2|0
|
||||
136,296,63625,2,0,B|67:220|140:136,1,160,8|0
|
||||
256,56,64125,5,6
|
||||
284,212,64375,1,0
|
||||
440,180,64625,1,8
|
||||
420,24,64875,1,0
|
||||
300,132,65125,1,6
|
||||
272,288,65375,1,0
|
||||
116,256,65625,1,8
|
||||
136,100,65875,1,0
|
||||
256,8,66125,1,4
|
||||
256,56,68125,6,0,B|298:128|244:237|123:241|74:173,1,320
|
||||
132,80,68875,2,2,B|344:328,1,320
|
||||
456,224,69625,1,8
|
||||
340,116,69875,1,0
|
||||
340,116,70000,1,0
|
||||
340,116,70125,1,4
|
||||
228,4,70375,5,0
|
||||
256,160,70625,2,0,B|186:224|88:168,1,160,2|0
|
||||
148,332,71125,2,0,B|216:396|316:340,1,160,2|8
|
||||
424,248,71625,1,8
|
||||
336,112,71875,1,0
|
||||
336,112,72000,1,0
|
||||
336,112,72125,1,4
|
||||
228,208,72375,2,0,B|139:179|144:80,1,160,0|8
|
||||
268,56,72875,2,2,B|272:164|220:272|120:308|72:308,1,320
|
||||
24,192,73625,1,8
|
||||
92,64,73875,1,0
|
||||
92,64,74000,1,0
|
||||
92,64,74125,1,4
|
||||
224,140,74375,5,0
|
||||
340,224,74625,2,0,B|412:211|428:121|363:77,1,160,2|0
|
||||
268,192,75125,2,0,B|196:205|180:295|245:339,1,160,2|0
|
||||
268,192,75625,2,0,B|104:168,1,160,8|0
|
||||
24,52,76125,6,0,B|132:40,1,80
|
||||
176,32,76375,1,2
|
||||
348,60,76625,1,2
|
||||
248,164,76875,1,2
|
||||
264,20,77125,1,2
|
||||
324,140,77375,1,2
|
||||
180,116,77625,1,2
|
||||
240,240,77875,1,0
|
||||
256,92,78125,1,4
|
||||
100,124,78375,5,0
|
||||
8,256,78625,2,0,B|64:332|176:304,1,160,8|0
|
||||
304,260,79125,2,0,B|248:184|136:212,1,160,2|0
|
||||
304,260,79625,1,8
|
||||
460,284,79875,1,2
|
||||
420,128,80125,6,0,B|332:128,1,80,4|0
|
||||
256,124,80375,1,2
|
||||
344,260,80625,1,2
|
||||
168,260,80875,1,2
|
||||
384,192,81125,1,2
|
||||
256,260,81375,1,2
|
||||
168,124,81625,1,2
|
||||
344,124,81875,1,2
|
||||
128,192,82125,1,4
|
||||
48,192,82250,6,0,B|48:84|152:52,1,160,2|0
|
||||
204,44,82625,2,0,B|204:152|308:184,1,160,2|0
|
||||
352,160,83000,2,0,B|244:160|212:264,1,160,2|0
|
||||
192,316,83375,2,0,B|84:316|52:212,1,160,2|2
|
||||
32,88,83875,1,2
|
||||
172,8,84125,1,4
|
||||
256,192,84250,12,6,86125
|
||||
256,192,86250,12,4,87125
|
||||
256,100,88125,6,2,B|308:116|368:104|404:16,1,160,6|0
|
||||
256,100,88625,1,8
|
||||
136,180,88875,1,0
|
||||
8,96,89125,2,0,B|-28:168|16:232|68:256,1,160,2|0
|
||||
164,312,89625,1,8
|
||||
288,236,89875,1,2
|
||||
288,236,90000,1,2
|
||||
288,236,90125,2,2,B|452:164,1,160,6|0
|
||||
476,32,90625,1,8
|
||||
332,104,90875,1,0
|
||||
180,104,91125,5,6
|
||||
36,32,91375,1,8
|
||||
56,164,91625,1,8
|
||||
56,164,92125,2,0,B|260:208,1,160,6|0
|
||||
84,296,92625,1,8
|
||||
220,376,92875,1,0
|
||||
320,268,93125,2,0,B|524:224,1,160,6|0
|
||||
432,80,93625,1,8
|
||||
296,152,93875,1,2
|
||||
296,152,94000,1,2
|
||||
296,152,94125,2,2,B|232:164|176:132|164:52,1,160,6|0
|
||||
216,232,94625,2,2,B|280:220|336:252|348:332,1,160,2|0
|
||||
341,304,95000,1,0
|
||||
341,304,95125,2,0,B|369:84,1,160,2|0
|
||||
171,80,95625,2,0,B|143:300,1,160,2|0
|
||||
43,358,96125,5,6
|
||||
81,219,96375,1,0
|
||||
169,332,96625,1,8
|
||||
304,272,96875,2,2,B|388:252|426:161|418:63|344:19,2,320,2|2|0
|
||||
240,144,98125,2,2,B|219:244|50:229|65:60|168:58,1,320
|
||||
240,144,98875,2,2,B|260:43|429:58|414:227|311:229,1,320,2|0
|
||||
180,292,99625,2,0,B|80:304|36:208,1,160,2|0
|
||||
48,64,100125,6,0,B|224:112,1,160,4|0
|
||||
348,52,100625,2,0,B|524:4,1,160,2|0
|
||||
504,172,101125,2,0,B|328:124,1,160,2|0
|
||||
204,184,101625,2,0,B|28:232,1,160,2|0
|
||||
49,226,102000,1,0
|
||||
49,226,102125,1,2
|
||||
256,324,102625,5,8
|
||||
384,256,102875,1,0
|
||||
256,188,103125,1,6
|
||||
256,188,103625,1,2
|
||||
128,256,103875,1,0
|
||||
256,324,104125,6,0,B|324:252|432:316,1,160,6|0
|
||||
492,168,104625,1,8
|
||||
332,188,104875,1,0
|
||||
256,60,105125,2,0,B|188:132|80:68,1,160,6|0
|
||||
20,216,105625,1,8
|
||||
180,196,105875,1,0
|
||||
368,156,106125,2,0,B|418:184|462:234|408:296,1,160,2|0
|
||||
220,80,106625,2,0,B|248:30|298:-14|360:40,1,160,2|0
|
||||
144,228,107125,2,0,B|94:200|50:150|104:88,1,160,2|0
|
||||
292,304,107625,2,0,B|264:354|214:398|152:344,1,160,2|0
|
||||
44,216,108125,6,0,B|145:221|172:132,1,160,6|0
|
||||
304,224,108625,1,8
|
||||
408,104,108875,1,0
|
||||
468,216,109125,2,0,B|367:221|340:132,1,160,6|0
|
||||
208,224,109625,1,8
|
||||
104,104,109875,1,0
|
||||
256,56,110125,2,0,B|144:180,1,160,2|0
|
||||
256,328,110625,2,0,B|368:204,1,160,2|0
|
||||
208,244,111125,2,0,B|96:368,1,160,2|0
|
||||
304,140,111625,2,0,B|416:16,1,160,2|0
|
||||
252,20,112125,5,6
|
||||
112,60,112375,1,0
|
||||
72,200,112625,1,8
|
||||
158,316,112875,2,2,B|236:321|324:259|326:152|278:89,2,320,2|2|0
|
||||
176,168,114125,2,2,B|214:236|313:276|405:220|431:145,1,320,2|0
|
||||
328,64,114875,2,2,B|259:102|219:201|275:293|350:319,1,320,2|0
|
||||
488,340,115625,2,0,B|456:172,1,160,2|0
|
||||
416,72,116125,5,6
|
||||
288,140,116375,1,0
|
||||
164,68,116625,1,8
|
||||
36,136,116875,1,0
|
||||
104,264,117125,1,6
|
||||
232,332,117375,1,0
|
||||
356,260,117625,1,8
|
||||
484,328,117875,1,0
|
||||
356,384,118125,1,6
|
||||
256,12,128125,5,4
|
||||
256,12,128250,1,2
|
||||
336,128,128500,1,0
|
||||
336,128,128625,1,2
|
||||
400,0,128875,1,0
|
||||
400,0,129000,1,2
|
||||
492,112,129250,1,0
|
||||
492,112,129375,1,2
|
||||
440,248,129625,2,2,B|272:284,1,160
|
||||
256,108,130125,5,4
|
||||
256,108,130250,1,2
|
||||
176,224,130500,1,0
|
||||
176,224,130625,1,2
|
||||
112,96,130875,1,0
|
||||
112,96,131000,1,2
|
||||
20,208,131250,1,0
|
||||
20,208,131375,1,2
|
||||
72,344,131625,2,2,B|240:380,1,160
|
||||
408,376,132125,6,0,B|512:352|584:248|592:-32|416:-48|256:-80|96:-16|56:88|8:224|88:304|144:336|184:368|256:368|256:368|328:368|368:336|424:304|504:224|456:88|416:-16|256:-80|96:-48|-80:-32|-72:248|0:352|104:376,1,2240,6|0
|
||||
256,192,135875,5,2
|
||||
256,192,136000,1,0
|
||||
256,192,136125,1,4
|
||||
136,104,136375,1,0
|
||||
132,240,136625,1,8
|
||||
133,240,136750,1,0
|
||||
256,280,137000,1,0
|
||||
255,280,137125,1,8
|
||||
256,280,137250,1,0
|
||||
256,280,137375,1,0
|
||||
380,240,137625,1,8
|
||||
376,104,137875,1,0
|
||||
256,124,138125,5,4
|
||||
256,124,138375,1,0
|
||||
144,192,138625,1,8
|
||||
144,192,138750,1,0
|
||||
256,260,139000,1,0
|
||||
256,260,139125,1,8
|
||||
256,260,139250,1,0
|
||||
256,260,139375,1,0
|
||||
368,192,139625,1,8
|
||||
256,124,139875,1,0
|
||||
256,124,140000,1,0
|
||||
256,124,140125,2,2,B|188:112|212:76|188:36|256:20,1,160,6|2
|
||||
332,128,140625,5,8
|
||||
332,128,140750,1,0
|
||||
332,256,141000,1,0
|
||||
332,256,141125,1,8
|
||||
332,256,141250,1,0
|
||||
332,256,141375,1,0
|
||||
180,256,141625,1,8
|
||||
180,128,141875,1,0
|
||||
256,56,142125,5,4
|
||||
256,56,142375,1,0
|
||||
256,160,142625,1,8
|
||||
256,160,142750,1,0
|
||||
256,264,143000,1,0
|
||||
256,264,143125,1,8
|
||||
256,264,143250,1,0
|
||||
256,264,143375,1,0
|
||||
188,352,143625,1,8
|
||||
324,352,143875,1,0
|
||||
324,352,144000,1,0
|
||||
324,352,144125,2,0,B|492:352,1,160,6|2
|
||||
392,280,144625,5,8
|
||||
392,280,144750,1,0
|
||||
324,192,145000,1,0
|
||||
324,192,145125,1,8
|
||||
324,192,145250,1,0
|
||||
324,192,145375,1,0
|
||||
188,192,145625,1,8
|
||||
120,280,145875,1,0
|
||||
256,288,146125,5,4
|
||||
256,288,146375,1,0
|
||||
256,176,146625,1,8
|
||||
256,176,146750,1,0
|
||||
176,96,147000,1,0
|
||||
176,96,147125,1,8
|
||||
176,96,147250,1,0
|
||||
176,96,147375,1,0
|
||||
256,16,147625,1,8
|
||||
336,96,147875,1,0
|
||||
336,96,148000,1,0
|
||||
336,96,148125,2,6,B|400:156|388:224|364:248,1,160,6|2
|
||||
256,272,148625,5,8
|
||||
240,264,148750,1,0
|
||||
240,180,149000,1,0
|
||||
256,172,149125,1,8
|
||||
272,164,149250,1,0
|
||||
288,156,149375,1,0
|
||||
256,64,149625,1,8
|
||||
256,64,149875,1,0
|
||||
116,180,150125,5,0
|
||||
120,200,150250,1,0
|
||||
132,224,150375,1,0
|
||||
152,236,150500,1,0
|
||||
176,240,150625,1,8
|
||||
208,240,150750,1,0
|
||||
232,236,150875,1,0
|
||||
248,216,151000,1,0
|
||||
256,192,151125,1,8
|
||||
260,168,151250,1,0
|
||||
272,144,151375,1,8
|
||||
292,132,151500,1,0
|
||||
316,128,151625,1,8
|
||||
348,128,151750,1,8
|
||||
372,132,151875,1,8
|
||||
388,152,152000,1,0
|
||||
404,184,152125,6,0,B|436:250|377:334|292:300,1,160,6|0
|
||||
108,200,152625,2,0,B|76:134|135:50|220:84,1,160,6|0
|
||||
256,192,153125,2,0,B|256:100,1,80,2|0
|
||||
256,192,153375,2,0,B|256:368,2,160,2|8|0
|
||||
360,60,154125,5,0
|
||||
360,60,154250,1,0
|
||||
360,60,154375,1,2
|
||||
256,12,154625,1,0
|
||||
256,12,154750,1,0
|
||||
256,12,154875,1,2
|
||||
154,64,155125,1,0
|
||||
154,64,155250,1,2
|
||||
155,63,155375,2,0,B|87:119|115:191|179:211|227:179,2,160,0|8|0
|
||||
163,74,156000,5,0
|
||||
163,74,156125,1,0
|
||||
163,74,156250,2,2,B|174:151|299:265|445:180|473:106,1,400,2|0
|
||||
320,80,157125,2,2,B|224:88|184:188|224:288|320:295,1,320
|
||||
348,292,157750,1,0
|
||||
380,280,157875,1,0
|
||||
404,260,158000,1,0
|
||||
412,236,158125,1,0
|
||||
412,208,158250,1,0
|
||||
404,180,158375,1,0
|
||||
264,68,158625,2,0,B|184:104,2,80,2|0|2
|
||||
164,216,159125,2,0,B|244:180,2,80,2|0|2
|
||||
56,144,159625,5,8
|
||||
64,276,159875,1,8
|
||||
64,276,160000,1,8
|
||||
64,276,160125,2,0,B|24:352,2,80,2|0|0
|
||||
128,288,160500,2,0,B|136:188,2,80,2|0|0
|
||||
192,300,160875,2,0,B|200:400,2,80,2|0|0
|
||||
240,256,161250,2,0,B|304:176,2,80,2|0|0
|
||||
284,304,161625,2,0,B|356:380,2,80,2|0|0
|
||||
328,256,162000,6,0,B|456:236,2,80,0|2|0
|
||||
308,192,162375,2,0,B|180:172,2,80,0|2|0
|
||||
340,136,162750,2,0,B|468:116,2,80,0|2|0
|
||||
284,100,163125,2,0,B|264:-28,2,80,0|2|0
|
||||
224,128,163500,2,0,B|204:256,2,80,0|2|0
|
||||
180,76,163875,6,0,B|92:52,2,80,2|0|0
|
||||
144,132,164250,2,0,B|72:184,2,80,2|0|0
|
||||
168,196,164625,2,0,B|240:248,2,80,2|0|0
|
||||
136,256,165000,2,0,B|96:340,2,80,2|0|0
|
||||
188,296,165375,2,0,B|228:380,2,80,2|0|0
|
||||
236,252,165750,1,0
|
||||
236,252,165875,1,2
|
||||
364,276,166125,6,2,B|408:176|360:156|320:168|296:176|268:132|264:112|272:76|304:52|328:40,1,240,2|0
|
||||
264,24,166625,2,2,B|308:124|260:144|220:132|196:124|168:168|164:188|172:224|204:248|228:260,1,240,2|0
|
||||
192,280,167125,1,0
|
||||
320,376,167375,1,0
|
||||
192,376,167625,1,0
|
||||
256,328,167750,1,0
|
||||
320,280,167875,1,0
|
||||
256,124,168125,1,6
|
||||
256,192,168250,12,0,170125
|
||||
256,192,171125,12,6,172125
|
||||
48,56,172375,5,0
|
||||
20,184,172625,2,0,B|16:264|92:316|152:304,1,160,8|0
|
||||
240,300,173125,1,2
|
||||
200,176,173375,1,0
|
||||
324,220,173625,2,0,B|360:220|416:258|412:338,1,160,8|0
|
||||
412,334,174000,1,0
|
||||
412,334,174125,2,0,B|456:156,1,160,6|0
|
||||
398,35,174625,2,0,B|220:-8,1,160,2|0
|
||||
245,0,175000,1,0
|
||||
245,0,175125,2,0,B|201:178,1,160,6|0
|
||||
259,299,175625,2,0,B|437:342,1,160,2|0
|
||||
424,176,176125,5,6
|
||||
272,128,176375,1,0
|
||||
116,152,176625,1,8
|
||||
173,253,176875,2,2,B|257:233|295:142|287:44|213:0,2,320,2|2|0
|
||||
28,204,178125,2,2,B|356:316,1,320
|
||||
172,360,178875,2,2,B|500:248,1,320,2|0
|
||||
384,148,179625,2,0,B|292:168|224:96|232:44,1,160,2|0
|
||||
244,93,180000,1,0
|
||||
244,93,180125,6,0,B|64:120,1,160,6|0
|
||||
100,268,180625,2,0,B|256:296,1,160,8|0
|
||||
257,296,181000,1,0
|
||||
256,296,181125,2,0,B|413:267,1,160,6|0
|
||||
426,116,181625,2,0,B|267:93,1,160,8|2
|
||||
267,93,182000,5,2
|
||||
267,93,182125,2,2,B|180:112|168:212,1,160,2|0
|
||||
140,380,182625,2,0,B|227:361|239:261,1,160,8|0
|
||||
62,169,183125,2,2,B|80:256|180:268,1,160,2|0
|
||||
348,296,183625,2,0,B|329:208|229:196,1,160,8|0
|
||||
64,172,184125,1,6
|
||||
256,192,184250,12,2,185625
|
||||
48,188,186125,6,2,B|96:108|256:108|256:192|256:276|416:276|464:196,1,480,2|0
|
||||
328,144,187125,2,0,B|296:316,1,160,2|0
|
||||
184,240,187625,2,0,B|216:68,1,160,2|0
|
||||
256,192,188125,1,6
|
||||
256,192,188250,12,2,189625
|
||||
464,188,190125,6,2,B|416:108|256:108|256:192|256:276|96:276|48:196,1,480,2|0
|
||||
184,144,191125,2,0,B|216:316,1,160,2|0
|
||||
328,240,191625,2,0,B|296:68,1,160,2|0
|
||||
164,32,192125,5,6
|
||||
28,84,192375,1,0
|
||||
28,228,192625,1,8
|
||||
128,332,192875,2,2,B|160:224|300:172|408:244,2,320,2|2|0
|
||||
276,356,194125,2,2,B|384:324|436:184|364:76,1,320
|
||||
236,28,194875,2,2,B|128:60|76:200|148:308,1,320,2|0
|
||||
280,268,195625,2,0,B|232:116,1,160,2|0
|
||||
104,52,196125,5,6
|
||||
136,192,196375,1,0
|
||||
116,344,196625,1,8
|
||||
256,312,196875,1,0
|
||||
332,312,197000,1,0
|
||||
408,332,197125,1,6
|
||||
392,264,197250,1,0
|
||||
376,192,197375,1,0
|
||||
396,40,197625,1,8
|
||||
256,72,197875,5,0
|
||||
256,72,198000,1,0
|
||||
256,72,198125,1,6
|
||||
136,192,198625,1,6
|
||||
256,312,199125,1,6
|
||||
376,192,199625,1,6
|
||||
256,192,200125,1,6
|
@ -15,6 +15,7 @@ using osu.Game.Rulesets.Catch.Beatmaps;
|
||||
using osu.Game.Rulesets.Catch.Difficulty;
|
||||
using osu.Game.Rulesets.Catch.Edit;
|
||||
using osu.Game.Rulesets.Catch.Mods;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Catch.Replays;
|
||||
using osu.Game.Rulesets.Catch.Scoring;
|
||||
using osu.Game.Rulesets.Catch.Skinning.Argon;
|
||||
@ -235,5 +236,17 @@ namespace osu.Game.Rulesets.Catch
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
/// <seealso cref="CatchHitObject.ApplyDefaultsToSelf"/>
|
||||
public override BeatmapDifficulty GetRateAdjustedDisplayDifficulty(IBeatmapDifficultyInfo difficulty, double rate)
|
||||
{
|
||||
BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(difficulty);
|
||||
|
||||
double preempt = IBeatmapDifficultyInfo.DifficultyRange(adjustedDifficulty.ApproachRate, CatchHitObject.PREEMPT_MAX, CatchHitObject.PREEMPT_MID, CatchHitObject.PREEMPT_MIN);
|
||||
preempt /= rate;
|
||||
adjustedDifficulty.ApproachRate = (float)IBeatmapDifficultyInfo.InverseDifficultyRange(preempt, CatchHitObject.PREEMPT_MAX, CatchHitObject.PREEMPT_MID, CatchHitObject.PREEMPT_MIN);
|
||||
|
||||
return adjustedDifficulty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -150,7 +150,7 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
{
|
||||
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
||||
|
||||
TimePreempt = (float)IBeatmapDifficultyInfo.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450);
|
||||
TimePreempt = (float)IBeatmapDifficultyInfo.DifficultyRange(difficulty.ApproachRate, PREEMPT_MAX, PREEMPT_MID, PREEMPT_MIN);
|
||||
|
||||
Scale = LegacyRulesetExtensions.CalculateScaleFromCircleSize(difficulty.CircleSize);
|
||||
}
|
||||
@ -189,6 +189,21 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
// The half of the height of the osu! playfield.
|
||||
public const float DEFAULT_LEGACY_CONVERT_Y = 192;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum preempt time at AR=10.
|
||||
/// </summary>
|
||||
public const double PREEMPT_MIN = 450;
|
||||
|
||||
/// <summary>
|
||||
/// Median preempt time at AR=5.
|
||||
/// </summary>
|
||||
public const double PREEMPT_MID = 1200;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum preempt time at AR=0.
|
||||
/// </summary>
|
||||
public const double PREEMPT_MAX = 1800;
|
||||
|
||||
/// <summary>
|
||||
/// The Y position of the hit object is not used in the normal osu!catch gameplay.
|
||||
/// It is preserved to maximize the backward compatibility with the legacy editor, in which the mappers use the Y position to organize the patterns.
|
||||
|
@ -10,7 +10,6 @@ using osu.Framework.Bindables;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
@ -103,8 +102,7 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
AddNested(new TinyDroplet
|
||||
{
|
||||
StartTime = t + lastEvent.Value.Time,
|
||||
X = ClampToPlayfield(EffectiveX + Path.PositionAt(
|
||||
lastEvent.Value.PathProgress + (t / sinceLastTick) * (e.PathProgress - lastEvent.Value.PathProgress)).X),
|
||||
X = EffectiveX + Path.PositionAt(lastEvent.Value.PathProgress + (t / sinceLastTick) * (e.PathProgress - lastEvent.Value.PathProgress)).X,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -121,7 +119,7 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
{
|
||||
Samples = dropletSamples,
|
||||
StartTime = e.Time,
|
||||
X = ClampToPlayfield(EffectiveX + Path.PositionAt(e.PathProgress).X),
|
||||
X = EffectiveX + Path.PositionAt(e.PathProgress).X,
|
||||
});
|
||||
break;
|
||||
|
||||
@ -132,16 +130,14 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
{
|
||||
Samples = this.GetNodeSamples(nodeIndex++),
|
||||
StartTime = e.Time,
|
||||
X = ClampToPlayfield(EffectiveX + Path.PositionAt(e.PathProgress).X),
|
||||
X = EffectiveX + Path.PositionAt(e.PathProgress).X,
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public float EndX => ClampToPlayfield(EffectiveX + this.CurvePositionAt(1).X);
|
||||
|
||||
public float ClampToPlayfield(float value) => Math.Clamp(value, 0, CatchPlayfield.WIDTH);
|
||||
public float EndX => EffectiveX + this.CurvePositionAt(1).X;
|
||||
|
||||
[JsonIgnore]
|
||||
public double Duration
|
||||
|
@ -2,7 +2,6 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Framework;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
@ -100,16 +99,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
return SkinUtils.As<TValue>(new Bindable<float>(30));
|
||||
|
||||
case LegacyManiaSkinConfigurationLookups.ColumnWidth:
|
||||
|
||||
float width;
|
||||
|
||||
bool isSpecialColumn = stage.IsSpecialColumn(columnIndex);
|
||||
|
||||
// Best effort until we have better mobile support.
|
||||
if (RuntimeInfo.IsMobile)
|
||||
width = 170 * Math.Min(1, 7f / beatmap.TotalColumns) * (isSpecialColumn ? 1.8f : 1);
|
||||
else
|
||||
width = 60 * (isSpecialColumn ? 2 : 1);
|
||||
float width = 60 * (isSpecialColumn ? 2 : 1);
|
||||
|
||||
return SkinUtils.As<TValue>(new Bindable<float>(width));
|
||||
|
||||
|
@ -34,7 +34,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
FallbackColumnIndex = "S";
|
||||
else
|
||||
{
|
||||
int distanceToEdge = Math.Min(Column.Index, (stage.Columns - 1) - Column.Index);
|
||||
// Account for cases like dual-stage (assume that all stages have the same column count for now).
|
||||
int columnInStage = Column.Index % stage.Columns;
|
||||
int distanceToEdge = Math.Min(columnInStage, (stage.Columns - 1) - columnInStage);
|
||||
FallbackColumnIndex = distanceToEdge % 2 == 0 ? "1" : "2";
|
||||
}
|
||||
}
|
||||
|
@ -3,14 +3,17 @@
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Skinning;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.UI
|
||||
{
|
||||
@ -60,6 +63,12 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
onSkinChanged();
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
updateMobileSizing();
|
||||
}
|
||||
|
||||
private void onSkinChanged()
|
||||
{
|
||||
for (int i = 0; i < stageDefinition.Columns; i++)
|
||||
@ -77,12 +86,15 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.ColumnWidth, i))
|
||||
?.Value;
|
||||
|
||||
if (width == null)
|
||||
// only used by default skin (legacy skins get defaults set in LegacyManiaSkinConfiguration)
|
||||
columns[i].Width = stageDefinition.IsSpecialColumn(i) ? Column.SPECIAL_COLUMN_WIDTH : Column.COLUMN_WIDTH;
|
||||
else
|
||||
columns[i].Width = width.Value;
|
||||
bool isSpecialColumn = stageDefinition.IsSpecialColumn(i);
|
||||
|
||||
// only used by default skin (legacy skins get defaults set in LegacyManiaSkinConfiguration)
|
||||
width ??= isSpecialColumn ? Column.SPECIAL_COLUMN_WIDTH : Column.COLUMN_WIDTH;
|
||||
|
||||
columns[i].Width = width.Value;
|
||||
}
|
||||
|
||||
updateMobileSizing();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -92,10 +104,29 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
/// <param name="content">The content.</param>
|
||||
public void SetContentForColumn(int column, TContent content) => columns[column].Child = content;
|
||||
|
||||
public new MarginPadding Padding
|
||||
private void updateMobileSizing()
|
||||
{
|
||||
get => base.Padding;
|
||||
set => base.Padding = value;
|
||||
if (!IsLoaded || !RuntimeInfo.IsMobile)
|
||||
return;
|
||||
|
||||
// GridContainer+CellContainer containing this stage (gets split up for dual stages).
|
||||
Vector2? containingCell = this.FindClosestParent<Stage>()?.Parent?.DrawSize;
|
||||
|
||||
// Will be null in tests.
|
||||
if (containingCell == null)
|
||||
return;
|
||||
|
||||
float aspectRatio = containingCell.Value.X / containingCell.Value.Y;
|
||||
|
||||
// 2.83 is a mostly arbitrary scale-up (170 / 60, based on original implementation for argon)
|
||||
float mobileAdjust = 2.83f * Math.Min(1, 7f / stageDefinition.Columns);
|
||||
// 1.92 is a "reference" mobile screen aspect ratio for phones.
|
||||
// We should scale it back for cases like tablets which aren't so extreme.
|
||||
mobileAdjust *= aspectRatio / 1.92f;
|
||||
|
||||
// Best effort until we have better mobile support.
|
||||
for (int i = 0; i < stageDefinition.Columns; i++)
|
||||
columns[i].Width *= mobileAdjust;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
|
@ -0,0 +1,65 @@
|
||||
// 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 NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class OsuRateAdjustedDisplayDifficultyTest
|
||||
{
|
||||
private static IEnumerable<float> difficultyValuesToTest()
|
||||
{
|
||||
for (float i = 0; i <= 10; i += 0.5f)
|
||||
yield return i;
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(difficultyValuesToTest))]
|
||||
public void TestApproachRateIsUnchangedWithRateEqualToOne(float originalApproachRate)
|
||||
{
|
||||
var ruleset = new OsuRuleset();
|
||||
var difficulty = new BeatmapDifficulty { ApproachRate = originalApproachRate };
|
||||
|
||||
var adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(difficulty, 1);
|
||||
|
||||
Assert.That(adjustedDifficulty.ApproachRate, Is.EqualTo(originalApproachRate));
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(difficultyValuesToTest))]
|
||||
public void TestOverallDifficultyIsUnchangedWithRateEqualToOne(float originalOverallDifficulty)
|
||||
{
|
||||
var ruleset = new OsuRuleset();
|
||||
var difficulty = new BeatmapDifficulty { OverallDifficulty = originalOverallDifficulty };
|
||||
|
||||
var adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(difficulty, 1);
|
||||
|
||||
Assert.That(adjustedDifficulty.OverallDifficulty, Is.EqualTo(originalOverallDifficulty));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRateBelowOne()
|
||||
{
|
||||
var ruleset = new OsuRuleset();
|
||||
var difficulty = new BeatmapDifficulty();
|
||||
|
||||
var adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(difficulty, 0.75);
|
||||
|
||||
Assert.That(adjustedDifficulty.ApproachRate, Is.EqualTo(1.67).Within(0.01));
|
||||
Assert.That(adjustedDifficulty.OverallDifficulty, Is.EqualTo(2.22).Within(0.01));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRateAboveOne()
|
||||
{
|
||||
var ruleset = new OsuRuleset();
|
||||
var difficulty = new BeatmapDifficulty();
|
||||
|
||||
var adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(difficulty, 1.5);
|
||||
|
||||
Assert.That(adjustedDifficulty.ApproachRate, Is.EqualTo(7.67).Within(0.01));
|
||||
Assert.That(adjustedDifficulty.OverallDifficulty, Is.EqualTo(7.77).Within(0.01));
|
||||
}
|
||||
}
|
||||
}
|
@ -37,6 +37,16 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
/// </summary>
|
||||
public const double PREEMPT_MIN = 450;
|
||||
|
||||
/// <summary>
|
||||
/// Median preempt time at AR=5.
|
||||
/// </summary>
|
||||
public const double PREEMPT_MID = 1200;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum preempt time at AR=0.
|
||||
/// </summary>
|
||||
public const double PREEMPT_MAX = 1800;
|
||||
|
||||
public double TimePreempt = 600;
|
||||
public double TimeFadeIn = 400;
|
||||
|
||||
@ -148,7 +158,7 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
{
|
||||
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
||||
|
||||
TimePreempt = (float)IBeatmapDifficultyInfo.DifficultyRange(difficulty.ApproachRate, 1800, 1200, PREEMPT_MIN);
|
||||
TimePreempt = (float)IBeatmapDifficultyInfo.DifficultyRange(difficulty.ApproachRate, PREEMPT_MAX, PREEMPT_MID, PREEMPT_MIN);
|
||||
|
||||
// Preempt time can go below 450ms. Normally, this is achieved via the DT mod which uniformly speeds up all animations game wide regardless of AR.
|
||||
// This uniform speedup is hard to match 1:1, however we can at least make AR>10 (via mods) feel good by extending the upper linear function above.
|
||||
|
@ -331,5 +331,23 @@ namespace osu.Game.Rulesets.Osu
|
||||
}
|
||||
|
||||
public override RulesetSetupSection CreateEditorSetupSection() => new OsuSetupSection();
|
||||
|
||||
/// <seealso cref="OsuHitObject.ApplyDefaultsToSelf"/>
|
||||
/// <seealso cref="OsuHitWindows"/>
|
||||
public override BeatmapDifficulty GetRateAdjustedDisplayDifficulty(IBeatmapDifficultyInfo difficulty, double rate)
|
||||
{
|
||||
BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(difficulty);
|
||||
|
||||
double preempt = IBeatmapDifficultyInfo.DifficultyRange(adjustedDifficulty.ApproachRate, OsuHitObject.PREEMPT_MAX, OsuHitObject.PREEMPT_MID, OsuHitObject.PREEMPT_MIN);
|
||||
preempt /= rate;
|
||||
adjustedDifficulty.ApproachRate = (float)IBeatmapDifficultyInfo.InverseDifficultyRange(preempt, OsuHitObject.PREEMPT_MAX, OsuHitObject.PREEMPT_MID, OsuHitObject.PREEMPT_MIN);
|
||||
|
||||
var greatHitWindowRange = OsuHitWindows.OSU_RANGES.Single(range => range.Result == HitResult.Great);
|
||||
double greatHitWindow = IBeatmapDifficultyInfo.DifficultyRange(adjustedDifficulty.OverallDifficulty, greatHitWindowRange.Min, greatHitWindowRange.Average, greatHitWindowRange.Max);
|
||||
greatHitWindow /= rate;
|
||||
adjustedDifficulty.OverallDifficulty = (float)IBeatmapDifficultyInfo.InverseDifficultyRange(greatHitWindow, greatHitWindowRange.Min, greatHitWindowRange.Average, greatHitWindowRange.Max);
|
||||
|
||||
return adjustedDifficulty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Scoring
|
||||
/// </summary>
|
||||
public const double MISS_WINDOW = 400;
|
||||
|
||||
private static readonly DifficultyRange[] osu_ranges =
|
||||
internal static readonly DifficultyRange[] OSU_RANGES =
|
||||
{
|
||||
new DifficultyRange(HitResult.Great, 80, 50, 20),
|
||||
new DifficultyRange(HitResult.Ok, 140, 100, 60),
|
||||
@ -34,6 +34,6 @@ namespace osu.Game.Rulesets.Osu.Scoring
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override DifficultyRange[] GetRanges() => osu_ranges;
|
||||
protected override DifficultyRange[] GetRanges() => OSU_RANGES;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,52 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class TaikoRateAdjustedDisplayDifficultyTest
|
||||
{
|
||||
private static IEnumerable<float> difficultyValuesToTest()
|
||||
{
|
||||
for (float i = 0; i <= 10; i += 0.5f)
|
||||
yield return i;
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(difficultyValuesToTest))]
|
||||
public void TestOverallDifficultyIsUnchangedWithRateEqualToOne(float originalOverallDifficulty)
|
||||
{
|
||||
var ruleset = new TaikoRuleset();
|
||||
var difficulty = new BeatmapDifficulty { OverallDifficulty = originalOverallDifficulty };
|
||||
|
||||
var adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(difficulty, 1);
|
||||
|
||||
Assert.That(adjustedDifficulty.OverallDifficulty, Is.EqualTo(originalOverallDifficulty));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRateBelowOne()
|
||||
{
|
||||
var ruleset = new TaikoRuleset();
|
||||
var difficulty = new BeatmapDifficulty();
|
||||
|
||||
var adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(difficulty, 0.75);
|
||||
|
||||
Assert.That(adjustedDifficulty.OverallDifficulty, Is.EqualTo(1.11).Within(0.01));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRateAboveOne()
|
||||
{
|
||||
var ruleset = new TaikoRuleset();
|
||||
var difficulty = new BeatmapDifficulty();
|
||||
|
||||
var adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(difficulty, 1.5);
|
||||
|
||||
Assert.That(adjustedDifficulty.OverallDifficulty, Is.EqualTo(8.89).Within(0.01));
|
||||
}
|
||||
}
|
||||
}
|
@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Taiko.Scoring
|
||||
{
|
||||
public class TaikoHitWindows : HitWindows
|
||||
{
|
||||
private static readonly DifficultyRange[] taiko_ranges =
|
||||
internal static readonly DifficultyRange[] TAIKO_RANGES =
|
||||
{
|
||||
new DifficultyRange(HitResult.Great, 50, 35, 20),
|
||||
new DifficultyRange(HitResult.Ok, 120, 80, 50),
|
||||
@ -27,6 +27,6 @@ namespace osu.Game.Rulesets.Taiko.Scoring
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override DifficultyRange[] GetRanges() => taiko_ranges;
|
||||
protected override DifficultyRange[] GetRanges() => TAIKO_RANGES;
|
||||
}
|
||||
}
|
||||
|
@ -264,5 +264,18 @@ namespace osu.Game.Rulesets.Taiko
|
||||
}), true)
|
||||
};
|
||||
}
|
||||
|
||||
/// <seealso cref="TaikoHitWindows"/>
|
||||
public override BeatmapDifficulty GetRateAdjustedDisplayDifficulty(IBeatmapDifficultyInfo difficulty, double rate)
|
||||
{
|
||||
BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(difficulty);
|
||||
|
||||
var greatHitWindowRange = TaikoHitWindows.TAIKO_RANGES.Single(range => range.Result == HitResult.Great);
|
||||
double greatHitWindow = IBeatmapDifficultyInfo.DifficultyRange(adjustedDifficulty.OverallDifficulty, greatHitWindowRange.Min, greatHitWindowRange.Average, greatHitWindowRange.Max);
|
||||
greatHitWindow /= rate;
|
||||
adjustedDifficulty.OverallDifficulty = (float)IBeatmapDifficultyInfo.InverseDifficultyRange(greatHitWindow, greatHitWindowRange.Min, greatHitWindowRange.Average, greatHitWindowRange.Max);
|
||||
|
||||
return adjustedDifficulty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -283,15 +283,17 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
openSkinEditor();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestOpenSkinEditorGameplaySceneWhenDifferentRulesetActive()
|
||||
[TestCase(1)]
|
||||
[TestCase(2)]
|
||||
[TestCase(3)]
|
||||
public void TestOpenSkinEditorGameplaySceneWhenDifferentRulesetActive(int rulesetId)
|
||||
{
|
||||
BeatmapSetInfo beatmapSet = null!;
|
||||
|
||||
AddStep("import beatmap", () => beatmapSet = BeatmapImportHelper.LoadQuickOszIntoOsu(Game).GetResultSafely());
|
||||
AddStep("select mania difficulty", () =>
|
||||
AddStep($"select difficulty for ruleset w/ ID {rulesetId}", () =>
|
||||
{
|
||||
var beatmap = beatmapSet.Beatmaps.First(b => b.Ruleset.OnlineID == 3);
|
||||
var beatmap = beatmapSet.Beatmaps.First(b => b.Ruleset.OnlineID == rulesetId);
|
||||
Game.Beatmap.Value = Game.BeatmapManager.GetWorkingBeatmap(beatmap);
|
||||
});
|
||||
|
||||
|
@ -220,7 +220,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
public void TestSelectedModsDontAffectStatistics()
|
||||
{
|
||||
AddStep("show map", () => overlay.ShowBeatmapSet(getBeatmapSet()));
|
||||
AddAssert("AR displayed as 0", () => overlay.ChildrenOfType<AdvancedStats.StatisticRow>().Single(s => s.Title == BeatmapsetsStrings.ShowStatsAr).Value == (0, null));
|
||||
AddAssert("AR displayed as 0", () => overlay.ChildrenOfType<AdvancedStats.StatisticRow>().Single(s => s.Title == BeatmapsetsStrings.ShowStatsAr).Value, () => Is.EqualTo((0, 0)));
|
||||
AddStep("set AR10 diff adjust", () => SelectedMods.Value = new[]
|
||||
{
|
||||
new OsuModDifficultyAdjust
|
||||
@ -228,7 +228,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
ApproachRate = { Value = 10 }
|
||||
}
|
||||
});
|
||||
AddAssert("AR still displayed as 0", () => overlay.ChildrenOfType<AdvancedStats.StatisticRow>().Single(s => s.Title == BeatmapsetsStrings.ShowStatsAr).Value == (0, null));
|
||||
AddAssert("AR still displayed as 0", () => overlay.ChildrenOfType<AdvancedStats.StatisticRow>().Single(s => s.Title == BeatmapsetsStrings.ShowStatsAr).Value, () => Is.EqualTo((0, 0)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -192,7 +192,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
AddStep("select collection", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(getCollectionDropdownItems().ElementAt(1));
|
||||
InputManager.MoveMouseTo(getCollectionDropdownItemAt(1));
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
@ -206,7 +206,8 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
AddStep("click manage collections filter", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(getCollectionDropdownItems().Last());
|
||||
int lastItemIndex = control.ChildrenOfType<CollectionDropdown>().Single().Items.Count() - 1;
|
||||
InputManager.MoveMouseTo(getCollectionDropdownItemAt(lastItemIndex));
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
@ -232,10 +233,10 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
private void assertCollectionDropdownContains(string collectionName, bool shouldContain = true) =>
|
||||
AddUntilStep($"collection dropdown {(shouldContain ? "contains" : "does not contain")} '{collectionName}'",
|
||||
// A bit of a roundabout way of going about this, see: https://github.com/ppy/osu-framework/issues/3871 + https://github.com/ppy/osu-framework/issues/3872
|
||||
() => shouldContain == (getCollectionDropdownItems().Any(i => i.ChildrenOfType<CompositeDrawable>().OfType<IHasText>().First().Text == collectionName)));
|
||||
() => shouldContain == control.ChildrenOfType<Menu.DrawableMenuItem>().Any(i => i.ChildrenOfType<CompositeDrawable>().OfType<IHasText>().First().Text == collectionName));
|
||||
|
||||
private IconButton getAddOrRemoveButton(int index)
|
||||
=> getCollectionDropdownItems().ElementAt(index).ChildrenOfType<IconButton>().Single();
|
||||
=> getCollectionDropdownItemAt(index).ChildrenOfType<IconButton>().Single();
|
||||
|
||||
private void addExpandHeaderStep() => AddStep("expand header", () =>
|
||||
{
|
||||
@ -249,7 +250,11 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
private IEnumerable<Dropdown<CollectionFilterMenuItem>.DropdownMenu.DrawableDropdownMenuItem> getCollectionDropdownItems()
|
||||
=> control.ChildrenOfType<CollectionDropdown>().Single().ChildrenOfType<Dropdown<CollectionFilterMenuItem>.DropdownMenu.DrawableDropdownMenuItem>();
|
||||
private Menu.DrawableMenuItem getCollectionDropdownItemAt(int index)
|
||||
{
|
||||
// todo: we should be able to use Items, but apparently that's not guaranteed to be ordered... see: https://github.com/ppy/osu-framework/pull/6079
|
||||
CollectionFilterMenuItem item = control.ChildrenOfType<CollectionDropdown>().Single().ItemSource.ElementAt(index);
|
||||
return control.ChildrenOfType<Menu.DrawableMenuItem>().Single(i => i.Item.Text.Value == item.CollectionName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Mods;
|
||||
@ -38,6 +39,9 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
|
||||
private TestModSelectOverlay modSelectOverlay = null!;
|
||||
|
||||
[Resolved]
|
||||
private OsuConfigManager configManager { get; set; } = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
@ -566,17 +570,33 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSearchFocusChangeViaKey()
|
||||
public void TestTextSearchActiveByDefault()
|
||||
{
|
||||
configManager.SetValue(OsuSetting.ModSelectTextSearchStartsActive, true);
|
||||
createScreen();
|
||||
|
||||
const Key focus_switch_key = Key.Tab;
|
||||
AddUntilStep("search text box focused", () => modSelectOverlay.SearchTextBox.HasFocus);
|
||||
|
||||
AddStep("press tab", () => InputManager.Key(focus_switch_key));
|
||||
AddAssert("focused", () => modSelectOverlay.SearchTextBox.HasFocus);
|
||||
AddStep("press tab", () => InputManager.Key(Key.Tab));
|
||||
AddAssert("search text box unfocused", () => !modSelectOverlay.SearchTextBox.HasFocus);
|
||||
|
||||
AddStep("press tab", () => InputManager.Key(focus_switch_key));
|
||||
AddAssert("lost focus", () => !modSelectOverlay.SearchTextBox.HasFocus);
|
||||
AddStep("press tab", () => InputManager.Key(Key.Tab));
|
||||
AddAssert("search text box focused", () => modSelectOverlay.SearchTextBox.HasFocus);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestTextSearchNotActiveByDefault()
|
||||
{
|
||||
configManager.SetValue(OsuSetting.ModSelectTextSearchStartsActive, false);
|
||||
createScreen();
|
||||
|
||||
AddUntilStep("search text box not focused", () => !modSelectOverlay.SearchTextBox.HasFocus);
|
||||
|
||||
AddStep("press tab", () => InputManager.Key(Key.Tab));
|
||||
AddAssert("search text box focused", () => modSelectOverlay.SearchTextBox.HasFocus);
|
||||
|
||||
AddStep("press tab", () => InputManager.Key(Key.Tab));
|
||||
AddAssert("search text box unfocused", () => !modSelectOverlay.SearchTextBox.HasFocus);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -787,7 +807,8 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<ModPresetPanel>().Single(preset => preset.Preset.Value.Name == "Half Time 0.5x"));
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddAssert("difficulty multiplier display shows correct value", () => modSelectOverlay.ChildrenOfType<ScoreMultiplierDisplay>().Single().Current.Value, () => Is.EqualTo(0.5));
|
||||
AddAssert("difficulty multiplier display shows correct value",
|
||||
() => modSelectOverlay.ChildrenOfType<ScoreMultiplierDisplay>().Single().Current.Value, () => Is.EqualTo(0.1).Within(Precision.DOUBLE_EPSILON));
|
||||
|
||||
// this is highly unorthodox in a test, but because the `ModSettingChangeTracker` machinery heavily leans on events and object disposal and re-creation,
|
||||
// it is instrumental in the reproduction of the failure scenario that this test is supposed to cover.
|
||||
@ -796,7 +817,8 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
AddStep("open customisation area", () => modSelectOverlay.CustomisationButton!.TriggerClick());
|
||||
AddStep("reset half time speed to default", () => modSelectOverlay.ChildrenOfType<ModSettingsArea>().Single()
|
||||
.ChildrenOfType<RevertToDefaultButton<double>>().Single().TriggerClick());
|
||||
AddUntilStep("difficulty multiplier display shows correct value", () => modSelectOverlay.ChildrenOfType<ScoreMultiplierDisplay>().Single().Current.Value, () => Is.EqualTo(0.7));
|
||||
AddUntilStep("difficulty multiplier display shows correct value",
|
||||
() => modSelectOverlay.ChildrenOfType<ScoreMultiplierDisplay>().Single().Current.Value, () => Is.EqualTo(0.3).Within(Precision.DOUBLE_EPSILON));
|
||||
}
|
||||
|
||||
private void waitForColumnLoad() => AddUntilStep("all column content loaded", () =>
|
||||
|
@ -21,6 +21,10 @@ namespace osu.Game.Tournament.Models
|
||||
|
||||
public double StarRating { get; set; }
|
||||
|
||||
public int EndTimeObjectCount { get; set; }
|
||||
|
||||
public int TotalObjectCount { get; set; }
|
||||
|
||||
public IBeatmapMetadataInfo Metadata { get; set; } = new BeatmapMetadata();
|
||||
|
||||
public IBeatmapDifficultyInfo Difficulty { get; set; } = new BeatmapDifficulty();
|
||||
@ -41,6 +45,8 @@ namespace osu.Game.Tournament.Models
|
||||
Metadata = beatmap.Metadata;
|
||||
Difficulty = beatmap.Difficulty;
|
||||
Covers = beatmap.BeatmapSet?.Covers ?? new BeatmapSetOnlineCovers();
|
||||
EndTimeObjectCount = beatmap.EndTimeObjectCount;
|
||||
TotalObjectCount = beatmap.TotalObjectCount;
|
||||
}
|
||||
|
||||
public bool Equals(IBeatmapInfo? other) => other is TournamentBeatmap b && this.MatchesOnlineID(b);
|
||||
|
@ -20,7 +20,6 @@ using osu.Game.Rulesets;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Scoring.Legacy;
|
||||
using osu.Game.Screens.Play;
|
||||
using Realms;
|
||||
|
||||
namespace osu.Game
|
||||
{
|
||||
@ -177,9 +176,13 @@ namespace osu.Game
|
||||
{
|
||||
Logger.Log("Querying for beatmaps with missing hitobject counts to reprocess...");
|
||||
|
||||
HashSet<Guid> beatmapIds = realmAccess.Run(r => new HashSet<Guid>(r.All<BeatmapInfo>()
|
||||
.Filter($"{nameof(BeatmapInfo.Difficulty)}.{nameof(BeatmapDifficulty.TotalObjectCount)} == 0")
|
||||
.AsEnumerable().Select(b => b.ID)));
|
||||
HashSet<Guid> beatmapIds = new HashSet<Guid>();
|
||||
|
||||
realmAccess.Run(r =>
|
||||
{
|
||||
foreach (var b in r.All<BeatmapInfo>().Where(b => b.TotalObjectCount == 0))
|
||||
beatmapIds.Add(b.ID);
|
||||
});
|
||||
|
||||
Logger.Log($"Found {beatmapIds.Count} beatmaps which require reprocessing.");
|
||||
|
||||
|
@ -21,9 +21,6 @@ namespace osu.Game.Beatmaps
|
||||
public double SliderMultiplier { get; set; } = 1.4;
|
||||
public double SliderTickRate { get; set; } = 1;
|
||||
|
||||
public int EndTimeObjectCount { get; set; }
|
||||
public int TotalObjectCount { get; set; }
|
||||
|
||||
public BeatmapDifficulty()
|
||||
{
|
||||
}
|
||||
@ -47,9 +44,6 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
difficulty.SliderMultiplier = SliderMultiplier;
|
||||
difficulty.SliderTickRate = SliderTickRate;
|
||||
|
||||
difficulty.EndTimeObjectCount = EndTimeObjectCount;
|
||||
difficulty.TotalObjectCount = TotalObjectCount;
|
||||
}
|
||||
|
||||
public virtual void CopyFrom(IBeatmapDifficultyInfo other)
|
||||
@ -61,9 +55,6 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
SliderMultiplier = other.SliderMultiplier;
|
||||
SliderTickRate = other.SliderTickRate;
|
||||
|
||||
EndTimeObjectCount = other.EndTimeObjectCount;
|
||||
TotalObjectCount = other.TotalObjectCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -388,9 +388,7 @@ namespace osu.Game.Beatmaps
|
||||
OverallDifficulty = decodedDifficulty.OverallDifficulty,
|
||||
ApproachRate = decodedDifficulty.ApproachRate,
|
||||
SliderMultiplier = decodedDifficulty.SliderMultiplier,
|
||||
SliderTickRate = decodedDifficulty.SliderTickRate,
|
||||
EndTimeObjectCount = decoded.HitObjects.Count(h => h is IHasDuration),
|
||||
TotalObjectCount = decoded.HitObjects.Count
|
||||
SliderTickRate = decodedDifficulty.SliderTickRate
|
||||
};
|
||||
|
||||
var metadata = new BeatmapMetadata
|
||||
@ -428,6 +426,8 @@ namespace osu.Game.Beatmaps
|
||||
GridSize = decodedInfo.GridSize,
|
||||
TimelineZoom = decodedInfo.TimelineZoom,
|
||||
MD5Hash = memoryStream.ComputeMD5Hash(),
|
||||
EndTimeObjectCount = decoded.HitObjects.Count(h => h is IHasDuration),
|
||||
TotalObjectCount = decoded.HitObjects.Count
|
||||
};
|
||||
|
||||
beatmaps.Add(beatmap);
|
||||
|
@ -120,6 +120,10 @@ namespace osu.Game.Beatmaps
|
||||
[JsonIgnore]
|
||||
public bool Hidden { get; set; }
|
||||
|
||||
public int EndTimeObjectCount { get; set; }
|
||||
|
||||
public int TotalObjectCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reset any fetched online linking information (and history).
|
||||
/// </summary>
|
||||
|
@ -91,8 +91,8 @@ namespace osu.Game.Beatmaps
|
||||
var working = workingBeatmapCache.GetWorkingBeatmap(beatmapInfo);
|
||||
var beatmap = working.Beatmap;
|
||||
|
||||
beatmapInfo.Difficulty.EndTimeObjectCount = beatmap.HitObjects.Count(h => h is IHasDuration);
|
||||
beatmapInfo.Difficulty.TotalObjectCount = beatmap.HitObjects.Count;
|
||||
beatmapInfo.EndTimeObjectCount = beatmap.HitObjects.Count(h => h is IHasDuration);
|
||||
beatmapInfo.TotalObjectCount = beatmap.HitObjects.Count;
|
||||
|
||||
// And invalidate again afterwards as re-fetching the most up-to-date database metadata will be required.
|
||||
workingBeatmapCache.Invalidate(beatmapInfo);
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
@ -38,6 +39,8 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
|
||||
private readonly Bindable<double> displayedStars = new BindableDouble();
|
||||
|
||||
private readonly Container textContainer;
|
||||
|
||||
/// <summary>
|
||||
/// The currently displayed stars of this display wrapped in a bindable.
|
||||
/// This bindable gets transformed on change rather than instantaneous, if animation is enabled.
|
||||
@ -116,15 +119,19 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
Size = new Vector2(8f),
|
||||
},
|
||||
Empty(),
|
||||
starsText = new OsuSpriteText
|
||||
textContainer = new Container
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Margin = new MarginPadding { Bottom = 1.5f },
|
||||
// todo: this should be size: 12f, but to match up with the design, it needs to be 14.4f
|
||||
// see https://github.com/ppy/osu-framework/issues/3271.
|
||||
Font = OsuFont.Torus.With(size: 14.4f, weight: FontWeight.Bold),
|
||||
Shadow = false,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Child = starsText = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Margin = new MarginPadding { Bottom = 1.5f },
|
||||
// todo: this should be size: 12f, but to match up with the design, it needs to be 14.4f
|
||||
// see https://github.com/ppy/osu-framework/issues/3271.
|
||||
Font = OsuFont.Torus.With(size: 14.4f, weight: FontWeight.Bold),
|
||||
Shadow = false,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -155,6 +162,11 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
|
||||
starIcon.Colour = s.NewValue >= 6.5 ? colours.Orange1 : colourProvider?.Background5 ?? Color4Extensions.FromHex("303d47");
|
||||
starsText.Colour = s.NewValue >= 6.5 ? colours.Orange1 : colourProvider?.Background5 ?? Color4.Black.Opacity(0.75f);
|
||||
|
||||
// In order to avoid autosize throwing the width of these displays all over the place,
|
||||
// let's lock in some sane defaults for the text width based on how many digits we're
|
||||
// displaying.
|
||||
textContainer.Width = 24 + Math.Max(starsText.Text.ToString().Length - 4, 0) * 6;
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
// 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;
|
||||
|
||||
namespace osu.Game.Beatmaps
|
||||
{
|
||||
/// <summary>
|
||||
@ -44,19 +46,6 @@ namespace osu.Game.Beatmaps
|
||||
/// </summary>
|
||||
double SliderTickRate { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The number of hitobjects in the beatmap with a distinct end time.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Canonically, these are hitobjects are either sliders or spinners.
|
||||
/// </remarks>
|
||||
int EndTimeObjectCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The total number of hitobjects in the beatmap.
|
||||
/// </summary>
|
||||
int TotalObjectCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Maps a difficulty value [0, 10] to a two-piece linear range of values.
|
||||
/// </summary>
|
||||
@ -105,5 +94,21 @@ namespace osu.Game.Beatmaps
|
||||
/// <returns>Value to which the difficulty value maps in the specified range.</returns>
|
||||
static double DifficultyRange(double difficulty, (double od0, double od5, double od10) range)
|
||||
=> DifficultyRange(difficulty, range.od0, range.od5, range.od10);
|
||||
|
||||
/// <summary>
|
||||
/// Inverse function to <see cref="DifficultyRange(double,double,double,double)"/>.
|
||||
/// Maps a value returned by the function above back to the difficulty that produced it.
|
||||
/// </summary>
|
||||
/// <param name="difficultyValue">The difficulty-dependent value to be unmapped.</param>
|
||||
/// <param name="diff0">Minimum of the resulting range which will be achieved by a difficulty value of 0.</param>
|
||||
/// <param name="diff5">Midpoint of the resulting range which will be achieved by a difficulty value of 5.</param>
|
||||
/// <param name="diff10">Maximum of the resulting range which will be achieved by a difficulty value of 10.</param>
|
||||
/// <returns>Value to which the difficulty value maps in the specified range.</returns>
|
||||
static double InverseDifficultyRange(double difficultyValue, double diff0, double diff5, double diff10)
|
||||
{
|
||||
return Math.Sign(difficultyValue - diff5) == Math.Sign(diff10 - diff5)
|
||||
? (difficultyValue - diff5) / (diff10 - diff5) * 5 + 5
|
||||
: (difficultyValue - diff5) / (diff5 - diff0) * 5 + 5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -61,5 +61,18 @@ namespace osu.Game.Beatmaps
|
||||
/// The basic star rating for this beatmap (with no mods applied).
|
||||
/// </summary>
|
||||
double StarRating { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The number of hitobjects in the beatmap with a distinct end time.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Canonically, these are hitobjects are either sliders or spinners.
|
||||
/// </remarks>
|
||||
int EndTimeObjectCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The total number of hitobjects in the beatmap.
|
||||
/// </summary>
|
||||
int TotalObjectCount { get; }
|
||||
}
|
||||
}
|
||||
|
@ -43,11 +43,14 @@ namespace osu.Game.Collections
|
||||
|
||||
private IDisposable? realmSubscription;
|
||||
|
||||
private readonly CollectionFilterMenuItem allBeatmapsItem = new AllBeatmapsCollectionFilterMenuItem();
|
||||
|
||||
public CollectionDropdown()
|
||||
{
|
||||
ItemSource = filters;
|
||||
|
||||
Current.Value = new AllBeatmapsCollectionFilterMenuItem();
|
||||
Current.Value = allBeatmapsItem;
|
||||
AlwaysShowSearchBar = true;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@ -61,37 +64,51 @@ namespace osu.Game.Collections
|
||||
|
||||
private void collectionsChanged(IRealmCollection<BeatmapCollection> collections, ChangeSet? changes)
|
||||
{
|
||||
var selectedItem = SelectedItem?.Value?.Collection;
|
||||
|
||||
var allBeatmaps = new AllBeatmapsCollectionFilterMenuItem();
|
||||
|
||||
filters.Clear();
|
||||
filters.Add(allBeatmaps);
|
||||
filters.AddRange(collections.Select(c => new CollectionFilterMenuItem(c.ToLive(realm))));
|
||||
|
||||
if (ShowManageCollectionsItem)
|
||||
filters.Add(new ManageCollectionsFilterMenuItem());
|
||||
|
||||
// This current update and schedule is required to work around dropdown headers not updating text even when the selected item
|
||||
// changes. It's not great but honestly the whole dropdown menu structure isn't great. This needs to be fixed, but I'll issue
|
||||
// a warning that it's going to be a frustrating journey.
|
||||
Current.Value = allBeatmaps;
|
||||
Schedule(() =>
|
||||
if (changes == null)
|
||||
{
|
||||
// current may have changed before the scheduled call is run.
|
||||
if (Current.Value != allBeatmaps)
|
||||
return;
|
||||
|
||||
Current.Value = filters.SingleOrDefault(f => f.Collection != null && f.Collection.ID == selectedItem?.ID) ?? filters[0];
|
||||
});
|
||||
|
||||
// Trigger a re-filter if the current item was in the change set.
|
||||
if (selectedItem != null && changes != null)
|
||||
filters.Add(allBeatmapsItem);
|
||||
filters.AddRange(collections.Select(c => new CollectionFilterMenuItem(c.ToLive(realm))));
|
||||
if (ShowManageCollectionsItem)
|
||||
filters.Add(new ManageCollectionsFilterMenuItem());
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (int index in changes.ModifiedIndices)
|
||||
foreach (int i in changes.DeletedIndices)
|
||||
filters.RemoveAt(i + 1);
|
||||
|
||||
foreach (int i in changes.InsertedIndices)
|
||||
filters.Insert(i + 1, new CollectionFilterMenuItem(collections[i].ToLive(realm)));
|
||||
|
||||
var selectedItem = SelectedItem?.Value;
|
||||
|
||||
foreach (int i in changes.NewModifiedIndices)
|
||||
{
|
||||
if (collections[index].ID == selectedItem.ID)
|
||||
var updatedItem = collections[i];
|
||||
|
||||
// This is responsible for updating the state of the +/- button and the collection's name.
|
||||
// TODO: we can probably make the menu items update with changes to avoid this.
|
||||
filters.RemoveAt(i + 1);
|
||||
filters.Insert(i + 1, new CollectionFilterMenuItem(updatedItem.ToLive(realm)));
|
||||
|
||||
if (updatedItem.ID == selectedItem?.Collection?.ID)
|
||||
{
|
||||
// This current update and schedule is required to work around dropdown headers not updating text even when the selected item
|
||||
// changes. It's not great but honestly the whole dropdown menu structure isn't great. This needs to be fixed, but I'll issue
|
||||
// a warning that it's going to be a frustrating journey.
|
||||
Current.Value = allBeatmapsItem;
|
||||
Schedule(() =>
|
||||
{
|
||||
// current may have changed before the scheduled call is run.
|
||||
if (Current.Value != allBeatmapsItem)
|
||||
return;
|
||||
|
||||
Current.Value = filters.SingleOrDefault(f => f.Collection?.ID == selectedItem.Collection?.ID) ?? filters[0];
|
||||
});
|
||||
|
||||
// Trigger an external re-filter if the current item was in the change set.
|
||||
RequestFilter?.Invoke();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -49,6 +49,7 @@ namespace osu.Game.Configuration
|
||||
|
||||
SetDefault(OsuSetting.RandomSelectAlgorithm, RandomSelectAlgorithm.RandomPermutation);
|
||||
SetDefault(OsuSetting.ModSelectHotkeyStyle, ModSelectHotkeyStyle.Sequential);
|
||||
SetDefault(OsuSetting.ModSelectTextSearchStartsActive, true);
|
||||
|
||||
SetDefault(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2f, 1f);
|
||||
|
||||
@ -416,5 +417,6 @@ namespace osu.Game.Configuration
|
||||
AutomaticallyDownloadMissingBeatmaps,
|
||||
EditorShowSpeedChanges,
|
||||
TouchDisableGameplayTaps,
|
||||
ModSelectTextSearchStartsActive,
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,9 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework;
|
||||
@ -54,6 +57,49 @@ namespace osu.Game.Database
|
||||
|
||||
public void UpdateStorage(string stablePath) => cachedStorage = new StableStorage(stablePath, gameHost as DesktopGameHost);
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether a valid location to run a stable import from can be determined starting from the supplied <paramref name="directory"/>.
|
||||
/// </summary>
|
||||
/// <param name="directory">The directory to check for stable import eligibility.</param>
|
||||
/// <param name="stableRoot">
|
||||
/// If the return value is <see langword="true"/>,
|
||||
/// this parameter will contain the <see cref="DirectoryInfo"/> to use as the root directory for importing.
|
||||
/// </param>
|
||||
public bool IsUsableForStableImport(DirectoryInfo? directory, [NotNullWhen(true)] out DirectoryInfo? stableRoot)
|
||||
{
|
||||
if (directory == null)
|
||||
{
|
||||
stableRoot = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
// A full stable installation will have a configuration file present.
|
||||
// This is the best case scenario, as it may contain a custom beatmap directory we need to traverse to.
|
||||
if (directory.GetFiles(@"osu!.*.cfg").Any())
|
||||
{
|
||||
stableRoot = directory;
|
||||
return true;
|
||||
}
|
||||
|
||||
// The user may only have their songs or skins folders left.
|
||||
// We still want to allow them to import based on this.
|
||||
if (directory.GetDirectories(@"Songs").Any() || directory.GetDirectories(@"Skins").Any())
|
||||
{
|
||||
stableRoot = directory;
|
||||
return true;
|
||||
}
|
||||
|
||||
// The user may have traversed *inside* their songs or skins folders.
|
||||
if (directory.Parent != null && (directory.Name == @"Songs" || directory.Name == @"Skins"))
|
||||
{
|
||||
stableRoot = directory.Parent;
|
||||
return true;
|
||||
}
|
||||
|
||||
stableRoot = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool CheckSongsFolderHardLinkAvailability()
|
||||
{
|
||||
var stableStorage = GetCurrentStableStorage();
|
||||
|
@ -88,9 +88,9 @@ namespace osu.Game.Database
|
||||
/// 34 2023-08-21 Add BackgroundReprocessingFailed flag to ScoreInfo to track upgrade failures.
|
||||
/// 35 2023-10-16 Clear key combinations of keybindings that are assigned to more than one action in a given settings section.
|
||||
/// 36 2023-10-26 Add LegacyOnlineID to ScoreInfo. Move osu_scores_*_high IDs stored in OnlineID to LegacyOnlineID. Reset anomalous OnlineIDs.
|
||||
/// 37 2023-12-10 Add EndTimeObjectCount and TotalObjectCount to BeatmapDifficulty.
|
||||
/// 38 2023-12-10 Add EndTimeObjectCount and TotalObjectCount to BeatmapInfo.
|
||||
/// </summary>
|
||||
private const int schema_version = 37;
|
||||
private const int schema_version = 38;
|
||||
|
||||
/// <summary>
|
||||
/// Lock object which is held during <see cref="BlockAllOperations"/> sections, blocking realm retrieval during blocking periods.
|
||||
|
@ -49,6 +49,11 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString OpenOsuFolder => new TranslatableString(getKey(@"open_osu_folder"), @"Open osu! folder");
|
||||
|
||||
/// <summary>
|
||||
/// "Export logs"
|
||||
/// </summary>
|
||||
public static LocalisableString ExportLogs => new TranslatableString(getKey(@"export_logs"), @"Export logs");
|
||||
|
||||
/// <summary>
|
||||
/// "Change folder location..."
|
||||
/// </summary>
|
||||
|
@ -104,6 +104,11 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString ModSelectHotkeyStyle => new TranslatableString(getKey(@"mod_select_hotkey_style"), @"Mod select hotkey style");
|
||||
|
||||
/// <summary>
|
||||
/// "Automatically focus search text box in mod select"
|
||||
/// </summary>
|
||||
public static LocalisableString ModSelectTextSearchStartsActive => new TranslatableString(getKey(@"mod_select_text_search_starts_active"), @"Automatically focus search text box in mod select");
|
||||
|
||||
/// <summary>
|
||||
/// "no limit"
|
||||
/// </summary>
|
||||
|
@ -41,6 +41,10 @@ namespace osu.Game.Online.API.Requests.Responses
|
||||
[JsonProperty(@"difficulty_rating")]
|
||||
public double StarRating { get; set; }
|
||||
|
||||
public int EndTimeObjectCount => SliderCount + SpinnerCount;
|
||||
|
||||
public int TotalObjectCount => CircleCount + SliderCount + SpinnerCount;
|
||||
|
||||
[JsonProperty(@"drain")]
|
||||
public float DrainRate { get; set; }
|
||||
|
||||
@ -108,9 +112,7 @@ namespace osu.Game.Online.API.Requests.Responses
|
||||
DrainRate = DrainRate,
|
||||
CircleSize = CircleSize,
|
||||
ApproachRate = ApproachRate,
|
||||
OverallDifficulty = OverallDifficulty,
|
||||
EndTimeObjectCount = SliderCount + SpinnerCount,
|
||||
TotalObjectCount = CircleCount + SliderCount + SpinnerCount
|
||||
OverallDifficulty = OverallDifficulty
|
||||
};
|
||||
|
||||
IBeatmapSetInfo? IBeatmapInfo.BeatmapSet => BeatmapSet;
|
||||
|
@ -244,6 +244,8 @@ namespace osu.Game.Overlays.FirstRunSetup
|
||||
[Resolved(canBeNull: true)] // Can't really be null but required to handle potential of disposal before DI completes.
|
||||
private OsuGameBase? game { get; set; }
|
||||
|
||||
private bool changingDirectory;
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
@ -259,24 +261,37 @@ namespace osu.Game.Overlays.FirstRunSetup
|
||||
|
||||
private void onDirectorySelected(ValueChangedEvent<DirectoryInfo?> directory)
|
||||
{
|
||||
if (directory.NewValue == null)
|
||||
{
|
||||
Current.Value = string.Empty;
|
||||
if (changingDirectory)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
changingDirectory = true;
|
||||
|
||||
if (directory.NewValue == null)
|
||||
{
|
||||
Current.Value = string.Empty;
|
||||
return;
|
||||
}
|
||||
|
||||
// DirectorySelectors can trigger a noop value changed, but `DirectoryInfo` equality doesn't catch this.
|
||||
if (directory.OldValue?.FullName == directory.NewValue.FullName)
|
||||
return;
|
||||
|
||||
if (legacyImportManager.IsUsableForStableImport(directory.NewValue, out var stableRoot))
|
||||
{
|
||||
this.HidePopover();
|
||||
|
||||
string path = stableRoot.FullName;
|
||||
|
||||
legacyImportManager.UpdateStorage(path);
|
||||
Current.Value = path;
|
||||
currentDirectory.Value = stableRoot;
|
||||
}
|
||||
}
|
||||
|
||||
// DirectorySelectors can trigger a noop value changed, but `DirectoryInfo` equality doesn't catch this.
|
||||
if (directory.OldValue?.FullName == directory.NewValue.FullName)
|
||||
return;
|
||||
|
||||
if (directory.NewValue?.GetFiles(@"osu!.*.cfg").Any() ?? false)
|
||||
finally
|
||||
{
|
||||
this.HidePopover();
|
||||
|
||||
string path = directory.NewValue.FullName;
|
||||
|
||||
legacyImportManager.UpdateStorage(path);
|
||||
Current.Value = path;
|
||||
changingDirectory = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
142
osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs
Normal file
142
osu.Game/Overlays/Mods/AdjustedAttributesTooltip.cs
Normal file
@ -0,0 +1,142 @@
|
||||
// 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 osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
public partial class AdjustedAttributesTooltip : VisibilityContainer, ITooltip<AdjustedAttributesTooltip.Data?>
|
||||
{
|
||||
private FillFlowContainer attributesFillFlow = null!;
|
||||
|
||||
private Container content = null!;
|
||||
|
||||
private Data? data;
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
Masking = true;
|
||||
CornerRadius = 5;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
content = new Container
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colours.Gray3,
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Vertical = 10, Horizontal = 15 },
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = "One or more values are being adjusted by mods that change speed.",
|
||||
},
|
||||
attributesFillFlow = new FillFlowContainer
|
||||
{
|
||||
Direction = FillDirection.Vertical,
|
||||
AutoSizeAxes = Axes.Both
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
updateDisplay();
|
||||
}
|
||||
|
||||
private void updateDisplay()
|
||||
{
|
||||
attributesFillFlow.Clear();
|
||||
|
||||
if (data != null)
|
||||
{
|
||||
attemptAdd("CS", bd => bd.CircleSize);
|
||||
attemptAdd("HP", bd => bd.DrainRate);
|
||||
attemptAdd("OD", bd => bd.OverallDifficulty);
|
||||
attemptAdd("AR", bd => bd.ApproachRate);
|
||||
}
|
||||
|
||||
if (attributesFillFlow.Any())
|
||||
content.Show();
|
||||
else
|
||||
content.Hide();
|
||||
|
||||
void attemptAdd(string name, Func<BeatmapDifficulty, double> lookup)
|
||||
{
|
||||
double originalValue = lookup(data.OriginalDifficulty);
|
||||
double adjustedValue = lookup(data.AdjustedDifficulty);
|
||||
|
||||
if (!Precision.AlmostEquals(originalValue, adjustedValue))
|
||||
attributesFillFlow.Add(new AttributeDisplay(name, originalValue, adjustedValue));
|
||||
}
|
||||
}
|
||||
|
||||
public void SetContent(Data? data)
|
||||
{
|
||||
if (this.data == data)
|
||||
return;
|
||||
|
||||
this.data = data;
|
||||
updateDisplay();
|
||||
}
|
||||
|
||||
protected override void PopIn() => this.FadeIn(200, Easing.OutQuint);
|
||||
protected override void PopOut() => this.FadeOut(200, Easing.OutQuint);
|
||||
|
||||
public void Move(Vector2 pos) => Position = pos;
|
||||
|
||||
public class Data
|
||||
{
|
||||
public BeatmapDifficulty OriginalDifficulty { get; }
|
||||
public BeatmapDifficulty AdjustedDifficulty { get; }
|
||||
|
||||
public Data(BeatmapDifficulty originalDifficulty, BeatmapDifficulty adjustedDifficulty)
|
||||
{
|
||||
OriginalDifficulty = originalDifficulty;
|
||||
AdjustedDifficulty = adjustedDifficulty;
|
||||
}
|
||||
}
|
||||
|
||||
private partial class AttributeDisplay : CompositeDrawable
|
||||
{
|
||||
public AttributeDisplay(string name, double original, double adjusted)
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
InternalChild = new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.Default.With(weight: FontWeight.Bold),
|
||||
Text = $"{name}: {original:0.0#} → {adjusted:0.0#}"
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -3,21 +3,23 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.LocalisationExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osuTK;
|
||||
using System.Threading;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Configuration;
|
||||
|
||||
namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
@ -25,7 +27,7 @@ namespace osu.Game.Overlays.Mods
|
||||
/// On the mod select overlay, this provides a local updating view of BPM, star rating and other
|
||||
/// difficulty attributes so the user can have a better insight into what mods are changing.
|
||||
/// </summary>
|
||||
public partial class BeatmapAttributesDisplay : ModFooterInformationDisplay
|
||||
public partial class BeatmapAttributesDisplay : ModFooterInformationDisplay, IHasCustomTooltip<AdjustedAttributesTooltip.Data?>
|
||||
{
|
||||
private StarRatingDisplay starRatingDisplay = null!;
|
||||
private BPMDisplay bpmDisplay = null!;
|
||||
@ -47,9 +49,18 @@ namespace osu.Game.Overlays.Mods
|
||||
[Resolved]
|
||||
private BeatmapDifficultyCache difficultyCache { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private OsuGameBase game { get; set; } = null!;
|
||||
|
||||
private IBindable<RulesetInfo> gameRuleset = null!;
|
||||
|
||||
private CancellationTokenSource? cancellationSource;
|
||||
private IBindable<StarDifficulty?> starDifficulty = null!;
|
||||
|
||||
public ITooltip<AdjustedAttributesTooltip.Data?> GetCustomTooltip() => new AdjustedAttributesTooltip();
|
||||
|
||||
public AdjustedAttributesTooltip.Data? TooltipContent { get; private set; }
|
||||
|
||||
private const float transition_duration = 250;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -80,8 +91,8 @@ namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
circleSizeDisplay = new VerticalAttributeDisplay("CS") { Shear = new Vector2(-shear, 0), },
|
||||
drainRateDisplay = new VerticalAttributeDisplay("HP") { Shear = new Vector2(-shear, 0), },
|
||||
approachRateDisplay = new VerticalAttributeDisplay("AR") { Shear = new Vector2(-shear, 0), },
|
||||
overallDifficultyDisplay = new VerticalAttributeDisplay("OD") { Shear = new Vector2(-shear, 0), },
|
||||
approachRateDisplay = new VerticalAttributeDisplay("AR") { Shear = new Vector2(-shear, 0), },
|
||||
});
|
||||
}
|
||||
|
||||
@ -92,7 +103,6 @@ namespace osu.Game.Overlays.Mods
|
||||
mods.BindValueChanged(_ =>
|
||||
{
|
||||
modSettingChangeTracker?.Dispose();
|
||||
|
||||
modSettingChangeTracker = new ModSettingChangeTracker(mods.Value);
|
||||
modSettingChangeTracker.SettingChanged += _ => updateValues();
|
||||
updateValues();
|
||||
@ -107,6 +117,11 @@ namespace osu.Game.Overlays.Mods
|
||||
updateCollapsedState();
|
||||
});
|
||||
|
||||
gameRuleset = game.Ruleset.GetBoundCopy();
|
||||
gameRuleset.BindValueChanged(_ => updateValues());
|
||||
|
||||
BeatmapInfo.BindValueChanged(_ => updateValues(), true);
|
||||
|
||||
updateCollapsedState();
|
||||
}
|
||||
|
||||
@ -129,13 +144,8 @@ namespace osu.Game.Overlays.Mods
|
||||
|
||||
private void startAnimating()
|
||||
{
|
||||
Content.AutoSizeEasing = Easing.OutQuint;
|
||||
Content.AutoSizeDuration = transition_duration;
|
||||
}
|
||||
|
||||
private void updateCollapsedState()
|
||||
{
|
||||
RightContent.FadeTo(Collapsed.Value && !IsHovered ? 0 : 1, transition_duration, Easing.OutQuint);
|
||||
LeftContent.AutoSizeEasing = Content.AutoSizeEasing = Easing.OutQuint;
|
||||
LeftContent.AutoSizeDuration = Content.AutoSizeDuration = transition_duration;
|
||||
}
|
||||
|
||||
private void updateValues() => Scheduler.AddOnce(() =>
|
||||
@ -160,9 +170,18 @@ namespace osu.Game.Overlays.Mods
|
||||
|
||||
bpmDisplay.Current.Value = BeatmapInfo.Value.BPM * rate;
|
||||
|
||||
BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(BeatmapInfo.Value.Difficulty);
|
||||
BeatmapDifficulty originalDifficulty = new BeatmapDifficulty(BeatmapInfo.Value.Difficulty);
|
||||
|
||||
foreach (var mod in mods.Value.OfType<IApplicableToDifficulty>())
|
||||
mod.ApplyToDifficulty(adjustedDifficulty);
|
||||
mod.ApplyToDifficulty(originalDifficulty);
|
||||
|
||||
Ruleset ruleset = gameRuleset.Value.CreateInstance();
|
||||
BeatmapDifficulty adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(originalDifficulty, rate);
|
||||
|
||||
TooltipContent = new AdjustedAttributesTooltip.Data(originalDifficulty, adjustedDifficulty);
|
||||
|
||||
approachRateDisplay.AdjustType.Value = VerticalAttributeDisplay.CalculateEffect(originalDifficulty.ApproachRate, adjustedDifficulty.ApproachRate);
|
||||
overallDifficultyDisplay.AdjustType.Value = VerticalAttributeDisplay.CalculateEffect(originalDifficulty.OverallDifficulty, adjustedDifficulty.OverallDifficulty);
|
||||
|
||||
circleSizeDisplay.Current.Value = adjustedDifficulty.CircleSize;
|
||||
drainRateDisplay.Current.Value = adjustedDifficulty.DrainRate;
|
||||
@ -170,6 +189,11 @@ namespace osu.Game.Overlays.Mods
|
||||
overallDifficultyDisplay.Current.Value = adjustedDifficulty.OverallDifficulty;
|
||||
});
|
||||
|
||||
private void updateCollapsedState()
|
||||
{
|
||||
RightContent.FadeTo(Collapsed.Value && !IsHovered ? 0 : 1, transition_duration, Easing.OutQuint);
|
||||
}
|
||||
|
||||
private partial class BPMDisplay : RollingCounter<double>
|
||||
{
|
||||
protected override double RollingDuration => 500;
|
||||
|
@ -115,6 +115,7 @@ namespace osu.Game.Overlays.Mods
|
||||
public IEnumerable<ModState> AllAvailableMods => AvailableMods.Value.SelectMany(pair => pair.Value);
|
||||
|
||||
private readonly BindableBool customisationVisible = new BindableBool();
|
||||
private Bindable<bool> textSearchStartsActive = null!;
|
||||
|
||||
private ModSettingsArea modSettingsArea = null!;
|
||||
private ColumnScrollContainer columnScroll = null!;
|
||||
@ -154,7 +155,7 @@ namespace osu.Game.Overlays.Mods
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuGameBase game, OsuColour colours, AudioManager audio)
|
||||
private void load(OsuGameBase game, OsuColour colours, AudioManager audio, OsuConfigManager configManager)
|
||||
{
|
||||
Header.Title = ModSelectOverlayStrings.ModSelectTitle;
|
||||
Header.Description = ModSelectOverlayStrings.ModSelectDescription;
|
||||
@ -282,6 +283,8 @@ namespace osu.Game.Overlays.Mods
|
||||
}
|
||||
|
||||
globalAvailableMods.BindTo(game.AvailableMods);
|
||||
|
||||
textSearchStartsActive = configManager.GetBindable<bool>(OsuSetting.ModSelectTextSearchStartsActive);
|
||||
}
|
||||
|
||||
public override void Hide()
|
||||
@ -617,6 +620,9 @@ namespace osu.Game.Overlays.Mods
|
||||
|
||||
nonFilteredColumnCount += 1;
|
||||
}
|
||||
|
||||
if (textSearchStartsActive.Value)
|
||||
SearchTextBox.TakeFocus();
|
||||
}
|
||||
|
||||
protected override void PopOut()
|
||||
|
@ -1,15 +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;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.LocalisationExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
@ -23,11 +28,45 @@ namespace osu.Game.Overlays.Mods
|
||||
|
||||
private readonly BindableWithCurrent<double> current = new BindableWithCurrent<double>();
|
||||
|
||||
public Bindable<ModEffect> AdjustType = new Bindable<ModEffect>();
|
||||
|
||||
/// <summary>
|
||||
/// Text to display in the top area of the display.
|
||||
/// </summary>
|
||||
public LocalisableString Label { get; protected set; }
|
||||
|
||||
private readonly EffectCounter counter;
|
||||
private readonly OsuSpriteText text;
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
private void updateTextColor()
|
||||
{
|
||||
Color4 newColor;
|
||||
|
||||
switch (AdjustType.Value)
|
||||
{
|
||||
case ModEffect.NotChanged:
|
||||
newColor = Color4.White;
|
||||
break;
|
||||
|
||||
case ModEffect.DifficultyReduction:
|
||||
newColor = colours.ForModType(ModType.DifficultyReduction);
|
||||
break;
|
||||
|
||||
case ModEffect.DifficultyIncrease:
|
||||
newColor = colours.ForModType(ModType.DifficultyIncrease);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(AdjustType.Value));
|
||||
}
|
||||
|
||||
text.Colour = newColor;
|
||||
counter.Colour = newColor;
|
||||
}
|
||||
|
||||
public VerticalAttributeDisplay(LocalisableString label)
|
||||
{
|
||||
Label = label;
|
||||
@ -37,15 +76,18 @@ namespace osu.Game.Overlays.Mods
|
||||
Origin = Anchor.CentreLeft;
|
||||
Anchor = Anchor.CentreLeft;
|
||||
|
||||
AdjustType.BindValueChanged(_ => updateTextColor());
|
||||
|
||||
InternalChild = new FillFlowContainer
|
||||
{
|
||||
Origin = Anchor.CentreLeft,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Width = 50,
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
text = new OsuSpriteText
|
||||
{
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
@ -53,7 +95,7 @@ namespace osu.Game.Overlays.Mods
|
||||
Margin = new MarginPadding { Horizontal = 15 }, // to reserve space for 0.XX value
|
||||
Font = OsuFont.Default.With(size: 20, weight: FontWeight.Bold)
|
||||
},
|
||||
new EffectCounter
|
||||
counter = new EffectCounter
|
||||
{
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
@ -63,11 +105,28 @@ namespace osu.Game.Overlays.Mods
|
||||
};
|
||||
}
|
||||
|
||||
public static ModEffect CalculateEffect(double oldValue, double newValue)
|
||||
{
|
||||
if (Precision.AlmostEquals(newValue, oldValue, 0.01))
|
||||
return ModEffect.NotChanged;
|
||||
if (newValue < oldValue)
|
||||
return ModEffect.DifficultyReduction;
|
||||
|
||||
return ModEffect.DifficultyIncrease;
|
||||
}
|
||||
|
||||
public enum ModEffect
|
||||
{
|
||||
NotChanged,
|
||||
DifficultyReduction,
|
||||
DifficultyIncrease,
|
||||
}
|
||||
|
||||
private partial class EffectCounter : RollingCounter<double>
|
||||
{
|
||||
protected override double RollingDuration => 500;
|
||||
|
||||
protected override LocalisableString FormatCount(double count) => count.ToLocalisableString("0.0");
|
||||
protected override LocalisableString FormatCount(double count) => count.ToLocalisableString("0.0#");
|
||||
|
||||
protected override OsuSpriteText CreateSpriteText() => new OsuSpriteText
|
||||
{
|
||||
|
@ -23,6 +23,7 @@ namespace osu.Game.Overlays.Settings.Sections.General
|
||||
{
|
||||
LabelText = GeneralSettingsStrings.LanguageDropdown,
|
||||
Current = game.CurrentLanguage,
|
||||
AlwaysShowSearchBar = true,
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
|
@ -1,14 +1,13 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Configuration;
|
||||
@ -16,23 +15,27 @@ using osu.Game.Localisation;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using osu.Game.Overlays.Settings.Sections.Maintenance;
|
||||
using osu.Game.Updater;
|
||||
using SharpCompress.Archives.Zip;
|
||||
|
||||
namespace osu.Game.Overlays.Settings.Sections.General
|
||||
{
|
||||
public partial class UpdateSettings : SettingsSubsection
|
||||
{
|
||||
[Resolved(CanBeNull = true)]
|
||||
private UpdateManager updateManager { get; set; }
|
||||
|
||||
protected override LocalisableString Header => GeneralSettingsStrings.UpdateHeader;
|
||||
|
||||
private SettingsButton checkForUpdatesButton;
|
||||
private SettingsButton checkForUpdatesButton = null!;
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
private INotificationOverlay notifications { get; set; }
|
||||
[Resolved]
|
||||
private UpdateManager? updateManager { get; set; }
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(Storage storage, OsuConfigManager config, OsuGame game)
|
||||
[Resolved]
|
||||
private INotificationOverlay? notifications { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private Storage storage { get; set; } = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuConfigManager config, OsuGame? game)
|
||||
{
|
||||
Add(new SettingsEnumDropdown<ReleaseStream>
|
||||
{
|
||||
@ -54,7 +57,7 @@ namespace osu.Game.Overlays.Settings.Sections.General
|
||||
{
|
||||
notifications?.Post(new SimpleNotification
|
||||
{
|
||||
Text = GeneralSettingsStrings.RunningLatestRelease(game.Version),
|
||||
Text = GeneralSettingsStrings.RunningLatestRelease(game!.Version),
|
||||
Icon = FontAwesome.Solid.CheckCircle,
|
||||
});
|
||||
}
|
||||
@ -74,6 +77,13 @@ namespace osu.Game.Overlays.Settings.Sections.General
|
||||
Action = () => storage.PresentExternally(),
|
||||
});
|
||||
|
||||
Add(new SettingsButton
|
||||
{
|
||||
Text = GeneralSettingsStrings.ExportLogs,
|
||||
Keywords = new[] { @"bug", "report", "logs", "files" },
|
||||
Action = () => Task.Run(exportLogs),
|
||||
});
|
||||
|
||||
Add(new SettingsButton
|
||||
{
|
||||
Text = GeneralSettingsStrings.ChangeFolderLocation,
|
||||
@ -81,5 +91,44 @@ namespace osu.Game.Overlays.Settings.Sections.General
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void exportLogs()
|
||||
{
|
||||
ProgressNotification notification = new ProgressNotification
|
||||
{
|
||||
State = ProgressNotificationState.Active,
|
||||
Text = "Exporting logs...",
|
||||
};
|
||||
|
||||
notifications?.Post(notification);
|
||||
|
||||
const string archive_filename = "exports/compressed-logs.zip";
|
||||
|
||||
try
|
||||
{
|
||||
var logStorage = Logger.Storage;
|
||||
|
||||
using (var outStream = storage.CreateFileSafely(archive_filename))
|
||||
using (var zip = ZipArchive.Create())
|
||||
{
|
||||
foreach (string? f in logStorage.GetFiles(string.Empty, "*.log")) zip.AddEntry(f, logStorage.GetStream(f), true);
|
||||
|
||||
zip.SaveTo(outStream);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
notification.State = ProgressNotificationState.Cancelled;
|
||||
|
||||
// cleanup if export is failed or canceled.
|
||||
storage.Delete(archive_filename);
|
||||
throw;
|
||||
}
|
||||
|
||||
notification.CompletionText = "Exported logs! Click to view.";
|
||||
notification.CompletionClickAction = () => storage.PresentFileExternally(archive_filename);
|
||||
|
||||
notification.State = ProgressNotificationState.Completed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,13 @@
|
||||
// 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.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Database;
|
||||
|
||||
namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
||||
{
|
||||
@ -13,9 +15,12 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
||||
{
|
||||
private readonly TaskCompletionSource<string> taskCompletionSource;
|
||||
|
||||
[Resolved]
|
||||
private LegacyImportManager legacyImportManager { get; set; } = null!;
|
||||
|
||||
protected override OverlayActivation InitialOverlayActivationMode => OverlayActivation.Disabled;
|
||||
|
||||
protected override bool IsValidDirectory(DirectoryInfo? info) => info?.GetFiles("osu!.*.cfg").Any() ?? false;
|
||||
protected override bool IsValidDirectory(DirectoryInfo? info) => legacyImportManager.IsUsableForStableImport(info, out _);
|
||||
|
||||
public override LocalisableString HeaderText => "Please select your osu!stable install location";
|
||||
|
||||
@ -26,7 +31,10 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
||||
|
||||
protected override void OnSelection(DirectoryInfo directory)
|
||||
{
|
||||
taskCompletionSource.TrySetResult(directory.FullName);
|
||||
if (!legacyImportManager.IsUsableForStableImport(directory, out var stableRoot))
|
||||
throw new InvalidOperationException($@"{nameof(OnSelection)} was called on an invalid directory. This should never happen.");
|
||||
|
||||
taskCompletionSource.TrySetResult(stableRoot.FullName);
|
||||
this.Exit();
|
||||
}
|
||||
|
||||
|
@ -42,6 +42,12 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
|
||||
ClassicDefault = ModSelectHotkeyStyle.Classic
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = UserInterfaceStrings.ModSelectTextSearchStartsActive,
|
||||
Current = config.GetBindable<bool>(OsuSetting.ModSelectTextSearchStartsActive),
|
||||
ClassicDefault = false
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = GameplaySettingsStrings.BackgroundBlur,
|
||||
Current = config.GetBindable<bool>(OsuSetting.SongSelectBackgroundBlur),
|
||||
|
@ -17,6 +17,7 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens;
|
||||
@ -58,6 +59,9 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
[Resolved]
|
||||
private Bindable<IReadOnlyList<Mod>> mods { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private Bindable<RulesetInfo> ruleset { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private IBindable<WorkingBeatmap> beatmap { get; set; } = null!;
|
||||
|
||||
@ -153,7 +157,11 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
if (screen is Player)
|
||||
return;
|
||||
|
||||
var replayGeneratingMod = beatmap.Value.BeatmapInfo.Ruleset.CreateInstance().GetAutoplayMod();
|
||||
// the validity of the current game-wide beatmap + ruleset combination is enforced by song select.
|
||||
// if we're anywhere else, the state is unknown and may not make sense, so forcibly set something that does.
|
||||
if (screen is not PlaySongSelect)
|
||||
ruleset.Value = beatmap.Value.BeatmapInfo.Ruleset;
|
||||
var replayGeneratingMod = ruleset.Value.CreateInstance().GetAutoplayMod();
|
||||
|
||||
IReadOnlyList<Mod> usableMods = mods.Value;
|
||||
|
||||
|
@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Mods
|
||||
|
||||
public override string Acronym => "CL";
|
||||
|
||||
public override double ScoreMultiplier => 1;
|
||||
public override double ScoreMultiplier => 0.5;
|
||||
|
||||
public override IconUsage? Icon => FontAwesome.Solid.History;
|
||||
|
||||
|
@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Mods
|
||||
public override string Name => "Synesthesia";
|
||||
public override string Acronym => "SY";
|
||||
public override LocalisableString Description => "Colours hit objects based on the rhythm.";
|
||||
public override double ScoreMultiplier => 1;
|
||||
public override double ScoreMultiplier => 0.8;
|
||||
public override ModType Type => ModType.Fun;
|
||||
}
|
||||
}
|
||||
|
@ -32,9 +32,9 @@ namespace osu.Game.Rulesets.Mods
|
||||
value -= 1;
|
||||
|
||||
if (SpeedChange.Value >= 1)
|
||||
value /= 5;
|
||||
|
||||
return 1 + value;
|
||||
return 1 + value / 5;
|
||||
else
|
||||
return 0.6 + value;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -377,6 +377,17 @@ namespace osu.Game.Rulesets
|
||||
/// <returns>The display name.</returns>
|
||||
public virtual LocalisableString GetDisplayNameForHitResult(HitResult result) => result.GetLocalisableDescription();
|
||||
|
||||
/// <summary>
|
||||
/// Applies changes to difficulty attributes for presenting to a user a rough estimate of how rate adjust mods affect difficulty.
|
||||
/// Importantly, this should NOT BE USED FOR ANY CALCULATIONS.
|
||||
///
|
||||
/// It is also not always correct, and arguably is never correct depending on your frame of mind.
|
||||
/// </summary>
|
||||
/// <param name="difficulty">>The <see cref="IBeatmapDifficultyInfo"/> that will be adjusted.</param>
|
||||
/// <param name="rate">The rate adjustment multiplier from mods. For example 1.5 for DT.</param>
|
||||
/// <returns>The adjusted difficulty attributes.</returns>
|
||||
public virtual BeatmapDifficulty GetRateAdjustedDisplayDifficulty(IBeatmapDifficultyInfo difficulty, double rate) => new BeatmapDifficulty(difficulty);
|
||||
|
||||
/// <summary>
|
||||
/// Creates ruleset-specific beatmap filter criteria to be used on the song select screen.
|
||||
/// </summary>
|
||||
|
@ -139,9 +139,12 @@ namespace osu.Game.Rulesets.Scoring
|
||||
|
||||
/// <summary>
|
||||
/// A special result used as a padding value for legacy rulesets. It is a hit type and affects combo, but does not affect the base score (does not affect accuracy).
|
||||
///
|
||||
/// DO NOT USE FOR ANYTHING EVER.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// DO NOT USE.
|
||||
/// This is used when dealing with legacy scores, which historically only have counts stored for 300/100/50/miss.
|
||||
/// For these scores, we pad the hit statistics with `LegacyComboIncrease` to meet the correct max combo for the score.
|
||||
/// </remarks>
|
||||
[EnumMember(Value = "legacy_combo_increase")]
|
||||
[Order(99)]
|
||||
|
@ -62,8 +62,8 @@ namespace osu.Game.Rulesets.Scoring.Legacy
|
||||
SourceRuleset = beatmapInfo.Ruleset,
|
||||
CircleSize = beatmapInfo.Difficulty.CircleSize,
|
||||
OverallDifficulty = beatmapInfo.Difficulty.OverallDifficulty,
|
||||
EndTimeObjectCount = beatmapInfo.Difficulty.EndTimeObjectCount,
|
||||
TotalObjectCount = beatmapInfo.Difficulty.TotalObjectCount
|
||||
EndTimeObjectCount = beatmapInfo.EndTimeObjectCount,
|
||||
TotalObjectCount = beatmapInfo.TotalObjectCount
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ namespace osu.Game.Screens.Play
|
||||
/// Encapsulates gameplay timing logic and provides a <see cref="IGameplayClock"/> via DI for gameplay components to use.
|
||||
/// </summary>
|
||||
[Cached(typeof(IGameplayClock))]
|
||||
[Cached(typeof(GameplayClockContainer))]
|
||||
public partial class GameplayClockContainer : Container, IAdjustableClock, IGameplayClock
|
||||
{
|
||||
public IBindable<bool> IsPaused => isPaused;
|
||||
|
@ -212,6 +212,13 @@ namespace osu.Game.Screens.Play.HUD
|
||||
glowBar.Alpha = (float)Interpolation.DampContinuously(glowBar.Alpha, GlowBarValue > 0 ? 1 : 0, 40, Time.Elapsed);
|
||||
}
|
||||
|
||||
protected override void FinishInitialAnimation(double value)
|
||||
{
|
||||
base.FinishInitialAnimation(value);
|
||||
this.TransformTo(nameof(HealthBarValue), value, 500, Easing.OutQuint);
|
||||
this.TransformTo(nameof(GlowBarValue), value, 250, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override void Flash()
|
||||
{
|
||||
base.Flash();
|
||||
|
@ -58,7 +58,9 @@ namespace osu.Game.Screens.Play.HUD
|
||||
health = HealthProcessor.Health.GetBoundCopy();
|
||||
health.BindValueChanged(h =>
|
||||
{
|
||||
finishInitialAnimation();
|
||||
if (initialIncrease != null)
|
||||
FinishInitialAnimation(h.OldValue);
|
||||
|
||||
Current.Value = h.NewValue;
|
||||
});
|
||||
|
||||
@ -90,16 +92,16 @@ namespace osu.Game.Screens.Play.HUD
|
||||
Scheduler.AddOnce(Flash);
|
||||
|
||||
if (newValue >= health.Value)
|
||||
finishInitialAnimation();
|
||||
FinishInitialAnimation(health.Value);
|
||||
}, increase_delay, true);
|
||||
}
|
||||
|
||||
private void finishInitialAnimation()
|
||||
protected virtual void FinishInitialAnimation(double value)
|
||||
{
|
||||
if (initialIncrease == null)
|
||||
return;
|
||||
|
||||
initialIncrease?.Cancel();
|
||||
initialIncrease.Cancel();
|
||||
initialIncrease = null;
|
||||
|
||||
// aside from the repeating `initialIncrease` scheduled task,
|
||||
|
@ -485,7 +485,14 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
|
||||
}
|
||||
}
|
||||
|
||||
public override void Clear() => judgementsContainer.Clear();
|
||||
public override void Clear()
|
||||
{
|
||||
foreach (var j in judgementsContainer)
|
||||
{
|
||||
j.ClearTransforms();
|
||||
j.Expire();
|
||||
}
|
||||
}
|
||||
|
||||
public enum CentreMarkerStyles
|
||||
{
|
||||
|
@ -63,7 +63,14 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
|
||||
judgementsFlow.Push(GetColourForHitResult(judgement.Type));
|
||||
}
|
||||
|
||||
public override void Clear() => judgementsFlow.Clear();
|
||||
public override void Clear()
|
||||
{
|
||||
foreach (var j in judgementsFlow)
|
||||
{
|
||||
j.ClearTransforms();
|
||||
j.Expire();
|
||||
}
|
||||
}
|
||||
|
||||
private partial class JudgementFlow : FillFlowContainer<HitErrorShape>
|
||||
{
|
||||
|
@ -25,6 +25,8 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
private readonly Score score;
|
||||
|
||||
public override bool AllowBackButton => true;
|
||||
|
||||
protected override bool CheckModsAllowFailure()
|
||||
{
|
||||
if (!allowFail)
|
||||
|
@ -245,6 +245,9 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
|
||||
private void updateKeyCount()
|
||||
{
|
||||
if (Item?.State.Value == CarouselItemState.Collapsed)
|
||||
return;
|
||||
|
||||
if (ruleset.Value.OnlineID == 3)
|
||||
{
|
||||
// Account for mania differences locally for now.
|
||||
|
@ -8,6 +8,7 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
@ -25,10 +26,11 @@ using osu.Framework.Utils;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Overlays.Mods;
|
||||
|
||||
namespace osu.Game.Screens.Select.Details
|
||||
{
|
||||
public partial class AdvancedStats : Container
|
||||
public partial class AdvancedStats : Container, IHasCustomTooltip<AdjustedAttributesTooltip.Data>
|
||||
{
|
||||
[Resolved]
|
||||
private BeatmapDifficultyCache difficultyCache { get; set; }
|
||||
@ -44,6 +46,9 @@ namespace osu.Game.Screens.Select.Details
|
||||
protected readonly StatisticRow FirstValue, HpDrain, Accuracy, ApproachRate;
|
||||
private readonly StatisticRow starDifficulty;
|
||||
|
||||
public ITooltip<AdjustedAttributesTooltip.Data> GetCustomTooltip() => new AdjustedAttributesTooltip();
|
||||
public AdjustedAttributesTooltip.Data TooltipContent { get; private set; }
|
||||
|
||||
private IBeatmapInfo beatmapInfo;
|
||||
|
||||
public IBeatmapInfo BeatmapInfo
|
||||
@ -118,15 +123,28 @@ namespace osu.Game.Screens.Select.Details
|
||||
IBeatmapDifficultyInfo baseDifficulty = BeatmapInfo?.Difficulty;
|
||||
BeatmapDifficulty adjustedDifficulty = null;
|
||||
|
||||
if (baseDifficulty != null && mods.Value.Any(m => m is IApplicableToDifficulty))
|
||||
IRulesetInfo ruleset = gameRuleset?.Value ?? beatmapInfo.Ruleset;
|
||||
|
||||
if (baseDifficulty != null)
|
||||
{
|
||||
adjustedDifficulty = new BeatmapDifficulty(baseDifficulty);
|
||||
BeatmapDifficulty originalDifficulty = new BeatmapDifficulty(baseDifficulty);
|
||||
|
||||
foreach (var mod in mods.Value.OfType<IApplicableToDifficulty>())
|
||||
mod.ApplyToDifficulty(adjustedDifficulty);
|
||||
}
|
||||
mod.ApplyToDifficulty(originalDifficulty);
|
||||
|
||||
IRulesetInfo ruleset = gameRuleset?.Value ?? beatmapInfo.Ruleset;
|
||||
adjustedDifficulty = originalDifficulty;
|
||||
|
||||
if (gameRuleset != null)
|
||||
{
|
||||
double rate = 1;
|
||||
foreach (var mod in mods.Value.OfType<IApplicableToRate>())
|
||||
rate = mod.ApplyToRate(0, rate);
|
||||
|
||||
adjustedDifficulty = ruleset.CreateInstance().GetRateAdjustedDisplayDifficulty(originalDifficulty, rate);
|
||||
|
||||
TooltipContent = new AdjustedAttributesTooltip.Data(originalDifficulty, adjustedDifficulty);
|
||||
}
|
||||
}
|
||||
|
||||
switch (ruleset.OnlineID)
|
||||
{
|
||||
|
@ -37,7 +37,7 @@
|
||||
</PackageReference>
|
||||
<PackageReference Include="Realm" Version="11.5.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2023.1213.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2023.1127.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2023.1215.0" />
|
||||
<PackageReference Include="Sentry" Version="3.40.0" />
|
||||
<!-- Held back due to 0.34.0 failing AOT compilation on ZstdSharp.dll dependency. -->
|
||||
<PackageReference Include="SharpCompress" Version="0.33.0" />
|
||||
|
Loading…
Reference in New Issue
Block a user