mirror of
https://github.com/ppy/osu.git
synced 2026-05-19 01:49:53 +08:00
Compare commits
571 Commits
@@ -11,6 +11,10 @@ body:
|
||||
- Current open `priority:0` issues, filterable [here](https://github.com/ppy/osu/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3Apriority%3A0).
|
||||
- And most importantly, search for your issue both in the [issue listing](https://github.com/ppy/osu/issues) and the [Q&A discussion listing](https://github.com/ppy/osu/discussions/categories/q-a). If you find that it already exists, respond with a reaction or add any further information that may be helpful.
|
||||
|
||||
# ATTENTION LINUX USERS
|
||||
|
||||
If you are having an issue and it is hardware related, **please open a [q&a discussion](https://github.com/ppy/osu/discussions/categories/q-a)** instead of an issue. There's a high chance your issue is due to your system configuration, and not our software.
|
||||
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: Type
|
||||
@@ -46,22 +50,16 @@ body:
|
||||
value: |
|
||||
## Logs
|
||||
|
||||
Attaching log files is required for every reported bug. See instructions below on how to find them.
|
||||
|
||||
**Logs are reset when you reopen the game.** If the game crashed or has been closed since you found the bug, retrieve the logs using the file explorer instead.
|
||||
Attaching log files is required for **every** issue, regardless of whether you deem them required or not. See instructions below on how to find them.
|
||||
|
||||
### Desktop platforms
|
||||
|
||||
If the game has not yet been closed since you found the bug:
|
||||
1. Head on to game settings and click on "Open osu! folder"
|
||||
2. Then open the `logs` folder located there
|
||||
1. Head on to game settings and click on "Export logs"
|
||||
2. Click the notification to locate the file
|
||||
3. Drag the generated `.zip` files into the github issue window
|
||||
|
||||
The default places to find the logs on desktop platforms are as follows:
|
||||
- `%AppData%/osu/logs` *on Windows*
|
||||
- `~/.local/share/osu/logs` *on Linux*
|
||||
- `~/Library/Application Support/osu/logs` *on macOS*
|
||||
|
||||
If you have selected a custom location for the game files, you can find the `logs` folder there.
|
||||

|
||||
|
||||
### Mobile platforms
|
||||
|
||||
@@ -69,10 +67,6 @@ body:
|
||||
- *On Android*, navigate to `Android/data/sh.ppy.osulazer/files/logs` using a file browser app.
|
||||
- *On iOS*, connect your device to a PC and copy the `logs` directory from the app's document storage using iTunes. (https://support.apple.com/en-us/HT201301#copy-to-computer)
|
||||
|
||||
---
|
||||
|
||||
After locating the `logs` folder, select all log files inside and drag them into the "Logs" box below.
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Logs
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2023.1201.1" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2023.1227.1" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Tests.Mods
|
||||
{
|
||||
public partial class TestSceneCatchModPerfect : ModPerfectTestScene
|
||||
public partial class TestSceneCatchModPerfect : ModFailConditionTestScene
|
||||
{
|
||||
protected override Ruleset CreatePlayerRuleset() => new CatchRuleset();
|
||||
|
||||
|
||||
+1
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
|
||||
@@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
|
||||
AddAssert("First note is hyperdash", () => Beatmap.Value.Beatmap.HitObjects[0] is Fruit f && f.HyperDash);
|
||||
|
||||
for (int i = 0; i < 9; i++)
|
||||
for (int i = 0; i < 11; i++)
|
||||
{
|
||||
int count = i + 1;
|
||||
AddUntilStep($"wait for hyperdash #{count}", () => hyperDashCount >= count);
|
||||
@@ -104,12 +104,22 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
})
|
||||
}, 1);
|
||||
|
||||
createObjects(() => new Fruit { X = right_x }, count: 2, spacing: 0, spacingAfterGroup: 400);
|
||||
createObjects(() => new TestJuiceStream(left_x)
|
||||
{
|
||||
Path = new SliderPath(new[]
|
||||
{
|
||||
new PathControlPoint(Vector2.Zero),
|
||||
new PathControlPoint(new Vector2(0, 300))
|
||||
})
|
||||
}, count: 1, spacingAfterGroup: 150);
|
||||
createObjects(() => new Fruit { X = left_x }, count: 1, spacing: 0, spacingAfterGroup: 400);
|
||||
createObjects(() => new Fruit { X = right_x }, count: 2, spacing: 0);
|
||||
|
||||
return beatmap;
|
||||
|
||||
void createObjects(Func<CatchHitObject> createObject, int count = 3)
|
||||
void createObjects(Func<CatchHitObject> createObject, int count = 3, float spacing = 140, float spacingAfterGroup = 700)
|
||||
{
|
||||
const float spacing = 140;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var hitObject = createObject();
|
||||
@@ -117,7 +127,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
beatmap.HitObjects.Add(hitObject);
|
||||
}
|
||||
|
||||
startTime += 700;
|
||||
startTime += spacingAfterGroup;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Tests
|
||||
{
|
||||
public partial class TestSceneOutOfBoundsObjects : TestSceneCatchPlayer
|
||||
{
|
||||
protected override bool Autoplay => true;
|
||||
|
||||
[Test]
|
||||
public void TestNoOutOfBoundsObjects()
|
||||
{
|
||||
bool anyObjectOutOfBounds = false;
|
||||
|
||||
AddStep("reset flag", () => anyObjectOutOfBounds = false);
|
||||
|
||||
AddUntilStep("check for out-of-bounds objects",
|
||||
() =>
|
||||
{
|
||||
anyObjectOutOfBounds |= Player.ChildrenOfType<DrawableCatchHitObject>().Any(dho => dho.X < 0 || dho.X > CatchPlayfield.WIDTH);
|
||||
return Player.ScoreProcessor.HasCompleted.Value;
|
||||
});
|
||||
|
||||
AddAssert("no out of bound objects found", () => !anyObjectOutOfBounds);
|
||||
}
|
||||
|
||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap
|
||||
{
|
||||
BeatmapInfo = new BeatmapInfo
|
||||
{
|
||||
Ruleset = ruleset,
|
||||
},
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new Fruit { StartTime = 1000, X = -50 },
|
||||
new Fruit { StartTime = 1200, X = CatchPlayfield.WIDTH + 50 },
|
||||
new JuiceStream
|
||||
{
|
||||
StartTime = 1500,
|
||||
X = 10,
|
||||
Path = new SliderPath(PathType.LINEAR, new[]
|
||||
{
|
||||
Vector2.Zero,
|
||||
new Vector2(-200, 0)
|
||||
})
|
||||
},
|
||||
new JuiceStream
|
||||
{
|
||||
StartTime = 3000,
|
||||
X = CatchPlayfield.WIDTH - 10,
|
||||
Path = new SliderPath(PathType.LINEAR, new[]
|
||||
{
|
||||
Vector2.Zero,
|
||||
new Vector2(200, 0)
|
||||
})
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||
{
|
||||
@@ -38,5 +39,25 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumerate all <see cref="PalpableCatchHitObject"/>s, sorted by their start times.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If multiple objects have the same start time, the ordering is preserved (it is a stable sorting).
|
||||
/// </remarks>
|
||||
public static IEnumerable<PalpableCatchHitObject> GetPalpableObjects(IEnumerable<HitObject> hitObjects)
|
||||
{
|
||||
return hitObjects.SelectMany(selectPalpableObjects).OrderBy(h => h.StartTime);
|
||||
|
||||
IEnumerable<PalpableCatchHitObject> selectPalpableObjects(HitObject h)
|
||||
{
|
||||
if (h is PalpableCatchHitObject palpable)
|
||||
yield return palpable;
|
||||
|
||||
foreach (var nested in h.NestedHitObjects.OfType<PalpableCatchHitObject>())
|
||||
yield return nested;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
@@ -208,24 +207,9 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||
|
||||
private static void initialiseHyperDash(IBeatmap beatmap)
|
||||
{
|
||||
List<PalpableCatchHitObject> palpableObjects = new List<PalpableCatchHitObject>();
|
||||
|
||||
foreach (var currentObject in beatmap.HitObjects)
|
||||
{
|
||||
if (currentObject is Fruit fruitObject)
|
||||
palpableObjects.Add(fruitObject);
|
||||
|
||||
if (currentObject is JuiceStream)
|
||||
{
|
||||
foreach (var juice in currentObject.NestedHitObjects)
|
||||
{
|
||||
if (juice is PalpableCatchHitObject palpableObject && !(juice is TinyDroplet))
|
||||
palpableObjects.Add(palpableObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
palpableObjects.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime));
|
||||
var palpableObjects = CatchBeatmap.GetPalpableObjects(beatmap.HitObjects)
|
||||
.Where(h => h is Fruit || (h is Droplet && h is not TinyDroplet))
|
||||
.ToArray();
|
||||
|
||||
double halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.Difficulty) / 2;
|
||||
|
||||
@@ -237,7 +221,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||
int lastDirection = 0;
|
||||
double lastExcess = halfCatcherWidth;
|
||||
|
||||
for (int i = 0; i < palpableObjects.Count - 1; i++)
|
||||
for (int i = 0; i < palpableObjects.Length - 1; i++)
|
||||
{
|
||||
var currentObject = palpableObjects[i];
|
||||
var nextObject = palpableObjects[i + 1];
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Catch.Beatmaps;
|
||||
using osu.Game.Rulesets.Catch.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Catch.Difficulty.Skills;
|
||||
using osu.Game.Rulesets.Catch.Mods;
|
||||
@@ -56,13 +57,10 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
List<DifficultyHitObject> objects = new List<DifficultyHitObject>();
|
||||
|
||||
// In 2B beatmaps, it is possible that a normal Fruit is placed in the middle of a JuiceStream.
|
||||
foreach (var hitObject in beatmap.HitObjects
|
||||
.SelectMany(obj => obj is JuiceStream stream ? stream.NestedHitObjects.AsEnumerable() : new[] { obj })
|
||||
.Cast<CatchHitObject>()
|
||||
.OrderBy(x => x.StartTime))
|
||||
foreach (var hitObject in CatchBeatmap.GetPalpableObjects(beatmap.HitObjects))
|
||||
{
|
||||
// We want to only consider fruits that contribute to the combo.
|
||||
if (hitObject is BananaShower || hitObject is TinyDroplet)
|
||||
if (hitObject is Banana || hitObject is TinyDroplet)
|
||||
continue;
|
||||
|
||||
if (lastObject != null)
|
||||
|
||||
@@ -7,7 +7,7 @@ using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Catch.Mods;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Catch.Scoring;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
@@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
{
|
||||
internal class CatchLegacyScoreSimulator : ILegacyScoreSimulator
|
||||
{
|
||||
private readonly ScoreProcessor scoreProcessor = new CatchScoreProcessor();
|
||||
|
||||
private int legacyBonusScore;
|
||||
private int standardisedBonusScore;
|
||||
private int combo;
|
||||
@@ -74,6 +76,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
simulateHit(obj, ref attributes);
|
||||
|
||||
attributes.BonusScoreRatio = legacyBonusScore == 0 ? 0 : (double)standardisedBonusScore / legacyBonusScore;
|
||||
attributes.BonusScore = legacyBonusScore;
|
||||
attributes.MaxCombo = combo;
|
||||
|
||||
return attributes;
|
||||
}
|
||||
@@ -132,7 +136,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
if (isBonus)
|
||||
{
|
||||
legacyBonusScore += scoreIncrease;
|
||||
standardisedBonusScore += Judgement.ToNumericResult(bonusResult);
|
||||
standardisedBonusScore += scoreProcessor.GetBaseScoreForResult(bonusResult);
|
||||
}
|
||||
else
|
||||
attributes.AccuracyScore += scoreIncrease;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Rulesets.Catch.Edit.Blueprints.Components;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
@@ -17,7 +18,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
||||
private double placementStartTime;
|
||||
private double placementEndTime;
|
||||
|
||||
protected override bool IsValidForPlacement => HitObject.Duration > 0;
|
||||
protected override bool IsValidForPlacement => Precision.DefinitelyBigger(HitObject.Duration, 0);
|
||||
|
||||
public BananaShowerPlacementBlueprint()
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Rulesets.Catch.Edit.Blueprints.Components;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
@@ -24,7 +25,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
||||
|
||||
private InputManager inputManager = null!;
|
||||
|
||||
protected override bool IsValidForPlacement => HitObject.Duration > 0;
|
||||
protected override bool IsValidForPlacement => Precision.DefinitelyBigger(HitObject.Duration, 0);
|
||||
|
||||
public JuiceStreamPlacementBlueprint()
|
||||
{
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
// 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.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
@@ -70,7 +72,10 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
|
||||
private void updateXPosition(ValueChangedEvent<float> _)
|
||||
{
|
||||
X = OriginalXBindable.Value + XOffsetBindable.Value;
|
||||
// same as `CatchHitObject.EffectiveX`.
|
||||
// not using that property directly to support scenarios where `HitObject` may not necessarily be present
|
||||
// for this pooled drawable.
|
||||
X = Math.Clamp(OriginalXBindable.Value + XOffsetBindable.Value, 0, CatchPlayfield.WIDTH);
|
||||
}
|
||||
|
||||
protected override void OnApply()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Catch.Scoring
|
||||
}
|
||||
|
||||
protected override double GetComboScoreChange(JudgementResult result)
|
||||
=> Judgement.ToNumericResult(result.Type) * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(combo_cap, combo_base));
|
||||
=> GetBaseScoreForResult(result.Type) * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(combo_cap, combo_base));
|
||||
|
||||
public override ScoreRank RankFromAccuracy(double accuracy)
|
||||
{
|
||||
|
||||
@@ -126,6 +126,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
|
||||
private Color4 hyperDashColour = DEFAULT_HYPER_DASH_COLOUR;
|
||||
|
||||
private double? lastHyperDashStartTime;
|
||||
private double hyperDashModifier = 1;
|
||||
private int hyperDashDirection;
|
||||
private float hyperDashTargetPosition;
|
||||
@@ -233,16 +234,23 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
// droplet doesn't affect the catcher state
|
||||
if (hitObject is TinyDroplet) return;
|
||||
|
||||
if (result.IsHit && hitObject.HyperDashTarget is CatchHitObject target)
|
||||
// if a hyper fruit was already handled this frame, just go where it says to go.
|
||||
// this special-cases some aspire maps that have doubled-up objects (one hyper, one not) at the same time instant.
|
||||
// handling this "properly" elsewhere is impossible as there is no feasible way to ensure
|
||||
// that the hyperfruit gets judged second (especially if it coincides with a last fruit in a juice stream).
|
||||
if (lastHyperDashStartTime != Time.Current)
|
||||
{
|
||||
double timeDifference = target.StartTime - hitObject.StartTime;
|
||||
double positionDifference = target.EffectiveX - X;
|
||||
double velocity = positionDifference / Math.Max(1.0, timeDifference - 1000.0 / 60.0);
|
||||
if (result.IsHit && hitObject.HyperDashTarget is CatchHitObject target)
|
||||
{
|
||||
double timeDifference = target.StartTime - hitObject.StartTime;
|
||||
double positionDifference = target.EffectiveX - X;
|
||||
double velocity = positionDifference / Math.Max(1.0, timeDifference - 1000.0 / 60.0);
|
||||
|
||||
SetHyperDashState(Math.Abs(velocity) / BASE_DASH_SPEED, target.EffectiveX);
|
||||
SetHyperDashState(Math.Abs(velocity) / BASE_DASH_SPEED, target.EffectiveX);
|
||||
}
|
||||
else
|
||||
SetHyperDashState();
|
||||
}
|
||||
else
|
||||
SetHyperDashState();
|
||||
|
||||
if (result.IsHit)
|
||||
CurrentState = hitObject.Kiai ? CatcherAnimationState.Kiai : CatcherAnimationState.Idle;
|
||||
@@ -292,6 +300,8 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
|
||||
if (wasHyperDashing)
|
||||
runHyperDashStateTransition(false);
|
||||
|
||||
lastHyperDashStartTime = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -301,6 +311,8 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
|
||||
if (!wasHyperDashing)
|
||||
runHyperDashStateTransition(true);
|
||||
|
||||
lastHyperDashStartTime = Time.Current;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,10 +18,13 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
[TestFixture]
|
||||
public class ManiaBeatmapConversionTest : BeatmapConversionTest<ManiaConvertMapping, ConvertValue>
|
||||
{
|
||||
protected override string ResourceAssembly => "osu.Game.Rulesets.Mania";
|
||||
protected override string ResourceAssembly => "osu.Game.Rulesets.Mania.Tests";
|
||||
|
||||
[TestCase("basic")]
|
||||
[TestCase("zero-length-slider")]
|
||||
[TestCase("20544")]
|
||||
[TestCase("100374")]
|
||||
[TestCase("1450162")]
|
||||
public void Test(string name) => base.Test(name);
|
||||
|
||||
protected override IEnumerable<ConvertValue> CreateConvertValue(HitObject hitObject)
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
[TestFixture]
|
||||
public class ManiaBeatmapSampleConversionTest : BeatmapConversionTest<ConvertMapping<SampleConvertValue>, SampleConvertValue>
|
||||
{
|
||||
protected override string ResourceAssembly => "osu.Game.Rulesets.Mania";
|
||||
protected override string ResourceAssembly => "osu.Game.Rulesets.Mania.Tests";
|
||||
|
||||
[TestCase("convert-samples")]
|
||||
[TestCase("mania-samples")]
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
{
|
||||
public class ManiaDifficultyCalculatorTest : DifficultyCalculatorTest
|
||||
{
|
||||
protected override string ResourceAssembly => "osu.Game.Rulesets.Mania";
|
||||
protected override string ResourceAssembly => "osu.Game.Rulesets.Mania.Tests";
|
||||
|
||||
[TestCase(2.3493769750220914d, 242, "diffcalc-test")]
|
||||
public void Test(double expectedStarRating, int expectedMaxCombo, string name)
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
// 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.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests.Mods
|
||||
{
|
||||
public partial class TestSceneManiaModAutoplay : ModTestScene
|
||||
{
|
||||
protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
|
||||
|
||||
[Test]
|
||||
public void TestPerfectScoreOnShortHoldNote()
|
||||
{
|
||||
CreateModTest(new ModTestData
|
||||
{
|
||||
Autoplay = true,
|
||||
Beatmap = new ManiaBeatmap(new StageDefinition(1))
|
||||
{
|
||||
HitObjects = new List<ManiaHitObject>
|
||||
{
|
||||
new HoldNote
|
||||
{
|
||||
StartTime = 100,
|
||||
EndTime = 100,
|
||||
},
|
||||
new HoldNote
|
||||
{
|
||||
StartTime = 100.1,
|
||||
EndTime = 150,
|
||||
},
|
||||
}
|
||||
},
|
||||
PassCondition = () => Player.ScoreProcessor.Combo.Value == 4
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Mods;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
@@ -25,8 +26,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods
|
||||
public void TestHitWindowWithoutDoubleTime() => CreateModTest(new ModTestData
|
||||
{
|
||||
PassCondition = () => Player.ScoreProcessor.JudgedHits > 0
|
||||
&& Player.ScoreProcessor.Accuracy.Value == 1
|
||||
&& Player.ScoreProcessor.TotalScore.Value == 1_000_000,
|
||||
&& Precision.AlmostEquals(Player.ScoreProcessor.Accuracy.Value, 0.9836, 0.01)
|
||||
&& Player.ScoreProcessor.TotalScore.Value == 946_049,
|
||||
Autoplay = false,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
@@ -53,7 +54,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods
|
||||
Mod = doubleTime,
|
||||
PassCondition = () => Player.ScoreProcessor.JudgedHits > 0
|
||||
&& Player.ScoreProcessor.Accuracy.Value == 1
|
||||
&& Player.ScoreProcessor.TotalScore.Value == (long)(1_000_010 * doubleTime.ScoreMultiplier),
|
||||
&& Player.ScoreProcessor.TotalScore.Value == (long)(1_000_000 * doubleTime.ScoreMultiplier),
|
||||
Autoplay = false,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
// 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;
|
||||
using osu.Game.Rulesets.Mania.Mods;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.Replays;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests.Mods
|
||||
{
|
||||
public partial class TestSceneManiaModPerfect : ModPerfectTestScene
|
||||
public partial class TestSceneManiaModPerfect : ModFailConditionTestScene
|
||||
{
|
||||
protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
|
||||
|
||||
@@ -24,5 +29,52 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods
|
||||
[TestCase(false)]
|
||||
[TestCase(true)]
|
||||
public void TestHoldNote(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new HoldNote { StartTime = 1000, EndTime = 3000 }), shouldMiss);
|
||||
|
||||
[Test]
|
||||
public void TestGreatHit() => CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new ManiaModPerfect(),
|
||||
PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(false),
|
||||
Autoplay = false,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new Note
|
||||
{
|
||||
StartTime = 1000,
|
||||
}
|
||||
},
|
||||
},
|
||||
ReplayFrames = new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(1020, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(2000)
|
||||
}
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestBreakOnHoldNote() => CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new ManiaModPerfect(),
|
||||
PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(true) && Player.Results.Count == 2,
|
||||
Autoplay = false,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new HoldNote
|
||||
{
|
||||
StartTime = 1000,
|
||||
EndTime = 3000,
|
||||
},
|
||||
},
|
||||
},
|
||||
ReplayFrames = new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(1000, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(2000)
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
// 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;
|
||||
using osu.Game.Rulesets.Mania.Mods;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.Replays;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests.Mods
|
||||
{
|
||||
public partial class TestSceneManiaModSuddenDeath : ModFailConditionTestScene
|
||||
{
|
||||
protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
|
||||
|
||||
public TestSceneManiaModSuddenDeath()
|
||||
: base(new ManiaModSuddenDeath())
|
||||
{
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestGreatHit() => CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new ManiaModSuddenDeath(),
|
||||
PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(false),
|
||||
Autoplay = false,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new Note
|
||||
{
|
||||
StartTime = 1000,
|
||||
}
|
||||
},
|
||||
},
|
||||
ReplayFrames = new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(1020, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(2000)
|
||||
}
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestBreakOnHoldNote() => CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new ManiaModSuddenDeath(),
|
||||
PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(true) && Player.Results.Count == 2,
|
||||
Autoplay = false,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new HoldNote
|
||||
{
|
||||
StartTime = 1000,
|
||||
EndTime = 3000,
|
||||
},
|
||||
},
|
||||
},
|
||||
ReplayFrames = new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(1000, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(2000)
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
+1
File diff suppressed because one or more lines are too long
@@ -0,0 +1,449 @@
|
||||
osu file format v9
|
||||
|
||||
[General]
|
||||
StackLeniency: 0.4
|
||||
Mode: 0
|
||||
|
||||
[Difficulty]
|
||||
HPDrainRate:5
|
||||
CircleSize:4
|
||||
OverallDifficulty:5
|
||||
ApproachRate:6
|
||||
SliderMultiplier:1.7
|
||||
SliderTickRate:2
|
||||
|
||||
[Events]
|
||||
//Background and Video events
|
||||
//Break Periods
|
||||
2,98678,112295
|
||||
2,185757,200967
|
||||
//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]
|
||||
695,530.973451327434,4,2,1,20,1,0
|
||||
33457,-100,4,2,1,25,0,0
|
||||
33988,-100,4,2,1,30,0,0
|
||||
34386,-100,4,1,0,30,0,0
|
||||
38649,-100,4,1,1,30,0,0
|
||||
42897,-100,4,1,0,30,0,0
|
||||
47144,-100,4,1,1,30,0,0
|
||||
51530,-100,4,2,1,20,0,0
|
||||
56978,571.428571428571,4,2,1,20,1,0
|
||||
58692,845.070422535211,4,2,1,20,1,0
|
||||
60248,530.973451327434,4,2,1,20,1,0
|
||||
60740,-100,4,1,1,30,0,0
|
||||
61555,-66.6666666666667,4,1,1,30,0,0
|
||||
62219,-100,4,1,0,40,0,0
|
||||
78148,-100,4,1,0,30,0,0
|
||||
78413,-100,4,1,0,35,0,0
|
||||
78679,-100,4,1,0,40,0,0
|
||||
78944,-100,4,1,0,45,0,0
|
||||
79210,-100,4,1,0,40,0,0
|
||||
96466,-100,4,2,1,30,0,0
|
||||
132285,-100,4,2,1,20,0,0
|
||||
149453,-100,4,1,1,35,0,0
|
||||
153790,-100,4,2,1,40,0,0
|
||||
157639,-100,4,1,1,35,0,0
|
||||
162020,-100,4,2,1,40,0,0
|
||||
166158,-100,4,1,0,40,0,0
|
||||
201733,-100,4,2,1,20,0,0
|
||||
219099,-133.333333333333,4,2,1,20,0,0
|
||||
221024,-100,4,1,1,30,0,0
|
||||
221290,-100,4,1,0,30,0,0
|
||||
|
||||
[HitObjects]
|
||||
256,192,15562,12,0,17155
|
||||
72,120,17686,5,8
|
||||
128,224,17951,1,0
|
||||
185,119,18217,1,0
|
||||
246,220,18482,1,0
|
||||
128,224,18748,2,0,B|161:262|208:264,1,85,4|0
|
||||
309,213,19279,2,0,B|297:169|325:120,2,85,0|0|8
|
||||
309,213,20075,5,0
|
||||
309,332,20341,1,0
|
||||
206,272,20606,1,8
|
||||
309,213,20871,2,0,B|336:117|261:56,1,170,4|0
|
||||
205,272,21933,6,0,B|183:307|125:328,1,85,8|0
|
||||
149,256,22464,2,0,B|114:281|45:280,1,85,0|0
|
||||
101,216,22995,2,0,B|16:264|-56:176|16:72|104:128,1,255,4|0
|
||||
149,136,24057,6,0,B|170:100|229:80,1,85,8|0
|
||||
205,149,24588,2,0,B|239:123|309:125,1,85,0|8
|
||||
253,189,25119,2,0,B|349:144|413:221,1,170,4|8
|
||||
240,336,26181,5,8
|
||||
288,264,26447,1,0
|
||||
344,328,26712,2,0,B|391:339|440:328,1,85,0|0
|
||||
488,270,27243,2,0,B|424:256|392:200,1,85,4|0
|
||||
329,230,27774,2,0,B|328:176|386:142,1,85,0|0
|
||||
363,69,28305,2,0,B|328:40|280:56,2,85,8|0|0
|
||||
312,136,29102,1,0
|
||||
224,120,29367,2,0,B|192:168|256:240|224:296,1,170,4|8
|
||||
96,240,30429,6,0,B|83:195|56:160,1,85,8|0
|
||||
96,88,30960,2,0,B|83:132|56:168,1,85,0|0
|
||||
59,164,31491,2,0,B|129:182|187:167|254:149|323:168,1,255,4|0
|
||||
312,165,32553,6,0,B|302:210|256:237,1,85,8|0
|
||||
312,166,33084,2,0,B|321:120|368:94,1,85,8|0
|
||||
312,166,33615,2,0,B|318:204|374:193|426:183|450:247,1,170,8|8
|
||||
200,232,34677,5,4
|
||||
119,169,34942,1,0
|
||||
57,248,35208,1,8
|
||||
137,311,35473,1,0
|
||||
200,232,35739,5,0
|
||||
248,302,36004,1,0
|
||||
318,254,36270,1,8
|
||||
270,183,36535,1,0
|
||||
200,232,36801,6,0,B|120:272|120:272|40:224,1,170,0|8
|
||||
130,183,37597,1,0
|
||||
200,232,37863,2,0,B|280:192|280:192|368:240,1,170,0|8
|
||||
167,111,38925,6,0,B|134:71|98:65,1,85,8|0
|
||||
167,112,39456,2,0,B|115:116|90:142,1,85,4|0
|
||||
167,112,39987,2,0,B|120:192|176:248|240:312|152:368,1,255,8|0
|
||||
173,351,41048,6,0,B|142:305|80:288,1,85,8|0
|
||||
173,351,41579,2,0,B|194:299|175:238,1,85,4|0
|
||||
173,351,42110,2,0,B|237:351|253:303|269:255|341:263,1,170,8|8
|
||||
128,144,43172,5,4
|
||||
208,176,43438,1,0
|
||||
288,144,43703,1,8
|
||||
368,176,43969,1,0
|
||||
408,272,44234,5,0
|
||||
312,312,44500,1,0
|
||||
216,272,44765,1,8
|
||||
120,312,45031,1,0
|
||||
48,240,45296,5,0
|
||||
160,272,45562,1,0
|
||||
272,240,45827,1,8
|
||||
384,280,46093,1,0
|
||||
496,240,46358,2,0,B|448:208|448:208|496:176|504:128|442:127,1,170,0|8
|
||||
152,128,47420,6,0,B|122:167|120:224,1,85,8|0
|
||||
88,128,47951,2,0,B|95:177|133:218,1,85,4|0
|
||||
121,204,48482,2,0,B|140:296|264:280|308:368,1,255,8|0
|
||||
308,368,49544,6,0,B|293:318|324:264,1,85,8|0
|
||||
368,348,50075,2,0,B|322:323|305:263,1,85,4|0
|
||||
324,200,50606,2,0,B|274:214|203:224|142:108|131:56|243:32|243:120|211:160|107:136,1,340,8|2
|
||||
369,216,52730,5,2
|
||||
176,312,53792,2,0,B|166:217|64:144,1,170,0|0
|
||||
179,150,54588,1,0
|
||||
120,88,54854,2,0,B|107:176|38:232,1,170,2|0
|
||||
464,320,55916,6,0,B|392:252|288:280,1,170,0|0
|
||||
280,104,56978,6,0,B|312:192|416:208,1,170,2|0
|
||||
192,160,58120,2,0,B|182:224|112:240,1,85,2|0
|
||||
24,240,58692,6,0,B|72:240|88:272,1,56.6666666666667,6|0
|
||||
224,296,59325,2,0,B|240:200|200:120,1,170
|
||||
316,136,60513,5,0
|
||||
400,156,60778,2,0,B|408:100|364:56,1,85,10|0
|
||||
320,16,61309,1,2
|
||||
160,112,61840,6,0,B|95:104|28:135,1,127.499996200204,8|0
|
||||
160,112,62371,6,0,B|80:168|96:296,1,170,4|8
|
||||
176,280,63168,1,0
|
||||
224,208,63433,2,0,B|280:288|392:264,1,170,0|8
|
||||
456,184,64230,1,0
|
||||
328,144,64495,1,8
|
||||
416,248,64761,1,0
|
||||
408,112,65026,1,8
|
||||
336,232,65292,1,0
|
||||
388,182,65557,1,8
|
||||
256,288,66088,5,8
|
||||
256,288,66354,1,0
|
||||
256,288,66619,2,0,B|200:360|72:368,1,170,0|8
|
||||
44,308,67416,1,0
|
||||
87,234,67681,2,0,B|163:279|207:386,1,170,0|8
|
||||
256,288,68478,1,0
|
||||
400,120,68743,5,8
|
||||
328,256,69009,1,0
|
||||
400,120,69274,1,8
|
||||
264,184,69540,1,0
|
||||
400,120,69805,1,8
|
||||
400,120,70336,6,0,B|395:173|368:200,1,85,8|0
|
||||
213,255,70867,2,0,B|279:198|383:198,1,170,4|8
|
||||
329,125,71663,1,0
|
||||
248,104,71929,2,0,B|184:168|80:152,1,170,0|8
|
||||
200,224,72725,1,0
|
||||
272,339,72991,5,8
|
||||
151,276,73256,1,0
|
||||
267,204,73522,1,8
|
||||
204,322,73787,1,0
|
||||
287,272,74053,1,8
|
||||
287,272,74584,6,0,B|336:256|368:208,1,85,8|0
|
||||
372,140,75115,2,0,B|323:206|324:308,1,170,0|8
|
||||
240,288,75911,1,0
|
||||
160,248,76177,2,0,B|216:176|320:216,1,170,0|8
|
||||
272,136,76973,1,0
|
||||
200,88,77239,6,0,B|216:136|192:176,1,85,8|0
|
||||
160,248,77770,2,0,B|160:296|208:320,1,85,8|0
|
||||
328,232,78301,5,0
|
||||
233,133,78566,1,8
|
||||
297,15,78832,1,8
|
||||
432,40,79097,1,8
|
||||
453,176,79363,6,0,B|448:240|384:272|328:232,1,170,4|8
|
||||
286,306,80159,1,0
|
||||
203,288,80424,2,0,B|208:224|272:192|328:232,1,170,0|8
|
||||
404,231,81221,1,0
|
||||
408,160,81486,5,8
|
||||
360,288,81752,1,0
|
||||
472,216,82017,1,8
|
||||
336,208,82283,1,0
|
||||
440,296,82548,1,8
|
||||
288,320,83079,5,8
|
||||
288,320,83345,1,0
|
||||
288,320,83610,2,0,B|200:314|128:248,1,170,0|8
|
||||
88,320,84407,1,0
|
||||
56,240,84672,2,0,B|133:287|176:392,1,170,0|8
|
||||
163,274,85469,1,0
|
||||
296,216,85734,5,8
|
||||
165,75,86000,1,0
|
||||
99,178,86265,1,8
|
||||
282,97,86531,1,0
|
||||
184,264,86796,1,8
|
||||
184,264,87327,6,0,B|159:295|110:299,1,85,8|0
|
||||
23,247,87858,2,0,B|91:300|192:261,1,170,4|8
|
||||
245,326,88655,1,0
|
||||
293,254,88920,2,0,B|213:198|109:246,1,170,0|8
|
||||
181,302,89717,1,0
|
||||
165,166,89982,5,8
|
||||
141,302,90247,1,0
|
||||
205,182,90513,1,8
|
||||
109,278,90778,1,0
|
||||
229,214,91044,1,8
|
||||
376,132,91575,6,0,B|424:140|464:100,1,85,8|0
|
||||
464,192,92106,2,0,B|456:280|352:320,1,170,0|8
|
||||
300,256,92902,1,0
|
||||
228,212,93168,2,0,B|268:116|164:60,1,170,0|8
|
||||
100,32,93964,1,0
|
||||
84,116,94230,2,0,B|116:156|108:212,1,85,8|0
|
||||
188,160,94761,2,0,B|188:208|232:244,1,85,8|0
|
||||
296,196,95292,2,0,B|320:236|349:239|399:242|379:198|379:198|334:185|358:245|368:276|440:260|480:316|416:356,1,340,8|4
|
||||
256,192,96486,12,8,98478
|
||||
264,192,113345,5,8
|
||||
264,192,113876,1,8
|
||||
264,192,114407,5,0
|
||||
172,236,114672,1,8
|
||||
184,336,114938,1,0
|
||||
284,356,115203,1,8
|
||||
340,268,115469,1,8
|
||||
304,100,116000,1,8
|
||||
304,100,116531,1,0
|
||||
272,336,117062,5,8
|
||||
248,200,117327,1,0
|
||||
376,152,117593,1,8
|
||||
376,152,118124,1,8
|
||||
376,152,118655,5,0
|
||||
240,128,118920,1,8
|
||||
376,192,119186,1,0
|
||||
496,152,119451,1,8
|
||||
376,224,119717,1,8
|
||||
376,224,120247,1,8
|
||||
376,224,120778,1,0
|
||||
376,224,121309,5,8
|
||||
264,296,121575,1,0
|
||||
256,160,121840,1,8
|
||||
256,160,122371,1,8
|
||||
256,160,122902,1,0
|
||||
256,160,123433,5,8
|
||||
168,264,123699,1,0
|
||||
312,280,123964,1,8
|
||||
312,280,124495,1,8
|
||||
312,280,125026,1,0
|
||||
312,280,125557,5,8
|
||||
200,200,125823,1,0
|
||||
312,280,126088,1,8
|
||||
312,280,126619,1,8
|
||||
312,280,127150,5,0
|
||||
416,200,127416,1,8
|
||||
432,336,127681,1,0
|
||||
416,200,127947,1,8
|
||||
312,280,128212,1,8
|
||||
312,280,128743,1,8
|
||||
312,280,129274,5,8
|
||||
264,152,129540,1,8
|
||||
136,192,129805,1,8
|
||||
184,320,130071,1,12
|
||||
88,120,132460,6,0,B|127:224|104:304,1,170,2|0
|
||||
424,264,133522,2,0,B|384:159|408:80,1,170
|
||||
448,168,134318,2,0,B|369:240|297:240,1,170,4|0
|
||||
301,158,135115,2,0,B|277:206|309:262,1,85
|
||||
395,295,135646,2,0,B|323:263|227:287,1,170,0|2
|
||||
176,88,136708,6,0,B|134:57|80:64,1,85
|
||||
176,88,137239,2,0,B|221:64|264:64,1,85,8|0
|
||||
176,88,137770,2,0,B|137:175|196:220|272:272|208:344,1,255,4|0
|
||||
136,328,138832,6,0,B|83:306|40:328,1,85
|
||||
136,328,139363,2,0,B|184:312|224:328,1,85,2|0
|
||||
300,296,139894,2,0,B|300:198|388:200|468:200|452:104,1,255,4|0
|
||||
372,100,140955,1,0
|
||||
292,72,141221,6,0,B|250:102|244:152,2,85,0|8|0
|
||||
332,148,142017,1,4
|
||||
388,212,142283,2,0,B|414:243|465:241,1,85
|
||||
440,148,142814,2,0,B|400:172|388:213,1,85
|
||||
236,232,143345,1,0
|
||||
204,84,143610,1,0
|
||||
356,64,143876,1,0
|
||||
388,212,144141,2,0,B|350:295|228:308,1,170,4|0
|
||||
96,304,145203,6,0,B|96:208,1,85
|
||||
144,203,145734,2,0,B|144:288,1,85,8|0
|
||||
192,272,146265,2,0,B|192:176|192:176|192:120|256:112,1,170,4|0
|
||||
312,56,147062,1,0
|
||||
392,120,147327,6,0,B|392:208,1,85
|
||||
336,221,147858,2,0,B|336:136,1,85,8|0
|
||||
280,152,148389,2,0,B|280:256|280:256|264:272|280:288|280:288|296:304|280:320|280:320|248:336|280:352|280:352|312:368|312:368|280:376|224:384,1,340,4|4
|
||||
172,322,149717,5,0
|
||||
136,248,149982,1,8
|
||||
64,208,150247,1,0
|
||||
147,112,150513,5,0
|
||||
224,80,150778,1,0
|
||||
304,112,151044,1,8
|
||||
384,88,151309,1,0
|
||||
336,192,151575,6,0,B|280:272|176:264,1,170,0|8
|
||||
408,216,152637,2,0,B|429:173|464:152,1,85,0|0
|
||||
360,80,153168,2,0,B|376:168|304:264,1,170,8|0
|
||||
256,288,153964,5,2
|
||||
192,240,154230,1,4
|
||||
272,208,154495,1,0
|
||||
229,134,154761,2,0,B|276:214,1,85,0|2
|
||||
160,248,155292,1,4
|
||||
120,136,155557,1,0
|
||||
229,134,155823,6,0,B|331:134,1,85,0|2
|
||||
408,208,156354,2,0,B|312:208,1,85,4|0
|
||||
216,256,156885,2,0,B|272:280|264:352|208:344|192:296|256:272|328:312,1,170,0|4
|
||||
456,224,157947,5,0
|
||||
400,136,158212,1,0
|
||||
456,224,158478,1,8
|
||||
392,304,158743,1,0
|
||||
456,224,159009,1,0
|
||||
288,232,159540,5,8
|
||||
200,283,159805,1,0
|
||||
176,184,160071,1,0
|
||||
176,184,160601,5,8
|
||||
278,184,160867,1,0
|
||||
176,184,161132,2,0,B|88:184,1,85
|
||||
24,88,161663,2,0,B|192:88,1,170,8|0
|
||||
280,88,162460,1,2
|
||||
240,168,162725,1,4
|
||||
360,48,163256,5,0
|
||||
280,88,163522,1,2
|
||||
240,168,163787,2,0,B|344:168,1,85,4|0
|
||||
376,240,164318,2,0,B|320:312,1,85,2|0
|
||||
248,304,164849,2,0,B|200:232,1,85,6|0
|
||||
288,240,165380,2,0,B|288:136|288:136|286:82|344:72,1,170,6|8
|
||||
480,104,166442,6,0,B|416:168|416:296,1,170,4|8
|
||||
336,280,167239,1,0
|
||||
288,208,167504,2,0,B|232:288|120:264,1,170,0|8
|
||||
56,184,168301,1,0
|
||||
184,144,168566,1,8
|
||||
96,248,168832,1,0
|
||||
104,112,169097,1,8
|
||||
176,232,169363,1,0
|
||||
124,182,169628,1,8
|
||||
272,256,170159,5,8
|
||||
272,256,170424,1,0
|
||||
272,256,170690,2,0,B|310:339|428:329,1,170,0|8
|
||||
487,259,171486,1,0
|
||||
423,179,171752,2,0,B|340:241|340:329,1,170,0|8
|
||||
251,346,172548,1,0
|
||||
260,193,172814,5,8
|
||||
340,321,173079,1,0
|
||||
260,193,173345,1,8
|
||||
404,249,173610,1,0
|
||||
260,193,173876,1,8
|
||||
112,120,174407,6,0,B|117:173|144:200,1,85,8|0
|
||||
309,191,174938,2,0,B|225:225|117:191,1,170,0|8
|
||||
184,128,175734,1,0
|
||||
264,104,176000,2,0,B|328:168|432:152,1,170,0|8
|
||||
312,224,176796,1,0
|
||||
240,339,177062,5,8
|
||||
361,276,177327,1,0
|
||||
245,204,177593,1,8
|
||||
308,322,177858,1,0
|
||||
225,270,178124,1,8
|
||||
225,270,178655,6,0,B|176:256|144:208,1,85,8|0
|
||||
32,256,179186,2,0,B|120:256|192:312,1,170,0|8
|
||||
272,288,179982,1,0
|
||||
352,248,180247,2,0,B|296:176|192:216,1,170,0|8
|
||||
240,136,181044,1,0
|
||||
325,129,181309,6,0,B|322:176|285:217,1,85,8|0
|
||||
167,291,181840,2,0,B|170:244|207:203,1,85,8|0
|
||||
327,289,182371,2,0,B|280:286|239:249,1,85,8|0
|
||||
160,120,182902,2,0,B|216:112|248:152|272:192|336:192,1,170,8|4
|
||||
256,192,183699,12,4,185557
|
||||
80,104,202017,5,2
|
||||
152,219,202283,1,0
|
||||
16,224,202548,2,0,B|88:208|158:111,1,170,8|0
|
||||
226,87,203345,1,0
|
||||
304,120,203610,2,0,B|352:120|400:104,1,85,2|0
|
||||
304,120,204141,2,0,B|336:88|344:32,1,85,0|0
|
||||
341,45,204672,6,0,B|429:77|450:203,1,170,8|0
|
||||
360,184,205469,1,0
|
||||
304,120,205734,2,0,B|264:96|240:48,1,85,2|0
|
||||
304,120,206265,2,0,B|311:76|344:32,1,85,0|0
|
||||
408,88,206796,5,4
|
||||
472,168,207062,1,0
|
||||
392,224,207327,1,0
|
||||
304,280,207593,1,0
|
||||
224,208,207858,2,0,B|309:237|393:224,1,170
|
||||
472,168,208655,1,0
|
||||
408,88,208920,6,0,B|368:166|402:252,1,170,8|0
|
||||
504,280,209717,1,0
|
||||
403,319,209982,2,0,B|459:276|475:151,1,170,4|0
|
||||
408,88,210778,1,0
|
||||
384,200,211044,5,2
|
||||
240,160,211309,1,0
|
||||
264,304,211575,1,0
|
||||
296,224,211840,2,0,B|336:137|464:136,1,170,2|0
|
||||
296,224,212637,6,0,B|243:220|208:161,1,85,2|0
|
||||
163,324,213168,2,0,B|244:308|308:204,1,170,8|0
|
||||
296,136,213964,1,0
|
||||
264,56,214230,2,0,B|232:96|192:136,1,85,4|0
|
||||
208,120,214761,2,0,B|200:72|168:32,1,85
|
||||
175,42,215292,2,0,B|155:86|98:112,1,85,2|0
|
||||
50,53,215823,2,0,B|98:69|122:109,1,85,0|0
|
||||
117,102,216354,1,4
|
||||
168,344,216885,6,0,B|167:287|131:246,1,85
|
||||
88,160,217416,2,0,B|48:248|96:328,1,170,8|0
|
||||
144,264,218212,1,0
|
||||
224,296,218478,2,0,B|328:312|368:216,1,170,6|0
|
||||
363,110,219274,2,0,B|259:246|139:206|147:94|275:70|355:198|130:268,1,446.249986700714,2|8
|
||||
160,112,221663,6,0,B|80:168|96:296,1,170,4|8
|
||||
176,280,222460,1,0
|
||||
224,208,222725,2,0,B|280:288|392:264,1,170,0|8
|
||||
456,184,223522,1,0
|
||||
328,144,223787,5,8
|
||||
416,248,224053,1,0
|
||||
408,112,224318,1,8
|
||||
336,232,224584,1,0
|
||||
388,182,224849,1,8
|
||||
240,256,225380,5,8
|
||||
240,256,225646,1,0
|
||||
240,256,225911,2,0,B|184:328|76:314,1,170,0|8
|
||||
3,315,226708,1,0
|
||||
89,315,226973,2,0,B|184:302|240:374,1,170,0|8
|
||||
314,332,227770,1,0
|
||||
252,194,228035,5,8
|
||||
116,130,228301,1,0
|
||||
252,194,228566,1,8
|
||||
140,298,228832,1,0
|
||||
252,194,229097,1,8
|
||||
400,120,229628,6,0,B|352:112|288:144,1,85,8|0
|
||||
203,191,230159,2,0,B|287:225|395:191,1,170,0|8
|
||||
330,124,230955,1,0
|
||||
248,104,231221,2,0,B|152:96|80:152,1,170,0|8
|
||||
200,224,232017,1,0
|
||||
272,339,232283,5,8
|
||||
151,276,232548,1,0
|
||||
267,204,232814,1,8
|
||||
204,322,233079,1,0
|
||||
287,270,233345,1,8
|
||||
287,270,233876,6,0,B|335:254|367:206,1,85,8|0
|
||||
464,288,234407,2,0,B|368:272|304:344,1,170,0|8
|
||||
226,317,235203,1,0
|
||||
165,256,235469,2,0,B|224:192|336:208,1,170,0|8
|
||||
272,136,236265,1,0
|
||||
199,63,236531,2,0,B|152:80|120:128,1,85,8|0
|
||||
203,184,237062,2,0,B|167:218|165:267,1,85,8|0
|
||||
312,264,237593,5,8
|
||||
440,264,237858,1,8
|
||||
256,144,238124,1,8
|
||||
496,144,238389,1,0
|
||||
256,192,238655,12,4,240778
|
||||
+1
File diff suppressed because one or more lines are too long
@@ -0,0 +1,297 @@
|
||||
osu file format v14
|
||||
|
||||
[General]
|
||||
StackLeniency: 0.7
|
||||
Mode: 0
|
||||
|
||||
[Difficulty]
|
||||
HPDrainRate:5
|
||||
CircleSize:4
|
||||
OverallDifficulty:7
|
||||
ApproachRate:7.5
|
||||
SliderMultiplier:1.4
|
||||
SliderTickRate:1
|
||||
|
||||
[Events]
|
||||
//Background and Video events
|
||||
//Break Periods
|
||||
//Storyboard Layer 0 (Background)
|
||||
//Storyboard Layer 1 (Fail)
|
||||
//Storyboard Layer 2 (Pass)
|
||||
//Storyboard Layer 3 (Foreground)
|
||||
//Storyboard Sound Samples
|
||||
|
||||
[TimingPoints]
|
||||
1107,365.853658536585,4,2,1,50,1,0
|
||||
1107,-166.666666666667,4,2,1,50,0,0
|
||||
6960,-111.111111111111,4,2,1,50,0,0
|
||||
8424,-100,4,2,1,50,0,0
|
||||
48119,-125,4,2,1,50,0,0
|
||||
52143,-100,4,2,1,50,0,0
|
||||
62570,-100,4,2,1,60,0,1
|
||||
85985,-100,4,2,1,50,0,0
|
||||
97692,-100,4,2,1,30,0,0
|
||||
99155,-100,4,2,1,20,0,0
|
||||
100619,-100,4,2,1,5,0,0
|
||||
|
||||
[HitObjects]
|
||||
38,247,1107,6,0,P|96:269|170:192,1,167.999994873047,2|0,0:0|0:0,0:0:0:0:
|
||||
201,128,2570,6,0,L|205:221,1,83.9999974365235,2|0,0:0|0:0,0:0:0:0:
|
||||
242,230,3302,2,0,L|234:324,1,83.9999974365235,2|0,0:0|0:0,0:0:0:0:
|
||||
205,343,4033,6,0,P|246:296|351:314,1,167.999994873047,2|0,0:0|0:0,0:0:0:0:
|
||||
400,368,5497,6,0,L|412:269,1,83.9999974365235,6|0,0:0|0:0,0:0:0:0:
|
||||
436,251,6228,2,0,P|425:203|408:153,1,83.9999974365235,2|0,0:0|0:0,0:0:0:0:
|
||||
304,200,6960,6,0,P|262:186|234:181,1,62.9999980773926,6|0,0:0|0:0,0:0:0:0:
|
||||
202,179,7326,1,8,0:0:0:0:
|
||||
276,94,7509,2,0,P|313:92|353:87,1,62.9999980773926,2|0,0:0|0:0,0:0:0:0:
|
||||
398,31,7875,1,2,0:0:0:0:
|
||||
464,81,8058,2,0,L|450:150,1,62.9999980773926,2|0,0:0|0:0,0:0:0:0:
|
||||
449,230,8424,6,0,P|347:206|306:217,1,140,2|8,0:0|0:0,0:0:0:0:
|
||||
229,273,8972,2,0,P|225:339|235:361,1,70,2|0,0:0|0:0,0:0:0:0:
|
||||
304,313,9338,1,8,0:0:0:0:
|
||||
224,190,9521,1,2,0:0:0:0:
|
||||
296,45,9887,6,0,P|297:97|288:125,1,70,6|0,0:0|0:0,0:0:0:0:
|
||||
224,190,10253,1,8,0:0:0:0:
|
||||
167,118,10436,1,8,0:0:0:0:
|
||||
76,126,10619,1,8,0:0:0:0:
|
||||
39,209,10802,1,8,0:0:0:0:
|
||||
93,282,10985,1,10,0:0:0:0:
|
||||
184,280,11167,1,10,0:0:0:0:
|
||||
102,136,12814,5,2,0:0:0:0:
|
||||
102,136,13180,2,0,L|199:130,1,70,8|0,0:0|0:0,0:0:0:0:
|
||||
256,167,13546,2,0,L|339:161,1,70,8|2,0:0|0:0,0:0:0:0:
|
||||
408,201,13911,2,0,P|454:176|471:143,1,70,8|2,0:0|0:0,0:0:0:0:
|
||||
373,54,14277,6,0,L|396:137,2,70,6|0|8,0:0|0:0|0:0,0:0:0:0:
|
||||
305,111,14826,2,0,L|287:274,1,140,0|2,0:0|0:0,0:0:0:0:
|
||||
262,337,15375,2,0,L|349:327,1,70,8|2,0:0|0:0,0:0:0:0:
|
||||
419,354,15741,1,8,0:0:0:0:
|
||||
477,197,16106,6,0,P|423:197|385:209,1,70,8|0,0:0|0:0,0:0:0:0:
|
||||
321,170,16472,2,0,P|278:190|253:219,1,70,8|2,0:0|0:0,0:0:0:0:
|
||||
171,213,16838,2,0,P|152:259|158:304,1,70,8|2,0:0|0:0,0:0:0:0:
|
||||
305,294,17204,6,0,L|224:278,2,70,6|0|8,0:0|0:0|0:0,0:0:0:0:
|
||||
310,202,17753,2,0,L|149:214,1,140,0|2,0:0|0:0,0:0:0:0:
|
||||
84,244,18302,2,0,L|92:152,1,70,8|2,0:0|0:0,0:0:0:0:
|
||||
47,93,18667,6,0,P|78:53|176:80,1,140,6|8,0:0|0:0,0:0:0:0:
|
||||
218,130,19216,1,0,0:0:0:0:
|
||||
299,88,19399,2,0,L|387:91,1,70,8|0,0:0|0:0,0:0:0:0:
|
||||
458,106,19765,2,0,P|447:139|444:205,1,70,8|0,0:0|0:0,0:0:0:0:
|
||||
455,274,20131,5,2,0:0:0:0:
|
||||
366,292,20314,2,0,L|353:211,1,70,0|8,0:0|0:0,0:0:0:0:
|
||||
277,173,20680,2,0,L|253:342,1,140,0|2,0:0|0:0,0:0:0:0:
|
||||
322,376,21228,2,0,P|368:368|416:370,1,70,8|2,0:0|0:0,0:0:0:0:
|
||||
500,287,21594,6,0,P|427:273|362:293,2,140,6|8|8,0:0|0:0|0:0,0:0:0:0:
|
||||
496,111,22509,1,8,0:0:0:0:
|
||||
499,189,22692,2,0,L|418:191,1,70,8|2,0:0|0:0,0:0:0:0:
|
||||
344,164,23058,5,6,0:0:0:0:
|
||||
344,164,23241,1,12,0:0:0:0:
|
||||
261,326,23606,2,0,L|246:178,1,140,8|2,0:0|0:0,0:0:0:0:
|
||||
277,100,24155,2,0,P|225:99|196:109,1,70,8|2,0:0|0:0,0:0:0:0:
|
||||
165,273,24521,5,6,0:0:0:0:
|
||||
83,235,24704,2,0,L|93:81,1,140,0|0,0:0|0:0,0:0:0:0:
|
||||
21,37,25253,2,0,L|1:120,2,70,2|0|8,0:0|0:0|0:0,0:0:0:0:
|
||||
110,17,25802,1,0,0:0:0:0:
|
||||
172,83,25985,5,2,0:0:0:0:
|
||||
236,19,26167,2,0,P|223:70|227:170,1,140,0|0,0:0|0:0,0:0:0:0:
|
||||
293,216,26716,2,0,P|316:165|314:134,2,70,2|0|8,0:0|0:0|0:0,0:0:0:0:
|
||||
206,245,27265,1,0,0:0:0:0:
|
||||
274,305,27448,5,2,0:0:0:0:
|
||||
194,348,27631,2,0,L|363:332,1,140,0|0,0:0|0:0,0:0:0:0:
|
||||
424,336,28180,1,2,0:0:0:0:
|
||||
431,245,28363,2,0,P|381:252|354:276,2,70,0|8|0,0:0|0:0|0:0,0:0:0:0:
|
||||
509,291,28911,6,0,L|496:128,1,140,2|8,0:0|0:0,0:0:0:0:
|
||||
504,60,29460,1,0,0:0:0:0:
|
||||
417,34,29643,2,0,L|402:183,1,140,2|8,0:0|0:0,0:0:0:0:
|
||||
365,262,30192,1,0,0:0:0:0:
|
||||
295,202,30375,5,2,0:0:0:0:
|
||||
309,112,30558,2,0,P|282:172|196:176,1,140,0|0,0:0|0:0,0:0:0:0:
|
||||
148,120,31106,2,0,P|189:99|225:99,2,70,2|0|8,0:0|0:0|0:0,0:0:0:0:
|
||||
129,209,31655,1,0,0:0:0:0:
|
||||
63,146,31838,5,2,0:0:0:0:
|
||||
16,67,32021,2,0,L|27:220,1,140,0|0,0:0|0:0,0:0:0:0:
|
||||
23,297,32570,2,0,P|81:286|111:290,1,70,2|0,0:0|0:0,0:0:0:0:
|
||||
173,327,32936,1,8,0:0:0:0:
|
||||
338,251,33302,6,0,P|268:254|227:199,1,140,2|8,0:0|0:0,0:0:0:0:
|
||||
203,114,33850,2,0,L|185:262,1,140,0|0,0:0|0:0,0:0:0:0:
|
||||
244,323,34399,1,8,0:0:0:0:
|
||||
334,335,34582,1,0,0:0:0:0:
|
||||
419,219,34765,6,0,L|410:304,1,70,2|0,0:0|0:0,0:0:0:0:
|
||||
338,251,35131,1,8,0:0:0:0:
|
||||
301,111,35314,2,0,L|301:190,1,70,6|0,0:0|0:0,0:0:0:0:
|
||||
383,141,35680,1,8,0:0:0:0:
|
||||
462,97,35863,2,0,P|427:64|393:54,1,70,2|0,0:0|0:0,0:0:0:0:
|
||||
321,23,36228,5,2,0:0:0:0:
|
||||
237,60,36411,1,0,0:0:0:0:
|
||||
148,38,36594,2,0,P|107:33|56:43,1,70,8|0,0:0|0:0,0:0:0:0:
|
||||
86,125,36960,2,0,P|51:125|17:117,2,70,2|0|8,0:0|0:0|0:0,0:0:0:0:
|
||||
175,123,37509,1,0,0:0:0:0:
|
||||
129,201,37692,5,2,0:0:0:0:
|
||||
198,259,37875,1,0,0:0:0:0:
|
||||
205,349,38058,2,0,P|251:330|284:326,1,70,8|0,0:0|0:0,0:0:0:0:
|
||||
352,285,38424,2,0,P|361:318|357:353,2,70,2|0|8,0:0|0:0|0:0,0:0:0:0:
|
||||
282,239,38972,1,0,0:0:0:0:
|
||||
362,195,39155,5,2,0:0:0:0:
|
||||
436,142,39338,2,0,P|398:115|354:112,1,70,0|8,0:0|0:0,0:0:0:0:
|
||||
286,92,39704,2,0,L|451:74,1,140,0|0,0:0|0:0,0:0:0:0:
|
||||
512,118,40253,2,0,L|494:198,1,70,8|0,0:0|0:0,0:0:0:0:
|
||||
430,297,40619,6,0,P|423:236|336:195,1,140,2|8,0:0|0:0,0:0:0:0:
|
||||
282,239,41167,1,0,0:0:0:0:
|
||||
209,184,41350,2,0,L|222:112,1,70,2|2,0:0|0:0,0:0:0:0:
|
||||
177,34,41716,2,0,P|230:26|269:38,1,70,8|0,0:0|0:0,0:0:0:0:
|
||||
307,95,42082,5,2,0:0:0:0:
|
||||
363,23,42265,2,0,L|359:114,1,70,0|8,0:0|0:0,0:0:0:0:
|
||||
360,184,42631,1,0,0:0:0:0:
|
||||
450,191,42814,2,0,P|443:145|424:119,2,70,2|0|8,0:0|0:0|0:0,0:0:0:0:
|
||||
393,263,43363,1,0,0:0:0:0:
|
||||
304,242,43546,5,2,0:0:0:0:
|
||||
241,308,43728,1,0,0:0:0:0:
|
||||
167,256,43911,2,0,P|205:228|245:226,1,70,8|0,0:0|0:0,0:0:0:0:
|
||||
166,341,44277,2,0,P|118:325|90:289,1,70,2|0,0:0|0:0,0:0:0:0:
|
||||
125,177,44643,2,0,P|168:152|201:153,1,70,8|0,0:0|0:0,0:0:0:0:
|
||||
276,132,45009,6,0,L|119:105,1,140,2|8,0:0|0:0,0:0:0:0:
|
||||
52,74,45558,2,0,L|210:57,1,140,2|0,0:0|0:0,0:0:0:0:
|
||||
277,28,46106,1,8,0:0:0:0:
|
||||
349,82,46289,1,0,0:0:0:0:
|
||||
425,32,46472,6,0,L|451:110,2,70,6|2|8,0:0|0:0|0:0,0:0:0:0:
|
||||
349,82,47021,2,0,L|344:235,1,140,2|8,0:0|0:0,0:0:0:0:
|
||||
372,308,47570,1,2,0:0:0:0:
|
||||
170,324,47936,5,2,0:0:0:0:
|
||||
99,286,48119,2,0,L|112:112,1,168,2|2,0:0|0:0,0:0:0:0:
|
||||
64,48,48850,2,0,P|125:36|195:111,1,168,2|2,0:0|0:0,0:0:0:0:
|
||||
199,189,49582,6,0,L|369:166,1,168,2|2,0:0|0:0,0:0:0:0:
|
||||
413,97,50314,2,0,P|390:180|377:274,1,168,2|2,0:0|0:0,0:0:0:0:
|
||||
347,339,51046,6,0,P|424:333|463:251,1,168,2|2,0:0|0:0,0:0:0:0:
|
||||
473,175,51777,2,0,L|477:105,1,56,2|2,0:0|0:0,0:0:0:0:
|
||||
446,24,52143,6,0,P|363:22|308:82,1,140,12|2,0:0|0:0,0:0:0:0:
|
||||
282,138,52692,1,8,0:0:0:0:
|
||||
193,118,52875,2,0,L|213:281,1,140,2|8,0:0|0:0,0:0:0:0:
|
||||
225,347,53424,2,0,P|268:328|286:301,1,70,2|0,0:0|0:0,0:0:0:0:
|
||||
304,222,53789,5,2,0:0:0:0:
|
||||
385,263,53972,1,0,0:0:0:0:
|
||||
462,214,54155,2,0,P|421:185|383:179,1,70,8|0,0:0|0:0,0:0:0:0:
|
||||
322,136,54521,2,0,P|360:105|400:93,1,70,2|0,0:0|0:0,0:0:0:0:
|
||||
469,107,54887,2,0,L|483:24,1,70,8|0,0:0|0:0,0:0:0:0:
|
||||
390,22,55253,6,0,L|223:30,1,140,2|8,0:0|0:0,0:0:0:0:
|
||||
180,87,55802,1,0,0:0:0:0:
|
||||
230,162,55985,2,0,L|391:154,1,140,2|8,0:0|0:0,0:0:0:0:
|
||||
430,223,56533,1,0,0:0:0:0:
|
||||
407,311,56716,6,0,P|356:347|285:307,1,140,2|8,0:0|0:0,0:0:0:0:
|
||||
236,245,57265,1,0,0:0:0:0:
|
||||
145,237,57448,2,0,L|162:316,1,70,2|0,0:0|0:0,0:0:0:0:
|
||||
233,360,57814,6,0,P|185:349|142:350,1,70,8|0,0:0|0:0,0:0:0:0:
|
||||
11,311,58180,2,0,P|64:302|104:306,1,70,2|0,0:0|0:0,0:0:0:0:
|
||||
213,248,58546,2,0,P|162:237|130:237,1,70,8|0,0:0|0:0,0:0:0:0:
|
||||
1,194,58911,2,0,P|47:183|74:185,1,70,2|0,0:0|0:0,0:0:0:0:
|
||||
234,142,59277,2,0,P|175:129|152:128,1,70,8|0,0:0|0:0,0:0:0:0:
|
||||
12,26,59643,6,0,P|66:38|71:140,1,140,2|8,0:0|0:0,0:0:0:0:
|
||||
1,194,60192,1,0,0:0:0:0:
|
||||
84,230,60375,1,2,0:0:0:0:
|
||||
173,216,60558,1,8,0:0:0:0:
|
||||
173,216,60649,1,8,0:0:0:0:
|
||||
173,216,60741,1,8,0:0:0:0:
|
||||
263,213,60924,1,2,0:0:0:0:
|
||||
345,174,61106,6,0,P|320:144|286:130,1,70,2|0,0:0|0:0,0:0:0:0:
|
||||
200,134,61472,1,8,0:0:0:0:
|
||||
249,57,61655,2,0,L|263:12,2,35,12|8|8,0:0|0:0|0:0,0:0:0:0:
|
||||
157,64,62021,2,0,L|153:13,2,35,12|8|8,0:0|0:0|0:0,0:0:0:0:
|
||||
118,150,62387,1,2,0:0:0:0:
|
||||
101,260,62570,6,0,P|207:236|257:243,1,140,2|8,0:0|0:0,0:0:0:0:
|
||||
328,304,63119,1,0,0:0:0:0:
|
||||
434,156,63302,2,0,P|373:157|329:217,1,140,2|8,0:0|0:0,0:0:0:0:
|
||||
408,230,63850,1,2,0:0:0:0:
|
||||
483,215,64033,5,6,0:0:0:0:
|
||||
508,142,64216,1,0,0:0:0:0:
|
||||
482,69,64399,1,8,0:0:0:0:
|
||||
413,34,64582,2,0,P|336:30|256:49,1,140,0|2,0:0|0:0,0:0:0:0:
|
||||
150,97,65131,2,0,P|190:97|243:107,1,70,8|2,0:0|0:0,0:0:0:0:
|
||||
257,168,65497,6,0,L|225:323,1,140,2|8,0:0|0:0,0:0:0:0:
|
||||
155,329,66046,1,0,0:0:0:0:
|
||||
20,204,66228,2,0,P|92:202|133:271,1,140,8|8,0:0|0:0,0:0:0:0:
|
||||
56,274,66777,1,2,0:0:0:0:
|
||||
18,125,66960,6,0,L|93:119,1,70,6|0,0:0|0:0,0:0:0:0:
|
||||
162,156,67326,1,8,0:0:0:0:
|
||||
223,52,67509,2,0,L|227:219,1,140,0|2,0:0|0:0,0:0:0:0:
|
||||
266,263,68058,2,0,P|300:229|308:199,1,70,8|2,0:0|0:0,0:0:0:0:
|
||||
298,95,68424,6,0,L|458:75,1,140,6|8,0:0|0:0,0:0:0:0:
|
||||
512,164,68972,2,0,L|358:154,1,140,0|2,0:0|0:0,0:0:0:0:
|
||||
306,209,69521,1,8,0:0:0:0:
|
||||
342,334,69704,6,0,P|361:289|369:244,1,70,2|6,0:0|0:0,0:0:0:0:
|
||||
250,277,70070,2,0,P|223:228|219:186,1,70,0|8,0:0|0:0,0:0:0:0:
|
||||
272,128,70436,1,0,0:0:0:0:
|
||||
172,111,70619,2,0,L|343:97,1,140,8|8,0:0|0:0,0:0:0:0:
|
||||
385,128,71167,1,2,0:0:0:0:
|
||||
494,63,71350,6,0,L|413:54,1,70,6|0,0:0|0:0,0:0:0:0:
|
||||
385,128,71716,2,0,L|475:140,1,70,8|0,0:0|0:0,0:0:0:0:
|
||||
467,217,72082,2,0,L|386:208,1,70,8|2,0:0|0:0,0:0:0:0:
|
||||
358,282,72448,2,0,L|448:294,1,70,8|2,0:0|0:0,0:0:0:0:
|
||||
498,339,72814,5,12,0:0:0:0:
|
||||
498,339,72997,1,12,0:0:0:0:
|
||||
301,343,73363,1,8,0:0:0:0:
|
||||
211,173,73728,2,0,L|221:216,2,35,2|2|8,0:0|0:0|0:0,0:0:0:0:
|
||||
250,100,74094,1,2,0:0:0:0:
|
||||
123,92,74277,6,0,P|129:156|129:236,1,140,2|8,0:0|0:0,0:0:0:0:
|
||||
109,321,74826,1,0,0:0:0:0:
|
||||
211,173,75009,2,0,P|266:165|333:237,1,140,8|8,0:0|0:0,0:0:0:0:
|
||||
341,302,75558,1,2,0:0:0:0:
|
||||
418,272,75741,5,6,0:0:0:0:
|
||||
484,322,75924,1,0,0:0:0:0:
|
||||
407,352,76106,1,8,0:0:0:0:
|
||||
341,302,76289,2,0,L|364:147,1,140,0|2,0:0|0:0,0:0:0:0:
|
||||
269,60,76838,2,0,P|315:69|349:94,1,70,8|0,0:0|0:0,0:0:0:0:
|
||||
269,150,77204,6,0,P|228:160|114:139,1,140,2|8,0:0|0:0,0:0:0:0:
|
||||
49,80,77753,1,0,0:0:0:0:
|
||||
39,235,77936,2,0,P|103:222|160:277,1,140,8|8,0:0|0:0,0:0:0:0:
|
||||
82,297,78485,1,2,0:0:0:0:
|
||||
227,326,78667,6,0,L|233:241,1,70,4|0,0:0|0:0,0:0:0:0:
|
||||
269,150,79033,1,8,0:0:0:0:
|
||||
408,194,79216,2,0,P|359:172|271:187,1,140,0|2,0:0|0:0,0:0:0:0:
|
||||
409,281,79765,2,0,P|447:272|478:250,1,70,8|2,0:0|0:0,0:0:0:0:
|
||||
497,168,80131,6,0,L|481:332,1,140,6|8,0:0|0:0,0:0:0:0:
|
||||
389,365,80680,2,0,L|376:198,1,140,0|2,0:0|0:0,0:0:0:0:
|
||||
414,157,81228,1,8,0:0:0:0:
|
||||
229,89,81411,6,0,P|304:91|338:167,1,140,2|0,0:0|0:0,0:0:0:0:
|
||||
290,222,81960,1,8,0:0:0:0:
|
||||
211,214,82143,1,8,0:0:0:0:
|
||||
93,155,82326,2,0,P|137:143|172:150,1,70,2|2,0:0|0:0,0:0:0:0:
|
||||
235,301,82692,2,0,P|177:296|141:279,1,70,8|2,0:0|0:0,0:0:0:0:
|
||||
68,244,83058,6,0,L|72:328,1,70,6|0,0:0|0:0,0:0:0:0:
|
||||
166,292,83424,2,0,L|157:372,1,70,8|0,0:0|0:0,0:0:0:0:
|
||||
254,227,83789,2,0,L|258:310,1,70,8|2,0:0|0:0,0:0:0:0:
|
||||
345,265,84155,2,0,L|336:349,1,70,8|0,0:0|0:0,0:0:0:0:
|
||||
331,175,84521,5,2,0:0:0:0:
|
||||
416,205,84704,1,2,0:0:0:0:
|
||||
481,141,84887,1,8,0:0:0:0:
|
||||
431,64,85070,2,0,L|444:26,2,35,8|8|2,0:0|0:0|0:0,0:0:0:0:
|
||||
339,79,85436,2,0,L|341:39,2,35,8|8|8,0:0|0:0|0:0,0:0:0:0:
|
||||
256,109,85802,1,2,0:0:0:0:
|
||||
165,97,85985,6,0,P|167:150|164:187,1,70,2|0,0:0|0:0,0:0:0:0:
|
||||
117,244,86350,2,0,P|163:241|204:235,1,70,8|0,0:0|0:0,0:0:0:0:
|
||||
229,317,86716,2,0,P|273:305|300:294,1,70,8|2,0:0|0:0,0:0:0:0:
|
||||
365,354,87082,2,0,P|404:334|430:310,1,70,8|0,0:0|0:0,0:0:0:0:
|
||||
352,230,87448,6,0,L|271:216,2,70,6|0|8,0:0|0:0|0:0,0:0:0:0:
|
||||
378,142,87997,2,0,L|222:144,1,140,0|2,0:0|0:0,0:0:0:0:
|
||||
152,112,88546,2,0,L|166:214,1,70,8|2,0:0|0:0,0:0:0:0:
|
||||
139,270,88911,5,8,0:0:0:0:
|
||||
12,138,89277,2,0,L|29:55,1,70,8|0,0:0|0:0,0:0:0:0:
|
||||
91,5,89643,2,0,L|104:97,1,70,8|2,0:0|0:0,0:0:0:0:
|
||||
153,149,90009,2,0,L|175:78,1,70,8|0,0:0|0:0,0:0:0:0:
|
||||
279,36,90375,6,0,L|357:27,2,70,6|0|8,0:0|0:0|0:0,0:0:0:0:
|
||||
248,122,90924,2,0,L|398:125,1,140,0|2,0:0|0:0,0:0:0:0:
|
||||
479,123,91472,2,0,P|468:170|445:195,1,70,8|2,0:0|0:0,0:0:0:0:
|
||||
365,204,91838,6,0,P|414:220|409:320,1,140,6|8,0:0|0:0,0:0:0:0:
|
||||
354,354,92387,1,0,0:0:0:0:
|
||||
262,353,92570,2,0,L|271:273,1,70,8|2,0:0|0:0,0:0:0:0:
|
||||
297,196,92936,2,0,P|243:198|216:215,1,70,8|0,0:0|0:0,0:0:0:0:
|
||||
172,276,93302,5,6,0:0:0:0:
|
||||
137,360,93485,2,0,L|127:265,1,70,0|8,0:0|0:0,0:0:0:0:
|
||||
81,212,93850,2,0,P|93:138|118:67,1,140,0|2,0:0|0:0,0:0:0:0:
|
||||
170,4,94399,2,0,P|195:37|204:74,1,70,8|2,0:0|0:0,0:0:0:0:
|
||||
186,153,94765,6,0,L|340:139,1,140,6|8,0:0|0:0,0:0:0:0:
|
||||
408,101,95314,1,2,0:0:0:0:
|
||||
443,184,95497,1,6,0:0:0:0:
|
||||
369,237,95680,2,0,L|300:224,2,70,8|8|2,0:0|0:0|0:0,0:0:0:0:
|
||||
448,282,96228,5,12,0:0:0:0:
|
||||
448,282,96411,1,12,0:0:0:0:
|
||||
270,320,96777,1,8,0:0:0:0:
|
||||
313,143,97143,1,8,0:0:0:0:
|
||||
377,314,97509,1,8,0:0:0:0:
|
||||
256,192,97692,12,0,100619,0:0:0:0:
|
||||
+1
File diff suppressed because one or more lines are too long
@@ -0,0 +1,126 @@
|
||||
osu file format v5
|
||||
|
||||
[General]
|
||||
StackLeniency: 0.7
|
||||
Mode: 0
|
||||
|
||||
[Difficulty]
|
||||
HPDrainRate:2
|
||||
CircleSize:5
|
||||
OverallDifficulty:2
|
||||
SliderMultiplier:1
|
||||
SliderTickRate:2
|
||||
|
||||
[Events]
|
||||
//Background and Video events
|
||||
//Break Periods
|
||||
//Storyboard Layer 0 (Background)
|
||||
//Storyboard Layer 1 (Failing)
|
||||
//Storyboard Layer 2 (Passing)
|
||||
//Storyboard Layer 3 (Foreground)
|
||||
//Storyboard Sound Samples
|
||||
//Background Colour Transformations
|
||||
3,100,163,162,255
|
||||
|
||||
[TimingPoints]
|
||||
7460,466.735154027506,4,1,0,100
|
||||
|
||||
[HitObjects]
|
||||
80,56,7693,1,0
|
||||
120,96,8043,1,0
|
||||
176,104,8393,1,0
|
||||
216,104,8626,1,0
|
||||
256,104,8860,1,0
|
||||
296,168,9326,5,0
|
||||
296,208,9560,1,0
|
||||
296,248,9793,1,0
|
||||
216,256,10260,1,0
|
||||
176,256,10493,1,0
|
||||
136,256,10727,1,0
|
||||
136,136,11427,5,0
|
||||
136,72,11777,1,0
|
||||
192,72,12127,1,0
|
||||
232,72,12360,1,0
|
||||
272,72,12594,1,0
|
||||
280,152,13060,5,0
|
||||
280,192,13294,1,0
|
||||
280,232,13527,1,0
|
||||
360,240,13994,1,0
|
||||
400,240,14227,1,0
|
||||
440,240,14461,1,0
|
||||
256,192,14927,12,0,16561
|
||||
256,192,16794,12,0,18078
|
||||
192,96,18661,6,0,B|312:96,1,100
|
||||
288,176,19595,2,0,B|168:176,1,100
|
||||
192,256,20528,2,0,B|312:256,1,100
|
||||
304,176,21462,2,0,B|240:176|248:88,1,100
|
||||
168,104,22395,5,0
|
||||
128,104,22628,2,0,B|296:368,1,300
|
||||
328,352,24262,5,0
|
||||
368,352,24495,1,0
|
||||
368,232,25195,1,0
|
||||
368,192,25429,1,0
|
||||
280,104,26129,5,0
|
||||
240,104,26362,2,0,B|40:352,1,300
|
||||
88,336,27996,5,0
|
||||
128,336,28229,1,0
|
||||
136,216,28929,1,0
|
||||
136,176,29163,1,0
|
||||
256,176,29863,5,0
|
||||
312,176,30213,1,0
|
||||
352,176,30446,2,0,B|360:264|360:280|360:272|272:272,1,150
|
||||
208,232,31730,5,0
|
||||
208,168,32080,1,0
|
||||
208,104,32430,1,0
|
||||
248,104,32663,1,0
|
||||
248,104,32780,1,0
|
||||
120,160,33597,5,0
|
||||
120,216,33947,1,0
|
||||
120,256,34180,2,0,B|352:256,1,225
|
||||
344,216,35464,6,0,B|200:128,1,150
|
||||
176,136,36397,2,0,B|176:288,1,150
|
||||
296,288,37564,6,0,B|296:208,1,75
|
||||
296,152,38264,2,0,B|296:104,2,25
|
||||
248,32,39197,1,0
|
||||
208,32,39431,1,0
|
||||
168,32,39664,1,0
|
||||
168,72,39898,2,0,B|168:136,4,50
|
||||
104,128,41298,5,0
|
||||
168,136,41648,1,0
|
||||
208,184,41998,1,0
|
||||
232,216,42231,1,0
|
||||
344,248,42931,5,0
|
||||
344,208,43165,1,0
|
||||
344,168,43398,1,0
|
||||
304,168,43631,1,0
|
||||
264,168,43865,1,0
|
||||
224,168,44098,1,0
|
||||
184,168,44332,1,0
|
||||
144,168,44565,1,0
|
||||
104,176,44798,6,0,B|32:240|160:272,1,150
|
||||
192,272,45732,2,0,B|280:272|320:200,1,150
|
||||
320,160,46665,2,0,B|248:96|176:136,1,150
|
||||
144,144,47599,2,0,B|48:168,1,75
|
||||
112,256,48532,6,0,B|256:336,1,150
|
||||
280,320,49466,2,0,B|416:240,1,150
|
||||
408,200,50399,2,0,B|256:136,1,150
|
||||
232,144,51333,2,0,B|80:208,1,150
|
||||
56,216,52266,5,0
|
||||
96,216,52499,1,0
|
||||
152,216,52849,2,0,B|248:216,1,75
|
||||
328,88,54133,5,0
|
||||
328,88,54366,1,0
|
||||
328,88,54600,1,0
|
||||
248,88,55066,5,0
|
||||
248,88,55300,1,0
|
||||
248,88,55533,1,0
|
||||
256,168,56000,6,0,B|184:168,1,50
|
||||
144,168,56583,1,0
|
||||
144,168,56700,1,0
|
||||
104,168,56933,1,0
|
||||
264,168,57867,5,0
|
||||
264,168,58100,1,0
|
||||
264,168,58334,1,0
|
||||
344,168,58800,5,0
|
||||
344,168,59034,1,0
|
||||
344,168,59267,1,0
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
{
|
||||
"Mappings": [
|
||||
{
|
||||
"RandomW": 2659373485,
|
||||
"RandomX": 3579807591,
|
||||
"RandomY": 273326509,
|
||||
"RandomZ": 272969173,
|
||||
"StartTime": 500.0,
|
||||
"Objects": [
|
||||
{
|
||||
"StartTime": 500.0,
|
||||
"EndTime": 2500.0,
|
||||
"Column": 0
|
||||
},
|
||||
{
|
||||
"StartTime": 1500.0,
|
||||
"EndTime": 2500.0,
|
||||
"Column": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"RandomW": 3083803045,
|
||||
"RandomX": 273326509,
|
||||
"RandomY": 272969173,
|
||||
"RandomZ": 2659373485,
|
||||
"StartTime": 3000.0,
|
||||
"Objects": [
|
||||
{
|
||||
"StartTime": 3000.0,
|
||||
"EndTime": 4000.0,
|
||||
"Column": 2
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"RandomW": 4073554232,
|
||||
"RandomX": 272969173,
|
||||
"RandomY": 2659373485,
|
||||
"RandomZ": 3083803045,
|
||||
"StartTime": 4500.0,
|
||||
"Objects": [
|
||||
{
|
||||
"StartTime": 4500.0,
|
||||
"EndTime": 5500.0,
|
||||
"Column": 4
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"RandomW": 3420401969,
|
||||
"RandomX": 2659373485,
|
||||
"RandomY": 3083803045,
|
||||
"RandomZ": 4073554232,
|
||||
"StartTime": 6000.0,
|
||||
"Objects": [
|
||||
{
|
||||
"StartTime": 6000.0,
|
||||
"EndTime": 6500.0,
|
||||
"Column": 2
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"RandomW": 1129881182,
|
||||
"RandomX": 3083803045,
|
||||
"RandomY": 4073554232,
|
||||
"RandomZ": 3420401969,
|
||||
"StartTime": 7000.0,
|
||||
"Objects": [
|
||||
{
|
||||
"StartTime": 7000.0,
|
||||
"EndTime": 8000.0,
|
||||
"Column": 2
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"RandomW": 315568458,
|
||||
"RandomX": 3420401969,
|
||||
"RandomY": 1129881182,
|
||||
"RandomZ": 2358617505,
|
||||
"StartTime": 8500.0,
|
||||
"Objects": [
|
||||
{
|
||||
"StartTime": 8500.0,
|
||||
"EndTime": 11000.0,
|
||||
"Column": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"RandomW": 548134043,
|
||||
"RandomX": 1129881182,
|
||||
"RandomY": 2358617505,
|
||||
"RandomZ": 315568458,
|
||||
"StartTime": 11500.0,
|
||||
"Objects": [
|
||||
{
|
||||
"StartTime": 11500.0,
|
||||
"EndTime": 12000.0,
|
||||
"Column": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"RandomW": 3979422122,
|
||||
"RandomX": 548134043,
|
||||
"RandomY": 2810584254,
|
||||
"RandomZ": 2250186050,
|
||||
"StartTime": 12500.0,
|
||||
"Objects": [
|
||||
{
|
||||
"StartTime": 12500.0,
|
||||
"EndTime": 16500.0,
|
||||
"Column": 4
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"RandomW": 2466283411,
|
||||
"RandomX": 2810584254,
|
||||
"RandomY": 2250186050,
|
||||
"RandomZ": 3979422122,
|
||||
"StartTime": 17000.0,
|
||||
"Objects": [
|
||||
{
|
||||
"StartTime": 17000.0,
|
||||
"EndTime": 18000.0,
|
||||
"Column": 2
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"RandomW": 83157665,
|
||||
"RandomX": 2250186050,
|
||||
"RandomY": 3979422122,
|
||||
"RandomZ": 2466283411,
|
||||
"StartTime": 18500.0,
|
||||
"Objects": [
|
||||
{
|
||||
"StartTime": 18500.0,
|
||||
"EndTime": 19450.0,
|
||||
"Column": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"RandomW": 2383087700,
|
||||
"RandomX": 83157665,
|
||||
"RandomY": 2055150192,
|
||||
"RandomZ": 510071020,
|
||||
"StartTime": 19875.0,
|
||||
"Objects": [
|
||||
{
|
||||
"StartTime": 19875.0,
|
||||
"EndTime": 23875.0,
|
||||
"Column": 1
|
||||
},
|
||||
{
|
||||
"StartTime": 19875.0,
|
||||
"EndTime": 23875.0,
|
||||
"Column": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
+27
-27
@@ -1,27 +1,27 @@
|
||||
osu file format v14
|
||||
|
||||
[Difficulty]
|
||||
HPDrainRate:6
|
||||
CircleSize:4
|
||||
OverallDifficulty:7
|
||||
ApproachRate:8.3
|
||||
SliderMultiplier:1.6
|
||||
SliderTickRate:1
|
||||
|
||||
[TimingPoints]
|
||||
500,500,4,2,1,50,1,0
|
||||
13426,-100,4,3,1,45,0,0
|
||||
14884,-100,4,2,1,50,0,0
|
||||
|
||||
[HitObjects]
|
||||
96,192,500,6,0,L|416:192,2,320
|
||||
256,192,3000,12,0,4000,0:0:0:0:
|
||||
256,192,4500,12,0,5500,0:0:0:0:
|
||||
256,192,6000,12,0,6500,0:0:0:0:
|
||||
256,128,7000,6,0,L|352:128,4,80
|
||||
32,192,8500,6,0,B|32:384|256:384|256:192|256:192|256:0|512:0|512:192,1,800
|
||||
256,192,11500,12,0,12000,0:0:0:0:
|
||||
512,320,12500,6,0,B|0:256|0:256|512:96|512:96|256:32,1,1280
|
||||
256,256,17000,6,0,L|160:256,4,80
|
||||
256,192,18500,12,0,19450,0:0:0:0:
|
||||
216,231,19875,6,0,B|216:135|280:135|344:135|344:199|344:263|248:327|248:327|120:327|120:327|56:39|408:39|408:39|472:150|408:342,1,1280
|
||||
osu file format v14
|
||||
|
||||
[Difficulty]
|
||||
HPDrainRate:6
|
||||
CircleSize:4
|
||||
OverallDifficulty:7
|
||||
ApproachRate:8.3
|
||||
SliderMultiplier:1.6
|
||||
SliderTickRate:1
|
||||
|
||||
[TimingPoints]
|
||||
500,500,4,2,1,50,1,0
|
||||
13426,-100,4,3,1,45,0,0
|
||||
14884,-100,4,2,1,50,0,0
|
||||
|
||||
[HitObjects]
|
||||
96,192,500,6,0,L|416:192,2,320
|
||||
256,192,3000,12,0,4000,0:0:0:0:
|
||||
256,192,4500,12,0,5500,0:0:0:0:
|
||||
256,192,6000,12,0,6500,0:0:0:0:
|
||||
256,128,7000,6,0,L|352:128,4,80
|
||||
32,192,8500,6,0,B|32:384|256:384|256:192|256:192|256:0|512:0|512:192,1,800
|
||||
256,192,11500,12,0,12000,0:0:0:0:
|
||||
512,320,12500,6,0,B|0:256|0:256|512:96|512:96|256:32,1,1280
|
||||
256,256,17000,6,0,L|160:256,4,80
|
||||
256,192,18500,12,0,19450,0:0:0:0:
|
||||
216,231,19875,6,0,B|216:135|280:135|344:135|344:199|344:263|248:327|248:327|120:327|120:327|56:39|408:39|408:39|472:150|408:342,1,1280
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"Mappings": [
|
||||
{
|
||||
"RandomW": 3083084786,
|
||||
"RandomX": 273326509,
|
||||
"RandomY": 273553282,
|
||||
"RandomZ": 2659838971,
|
||||
"StartTime": 4836.0,
|
||||
"Objects": [
|
||||
{
|
||||
"StartTime": 4836.0,
|
||||
"EndTime": 4836.0,
|
||||
"Column": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -200,12 +200,10 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
});
|
||||
|
||||
assertHeadJudgement(HitResult.Perfect);
|
||||
// judgement combo offset by perfect bonus judgement. see logic in DrawableNote.CheckForResult.
|
||||
assertComboAtJudgement(1, 1);
|
||||
assertComboAtJudgement(0, 1);
|
||||
assertTailJudgement(HitResult.Meh);
|
||||
assertComboAtJudgement(2, 0);
|
||||
// judgement combo offset by perfect bonus judgement. see logic in DrawableNote.CheckForResult.
|
||||
assertComboAtJudgement(4, 1);
|
||||
assertComboAtJudgement(1, 0);
|
||||
assertComboAtJudgement(3, 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -54,7 +54,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
AddAssert("all objects perfectly judged",
|
||||
() => judgementResults.Select(result => result.Type),
|
||||
() => Is.EquivalentTo(judgementResults.Select(result => result.Judgement.MaxResult)));
|
||||
AddAssert("score is correct", () => currentPlayer.ScoreProcessor.TotalScore.Value, () => Is.EqualTo(1_000_030));
|
||||
AddAssert("score is correct", () => currentPlayer.ScoreProcessor.TotalScore.Value, () => Is.EqualTo(1_000_000));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
AddAssert("all objects perfectly judged",
|
||||
() => judgementResults.Select(result => result.Type),
|
||||
() => Is.EquivalentTo(judgementResults.Select(result => result.Judgement.MaxResult)));
|
||||
AddAssert("score is correct", () => currentPlayer.ScoreProcessor.TotalScore.Value, () => Is.EqualTo(1_000_040));
|
||||
AddAssert("score is correct", () => currentPlayer.ScoreProcessor.TotalScore.Value, () => Is.EqualTo(1_000_000));
|
||||
}
|
||||
|
||||
private void performTest(List<ManiaHitObject> hitObjects, List<ReplayFrame> frames)
|
||||
|
||||
@@ -57,31 +57,32 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
||||
|
||||
public static int GetColumnCount(LegacyBeatmapConversionDifficultyInfo difficulty)
|
||||
{
|
||||
if (new ManiaRuleset().RulesetInfo.Equals(difficulty.SourceRuleset))
|
||||
return GetColumnCountForNonConvert(difficulty);
|
||||
|
||||
double roundedCircleSize = Math.Round(difficulty.CircleSize);
|
||||
|
||||
if (difficulty.SourceRuleset.ShortName == ManiaRuleset.SHORT_NAME)
|
||||
return (int)Math.Max(1, roundedCircleSize);
|
||||
|
||||
double roundedOverallDifficulty = Math.Round(difficulty.OverallDifficulty);
|
||||
|
||||
int countSliderOrSpinner = difficulty.TotalObjectCount - difficulty.CircleCount;
|
||||
float percentSpecialObjects = (float)countSliderOrSpinner / difficulty.TotalObjectCount;
|
||||
if (difficulty.TotalObjectCount > 0 && difficulty.EndTimeObjectCount >= 0)
|
||||
{
|
||||
int countSliderOrSpinner = difficulty.EndTimeObjectCount;
|
||||
|
||||
if (percentSpecialObjects < 0.2)
|
||||
return 7;
|
||||
if (percentSpecialObjects < 0.3 || roundedCircleSize >= 5)
|
||||
return roundedOverallDifficulty > 5 ? 7 : 6;
|
||||
if (percentSpecialObjects > 0.6)
|
||||
return roundedOverallDifficulty > 4 ? 5 : 4;
|
||||
// In osu!stable, this division appears as if it happens on floats, but due to release-mode
|
||||
// optimisations, it actually ends up happening on doubles.
|
||||
double percentSpecialObjects = (double)countSliderOrSpinner / difficulty.TotalObjectCount;
|
||||
|
||||
if (percentSpecialObjects < 0.2)
|
||||
return 7;
|
||||
if (percentSpecialObjects < 0.3 || roundedCircleSize >= 5)
|
||||
return roundedOverallDifficulty > 5 ? 7 : 6;
|
||||
if (percentSpecialObjects > 0.6)
|
||||
return roundedOverallDifficulty > 4 ? 5 : 4;
|
||||
}
|
||||
|
||||
return Math.Max(4, Math.Min((int)roundedOverallDifficulty + 1, 7));
|
||||
}
|
||||
|
||||
public static int GetColumnCountForNonConvert(IBeatmapDifficultyInfo difficulty)
|
||||
{
|
||||
double roundedCircleSize = Math.Round(difficulty.CircleSize);
|
||||
return (int)Math.Max(1, roundedCircleSize);
|
||||
}
|
||||
|
||||
public override bool CanConvert() => Beatmap.HitObjects.All(h => h is IHasXPosition);
|
||||
|
||||
protected override Beatmap<ManiaHitObject> ConvertBeatmap(IBeatmap original, CancellationToken cancellationToken)
|
||||
|
||||
@@ -15,7 +15,11 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
||||
{
|
||||
public LegacyScoreAttributes Simulate(IWorkingBeatmap workingBeatmap, IBeatmap playableBeatmap)
|
||||
{
|
||||
return new LegacyScoreAttributes { ComboScore = 1000000 };
|
||||
return new LegacyScoreAttributes
|
||||
{
|
||||
ComboScore = 1000000,
|
||||
MaxCombo = 0 // Max combo is mod-dependent, so any value here is insufficient.
|
||||
};
|
||||
}
|
||||
|
||||
public double GetLegacyScoreMultiplier(IReadOnlyList<Mod> mods, LegacyBeatmapConversionDifficultyInfo difficulty)
|
||||
|
||||
@@ -5,6 +5,7 @@ using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
@@ -23,7 +24,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||
[Resolved]
|
||||
private IScrollingInfo scrollingInfo { get; set; } = null!;
|
||||
|
||||
protected override bool IsValidForPlacement => HitObject.Duration > 0;
|
||||
protected override bool IsValidForPlacement => Precision.DefinitelyBigger(HitObject.Duration, 0);
|
||||
|
||||
public HoldNotePlacementBlueprint()
|
||||
: base(new HoldNote())
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Filter;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Scoring.Legacy;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.Select.Filter;
|
||||
|
||||
@@ -15,7 +16,7 @@ namespace osu.Game.Rulesets.Mania
|
||||
|
||||
public bool Matches(BeatmapInfo beatmapInfo)
|
||||
{
|
||||
return !keys.HasFilter || (beatmapInfo.Ruleset.OnlineID == new ManiaRuleset().LegacyID && keys.IsInRange(ManiaBeatmapConverter.GetColumnCountForNonConvert(beatmapInfo.Difficulty)));
|
||||
return !keys.HasFilter || keys.IsInRange(ManiaBeatmapConverter.GetColumnCount(LegacyBeatmapConversionDifficultyInfo.FromBeatmapInfo(beatmapInfo)));
|
||||
}
|
||||
|
||||
public bool TryParseCustomKeywordCriteria(string key, Operator op, string value)
|
||||
|
||||
@@ -420,6 +420,9 @@ namespace osu.Game.Rulesets.Mania
|
||||
public override RulesetSetupSection CreateEditorSetupSection() => new ManiaSetupSection();
|
||||
|
||||
public override DifficultySection CreateEditorDifficultySection() => new ManiaDifficultySection();
|
||||
|
||||
public int GetKeyCount(IBeatmapInfo beatmapInfo)
|
||||
=> ManiaBeatmapConverter.GetColumnCount(LegacyBeatmapConversionDifficultyInfo.FromBeatmapInfo(beatmapInfo));
|
||||
}
|
||||
|
||||
public enum PlayfieldType
|
||||
|
||||
@@ -10,5 +10,10 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
public class ManiaModDoubleTime : ModDoubleTime, IManiaRateAdjustmentMod
|
||||
{
|
||||
public HitWindows HitWindows { get; set; } = new ManiaHitWindows();
|
||||
|
||||
// For now, all rate-increasing mods should be given a 1x multiplier in mania because it doesn't always
|
||||
// make the map harder and is more of a personal preference.
|
||||
// In the future, we can consider adjusting this by experimenting with not applying the hitwindow leniency.
|
||||
public override double ScoreMultiplier => 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,5 +11,10 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
public class ManiaModNightcore : ModNightcore<ManiaHitObject>, IManiaRateAdjustmentMod
|
||||
{
|
||||
public HitWindows HitWindows { get; set; } = new ManiaHitWindows();
|
||||
|
||||
// For now, all rate-increasing mods should be given a 1x multiplier in mania because it doesn't always
|
||||
// make the map any harder and is more of a personal preference.
|
||||
// In the future, we can consider adjusting this by experimenting with not applying the hitwindow leniency.
|
||||
public override double ScoreMultiplier => 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,26 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Mods
|
||||
{
|
||||
public class ManiaModPerfect : ModPerfect
|
||||
{
|
||||
protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result)
|
||||
{
|
||||
if (!isRelevantResult(result.Judgement.MinResult) && !isRelevantResult(result.Judgement.MaxResult) && !isRelevantResult(result.Type))
|
||||
return false;
|
||||
|
||||
// Mania allows imperfect "Great" hits without failing.
|
||||
if (result.Judgement.MaxResult == HitResult.Perfect)
|
||||
return result.Type < HitResult.Great;
|
||||
|
||||
return result.Type != result.Judgement.MaxResult;
|
||||
}
|
||||
|
||||
private bool isRelevantResult(HitResult result) => result.AffectsAccuracy() || result.AffectsCombo();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,6 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Mania.Configuration;
|
||||
using osu.Game.Rulesets.Mania.Skinning.Default;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Screens.Edit;
|
||||
@@ -40,8 +38,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
|
||||
private Drawable headPiece;
|
||||
|
||||
private DrawableNotePerfectBonus perfectBonus;
|
||||
|
||||
public DrawableNote()
|
||||
: this(null)
|
||||
{
|
||||
@@ -93,10 +89,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
if (!userTriggered)
|
||||
{
|
||||
if (!HitObject.HitWindows.CanBeHit(timeOffset))
|
||||
{
|
||||
perfectBonus.TriggerResult(false);
|
||||
ApplyResult(r => r.Type = r.Judgement.MinResult);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -107,16 +100,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
|
||||
result = GetCappedResult(result);
|
||||
|
||||
perfectBonus.TriggerResult(result == HitResult.Perfect);
|
||||
ApplyResult(r => r.Type = result);
|
||||
}
|
||||
|
||||
public override void MissForcefully()
|
||||
{
|
||||
perfectBonus.TriggerResult(false);
|
||||
base.MissForcefully();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Some objects in mania may want to limit the max result.
|
||||
/// </summary>
|
||||
@@ -137,32 +123,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
{
|
||||
}
|
||||
|
||||
protected override void AddNestedHitObject(DrawableHitObject hitObject)
|
||||
{
|
||||
switch (hitObject)
|
||||
{
|
||||
case DrawableNotePerfectBonus bonus:
|
||||
AddInternal(perfectBonus = bonus);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ClearNestedHitObjects()
|
||||
{
|
||||
RemoveInternal(perfectBonus, false);
|
||||
}
|
||||
|
||||
protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
|
||||
{
|
||||
switch (hitObject)
|
||||
{
|
||||
case NotePerfectBonus bonus:
|
||||
return new DrawableNotePerfectBonus(bonus);
|
||||
}
|
||||
|
||||
return base.CreateNestedHitObject(hitObject);
|
||||
}
|
||||
|
||||
private void updateSnapColour()
|
||||
{
|
||||
if (beatmap == null || HitObject == null) return;
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
// 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.
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
{
|
||||
public partial class DrawableNotePerfectBonus : DrawableManiaHitObject<NotePerfectBonus>
|
||||
{
|
||||
public override bool DisplayResult => false;
|
||||
|
||||
public DrawableNotePerfectBonus()
|
||||
: this(null!)
|
||||
{
|
||||
}
|
||||
|
||||
public DrawableNotePerfectBonus(NotePerfectBonus hitObject)
|
||||
: base(hitObject)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply a judgement result.
|
||||
/// </summary>
|
||||
/// <param name="hit">Whether this tick was reached.</param>
|
||||
internal void TriggerResult(bool hit) => ApplyResult(r => r.Type = hit ? r.Judgement.MaxResult : r.Judgement.MinResult);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Threading;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mania.Judgements;
|
||||
|
||||
@@ -13,12 +12,5 @@ namespace osu.Game.Rulesets.Mania.Objects
|
||||
public class Note : ManiaHitObject
|
||||
{
|
||||
public override Judgement CreateJudgement() => new ManiaJudgement();
|
||||
|
||||
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
||||
{
|
||||
base.CreateNestedHitObjects(cancellationToken);
|
||||
|
||||
AddNested(new NotePerfectBonus { StartTime = StartTime });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mania.Judgements;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Objects
|
||||
{
|
||||
public class NotePerfectBonus : ManiaHitObject
|
||||
{
|
||||
public override Judgement CreateJudgement() => new NotePerfectBonusJudgement();
|
||||
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
|
||||
|
||||
public class NotePerfectBonusJudgement : ManiaJudgement
|
||||
{
|
||||
public override HitResult MaxResult => HitResult.SmallBonus;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,15 +87,22 @@ namespace osu.Game.Rulesets.Mania.Replays
|
||||
private double calculateReleaseTime(HitObject currentObject, HitObject? nextObject)
|
||||
{
|
||||
double endTime = currentObject.GetEndTime();
|
||||
double releaseDelay = RELEASE_DELAY;
|
||||
|
||||
if (currentObject is HoldNote)
|
||||
// hold note releases must be timed exactly.
|
||||
return endTime;
|
||||
if (currentObject is HoldNote hold)
|
||||
{
|
||||
if (hold.Duration > 0)
|
||||
// hold note releases must be timed exactly.
|
||||
return endTime;
|
||||
|
||||
// Special case for super short hold notes
|
||||
releaseDelay = 1;
|
||||
}
|
||||
|
||||
bool canDelayKeyUpFully = nextObject == null ||
|
||||
nextObject.StartTime > endTime + RELEASE_DELAY;
|
||||
nextObject.StartTime > endTime + releaseDelay;
|
||||
|
||||
return endTime + (canDelayKeyUpFully ? RELEASE_DELAY : (nextObject.AsNonNull().StartTime - endTime) * 0.9);
|
||||
return endTime + (canDelayKeyUpFully ? releaseDelay : (nextObject.AsNonNull().StartTime - endTime) * 0.9);
|
||||
}
|
||||
|
||||
protected override HitObject? GetNextObject(int currentIndex)
|
||||
|
||||
@@ -1,132 +0,0 @@
|
||||
{
|
||||
"Mappings": [{
|
||||
"RandomW": 2659373485,
|
||||
"RandomX": 3579807591,
|
||||
"RandomY": 273326509,
|
||||
"RandomZ": 272969173,
|
||||
"StartTime": 500.0,
|
||||
"Objects": [{
|
||||
"StartTime": 500.0,
|
||||
"EndTime": 2500.0,
|
||||
"Column": 0
|
||||
}, {
|
||||
"StartTime": 1500.0,
|
||||
"EndTime": 2500.0,
|
||||
"Column": 1
|
||||
}]
|
||||
}, {
|
||||
"RandomW": 3083803045,
|
||||
"RandomX": 273326509,
|
||||
"RandomY": 272969173,
|
||||
"RandomZ": 2659373485,
|
||||
"StartTime": 3000.0,
|
||||
"Objects": [{
|
||||
"StartTime": 3000.0,
|
||||
"EndTime": 4000.0,
|
||||
"Column": 2
|
||||
}]
|
||||
}, {
|
||||
"RandomW": 4073554232,
|
||||
"RandomX": 272969173,
|
||||
"RandomY": 2659373485,
|
||||
"RandomZ": 3083803045,
|
||||
"StartTime": 4500.0,
|
||||
"Objects": [{
|
||||
"StartTime": 4500.0,
|
||||
"EndTime": 5500.0,
|
||||
"Column": 4
|
||||
}]
|
||||
}, {
|
||||
"RandomW": 3420401969,
|
||||
"RandomX": 2659373485,
|
||||
"RandomY": 3083803045,
|
||||
"RandomZ": 4073554232,
|
||||
"StartTime": 6000.0,
|
||||
"Objects": [{
|
||||
"StartTime": 6000.0,
|
||||
"EndTime": 6500.0,
|
||||
"Column": 2
|
||||
}]
|
||||
}, {
|
||||
"RandomW": 1129881182,
|
||||
"RandomX": 3083803045,
|
||||
"RandomY": 4073554232,
|
||||
"RandomZ": 3420401969,
|
||||
"StartTime": 7000.0,
|
||||
"Objects": [{
|
||||
"StartTime": 7000.0,
|
||||
"EndTime": 8000.0,
|
||||
"Column": 2
|
||||
}]
|
||||
}, {
|
||||
"RandomW": 315568458,
|
||||
"RandomX": 3420401969,
|
||||
"RandomY": 1129881182,
|
||||
"RandomZ": 2358617505,
|
||||
"StartTime": 8500.0,
|
||||
"Objects": [{
|
||||
"StartTime": 8500.0,
|
||||
"EndTime": 11000.0,
|
||||
"Column": 0
|
||||
}]
|
||||
}, {
|
||||
"RandomW": 548134043,
|
||||
"RandomX": 1129881182,
|
||||
"RandomY": 2358617505,
|
||||
"RandomZ": 315568458,
|
||||
"StartTime": 11500.0,
|
||||
"Objects": [{
|
||||
"StartTime": 11500.0,
|
||||
"EndTime": 12000.0,
|
||||
"Column": 1
|
||||
}]
|
||||
}, {
|
||||
"RandomW": 3979422122,
|
||||
"RandomX": 548134043,
|
||||
"RandomY": 2810584254,
|
||||
"RandomZ": 2250186050,
|
||||
"StartTime": 12500.0,
|
||||
"Objects": [{
|
||||
"StartTime": 12500.0,
|
||||
"EndTime": 16500.0,
|
||||
"Column": 4
|
||||
}]
|
||||
}, {
|
||||
"RandomW": 2466283411,
|
||||
"RandomX": 2810584254,
|
||||
"RandomY": 2250186050,
|
||||
"RandomZ": 3979422122,
|
||||
"StartTime": 17000.0,
|
||||
"Objects": [{
|
||||
"StartTime": 17000.0,
|
||||
"EndTime": 18000.0,
|
||||
"Column": 2
|
||||
}]
|
||||
}, {
|
||||
"RandomW": 83157665,
|
||||
"RandomX": 2250186050,
|
||||
"RandomY": 3979422122,
|
||||
"RandomZ": 2466283411,
|
||||
"StartTime": 18500.0,
|
||||
"Objects": [{
|
||||
"StartTime": 18500.0,
|
||||
"EndTime": 19450.0,
|
||||
"Column": 0
|
||||
}]
|
||||
}, {
|
||||
"RandomW": 2383087700,
|
||||
"RandomX": 83157665,
|
||||
"RandomY": 2055150192,
|
||||
"RandomZ": 510071020,
|
||||
"StartTime": 19875.0,
|
||||
"Objects": [{
|
||||
"StartTime": 19875.0,
|
||||
"EndTime": 23875.0,
|
||||
"Column": 1
|
||||
}, {
|
||||
"StartTime": 19875.0,
|
||||
"EndTime": 23875.0,
|
||||
"Column": 0
|
||||
}]
|
||||
}]
|
||||
}
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"Mappings": [{
|
||||
"RandomW": 3083084786,
|
||||
"RandomX": 273326509,
|
||||
"RandomY": 273553282,
|
||||
"RandomZ": 2659838971,
|
||||
"StartTime": 4836,
|
||||
"Objects": [{
|
||||
"StartTime": 4836,
|
||||
"EndTime": 4836,
|
||||
"Column": 0
|
||||
}]
|
||||
}]
|
||||
}
|
||||
@@ -26,13 +26,37 @@ namespace osu.Game.Rulesets.Mania.Scoring
|
||||
|
||||
protected override double ComputeTotalScore(double comboProgress, double accuracyProgress, double bonusPortion)
|
||||
{
|
||||
return 10000 * comboProgress
|
||||
+ 990000 * Math.Pow(Accuracy.Value, 2 + 2 * Accuracy.Value) * accuracyProgress
|
||||
return 150000 * comboProgress
|
||||
+ 850000 * Math.Pow(Accuracy.Value, 2 + 2 * Accuracy.Value) * accuracyProgress
|
||||
+ bonusPortion;
|
||||
}
|
||||
|
||||
protected override double GetComboScoreChange(JudgementResult result)
|
||||
=> Judgement.ToNumericResult(result.Type) * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(400, combo_base));
|
||||
{
|
||||
return getBaseComboScoreForResult(result.Type) * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(400, combo_base));
|
||||
}
|
||||
|
||||
public override int GetBaseScoreForResult(HitResult result)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case HitResult.Perfect:
|
||||
return 305;
|
||||
}
|
||||
|
||||
return base.GetBaseScoreForResult(result);
|
||||
}
|
||||
|
||||
private int getBaseComboScoreForResult(HitResult result)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case HitResult.Perfect:
|
||||
return 300;
|
||||
}
|
||||
|
||||
return GetBaseScoreForResult(result);
|
||||
}
|
||||
|
||||
private class JudgementOrderComparer : IComparer<HitObject>
|
||||
{
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -234,7 +234,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
break;
|
||||
|
||||
default:
|
||||
// this is where things get fucked up.
|
||||
// this is where things get a bit messed up.
|
||||
// honestly there's three modes to handle here but they seem really pointless?
|
||||
// let's wait to see if anyone actually uses them in skins.
|
||||
if (bodySprite != null)
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,7 +109,6 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
TopLevelContainer.Add(HitObjectArea.Explosions.CreateProxy());
|
||||
|
||||
RegisterPool<Note, DrawableNote>(10, 50);
|
||||
RegisterPool<NotePerfectBonus, DrawableNotePerfectBonus>(10, 50);
|
||||
RegisterPool<HoldNote, DrawableHoldNote>(10, 50);
|
||||
RegisterPool<HeadNote, DrawableHoldNoteHead>(10, 50);
|
||||
RegisterPool<TailNote, DrawableHoldNoteTail>(10, 50);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -19,12 +19,14 @@ using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Configuration;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.Replays;
|
||||
using osu.Game.Rulesets.Mania.Skinning;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.UI
|
||||
{
|
||||
@@ -57,6 +59,9 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
// Stores the current speed adjustment active in gameplay.
|
||||
private readonly Track speedAdjustmentTrack = new TrackVirtual(0);
|
||||
|
||||
[Resolved]
|
||||
private ISkinSource skin { get; set; }
|
||||
|
||||
public DrawableManiaRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods = null)
|
||||
: base(ruleset, beatmap, mods)
|
||||
{
|
||||
@@ -104,7 +109,20 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
updateTimeRange();
|
||||
}
|
||||
|
||||
private void updateTimeRange() => TimeRange.Value = smoothTimeRange * speedAdjustmentTrack.AggregateTempo.Value * speedAdjustmentTrack.AggregateFrequency.Value;
|
||||
private void updateTimeRange()
|
||||
{
|
||||
float hitPosition = skin.GetConfig<ManiaSkinConfigurationLookup, float>(
|
||||
new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.HitPosition))?.Value
|
||||
?? Stage.HIT_TARGET_POSITION;
|
||||
|
||||
const float length_to_default_hit_position = 768 - LegacyManiaSkinConfiguration.DEFAULT_HIT_POSITION;
|
||||
float lengthToHitPosition = 768 - hitPosition;
|
||||
|
||||
// This scaling factor preserves the scroll speed as the scroll length varies from changes to the hit position.
|
||||
float scale = lengthToHitPosition / length_to_default_hit_position;
|
||||
|
||||
TimeRange.Value = smoothTimeRange * speedAdjustmentTrack.AggregateTempo.Value * speedAdjustmentTrack.AggregateFrequency.Value * scale;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes a scroll time (in milliseconds) from a scroll speed in the range of 1-40.
|
||||
|
||||
@@ -310,9 +310,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
AddStep("release left button", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||
|
||||
assertPlaced(true);
|
||||
assertLength(760, tolerance: 10);
|
||||
assertLength(808, tolerance: 10);
|
||||
assertControlPointCount(5);
|
||||
assertControlPointType(0, PathType.BSpline(3));
|
||||
assertControlPointType(0, PathType.BSpline(4));
|
||||
assertControlPointType(1, null);
|
||||
assertControlPointType(2, null);
|
||||
assertControlPointType(3, null);
|
||||
@@ -337,9 +337,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
assertPlaced(true);
|
||||
assertLength(600, tolerance: 10);
|
||||
assertControlPointCount(4);
|
||||
assertControlPointType(0, PathType.LINEAR);
|
||||
assertControlPointType(1, null);
|
||||
assertControlPointType(2, null);
|
||||
assertControlPointType(0, PathType.BSpline(4));
|
||||
assertControlPointType(1, PathType.BSpline(4));
|
||||
assertControlPointType(2, PathType.BSpline(4));
|
||||
assertControlPointType(3, null);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
// 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;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Replays;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
{
|
||||
public partial class TestSceneOsuModPerfect : ModPerfectTestScene
|
||||
public partial class TestSceneOsuModPerfect : ModFailConditionTestScene
|
||||
{
|
||||
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
|
||||
|
||||
@@ -50,5 +54,30 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
|
||||
CreateHitObjectTest(new HitObjectTestData(spinner), shouldMiss);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMissSliderTail() => CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new OsuModPerfect(),
|
||||
PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(true),
|
||||
Autoplay = false,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new Slider
|
||||
{
|
||||
Position = new Vector2(256, 192),
|
||||
StartTime = 1000,
|
||||
Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(100, 0), })
|
||||
},
|
||||
},
|
||||
},
|
||||
ReplayFrames = new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame(1000, new Vector2(256, 192), OsuAction.LeftButton),
|
||||
new OsuReplayFrame(1001, new Vector2(256, 192)),
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Replays;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
{
|
||||
public partial class TestSceneOsuModStrictTracking : OsuModTestScene
|
||||
{
|
||||
[Test]
|
||||
public void TestSliderInput() => CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new OsuModStrictTracking(),
|
||||
Autoplay = false,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new Slider
|
||||
{
|
||||
StartTime = 1000,
|
||||
Path = new SliderPath
|
||||
{
|
||||
ControlPoints =
|
||||
{
|
||||
new PathControlPoint(),
|
||||
new PathControlPoint(new Vector2(0, 100))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
ReplayFrames = new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame(0, new Vector2(), OsuAction.LeftButton),
|
||||
new OsuReplayFrame(500, new Vector2(200, 0), OsuAction.LeftButton),
|
||||
new OsuReplayFrame(501, new Vector2(200, 0)),
|
||||
new OsuReplayFrame(1000, new Vector2(), OsuAction.LeftButton),
|
||||
new OsuReplayFrame(1750, new Vector2(0, 100), OsuAction.LeftButton),
|
||||
new OsuReplayFrame(1751, new Vector2(0, 100)),
|
||||
},
|
||||
PassCondition = () => Player.ScoreProcessor.Combo.Value == 2
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
// 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;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Replays;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
{
|
||||
public partial class TestSceneOsuModSuddenDeath : ModFailConditionTestScene
|
||||
{
|
||||
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
|
||||
|
||||
public TestSceneOsuModSuddenDeath()
|
||||
: base(new OsuModSuddenDeath())
|
||||
{
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMissTail() => CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new OsuModSuddenDeath(),
|
||||
PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(false),
|
||||
Autoplay = false,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new Slider
|
||||
{
|
||||
Position = new Vector2(256, 192),
|
||||
StartTime = 1000,
|
||||
Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(100, 0), })
|
||||
},
|
||||
},
|
||||
},
|
||||
ReplayFrames = new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame(1000, new Vector2(256, 192), OsuAction.LeftButton),
|
||||
new OsuReplayFrame(1001, new Vector2(256, 192)),
|
||||
}
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestMissTick() => CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new OsuModSuddenDeath(),
|
||||
PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(true),
|
||||
Autoplay = false,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new Slider
|
||||
{
|
||||
Position = new Vector2(256, 192),
|
||||
StartTime = 1000,
|
||||
Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(200, 0), })
|
||||
},
|
||||
},
|
||||
},
|
||||
ReplayFrames = new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame(1000, new Vector2(256, 192), OsuAction.LeftButton),
|
||||
new OsuReplayFrame(1001, new Vector2(256, 192)),
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-5
@@ -10,12 +10,12 @@ using osu.Game.Rulesets.Osu.Scoring;
|
||||
namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestSceneOsuHealthProcessor
|
||||
public class TestSceneOsuLegacyHealthProcessor
|
||||
{
|
||||
[Test]
|
||||
public void TestNoBreak()
|
||||
{
|
||||
OsuHealthProcessor hp = new OsuHealthProcessor(-1000);
|
||||
OsuLegacyHealthProcessor hp = new OsuLegacyHealthProcessor(-1000);
|
||||
hp.ApplyBeatmap(new Beatmap<OsuHitObject>
|
||||
{
|
||||
HitObjects =
|
||||
@@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
[Test]
|
||||
public void TestSingleBreak()
|
||||
{
|
||||
OsuHealthProcessor hp = new OsuHealthProcessor(-1000);
|
||||
OsuLegacyHealthProcessor hp = new OsuLegacyHealthProcessor(-1000);
|
||||
hp.ApplyBeatmap(new Beatmap<OsuHitObject>
|
||||
{
|
||||
HitObjects =
|
||||
@@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
[Test]
|
||||
public void TestOverlappingBreak()
|
||||
{
|
||||
OsuHealthProcessor hp = new OsuHealthProcessor(-1000);
|
||||
OsuLegacyHealthProcessor hp = new OsuLegacyHealthProcessor(-1000);
|
||||
hp.ApplyBeatmap(new Beatmap<OsuHitObject>
|
||||
{
|
||||
HitObjects =
|
||||
@@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
[Test]
|
||||
public void TestSequentialBreak()
|
||||
{
|
||||
OsuHealthProcessor hp = new OsuHealthProcessor(-1000);
|
||||
OsuLegacyHealthProcessor hp = new OsuLegacyHealthProcessor(-1000);
|
||||
hp.ApplyBeatmap(new Beatmap<OsuHitObject>
|
||||
{
|
||||
HitObjects =
|
||||
@@ -0,0 +1,229 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Replays;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Replays;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
public partial class TestSceneSliderEarlyHitJudgement : RateAdjustedBeatmapTestScene
|
||||
{
|
||||
private const double time_slider_start = 1000;
|
||||
private const double time_slider_end = 3000;
|
||||
|
||||
private static readonly Vector2 slider_start_position = new Vector2(256 - slider_path_length / 2, 192);
|
||||
private static readonly Vector2 slider_end_position = new Vector2(256 + slider_path_length / 2, 192);
|
||||
private static readonly Vector2 offset_inside_follow = new Vector2(35, 0);
|
||||
private static readonly Vector2 offset_outside_follow = offset_inside_follow * 2;
|
||||
|
||||
private ScoreAccessibleReplayPlayer currentPlayer = null!;
|
||||
|
||||
private const float slider_path_length = 200;
|
||||
|
||||
private readonly List<JudgementResult> judgementResults = new List<JudgementResult>();
|
||||
|
||||
[Test]
|
||||
public void TestHitEarlyMoveIntoFollowRegion()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame(time_slider_start - 150, slider_start_position, OsuAction.LeftButton),
|
||||
new OsuReplayFrame(time_slider_start - 100, slider_start_position + offset_inside_follow, OsuAction.LeftButton),
|
||||
new OsuReplayFrame(time_slider_end - 100, slider_end_position + offset_inside_follow, OsuAction.LeftButton),
|
||||
});
|
||||
|
||||
assertHeadJudgement(HitResult.Meh);
|
||||
assertTickJudgement(HitResult.LargeTickHit);
|
||||
assertTailJudgement(HitResult.LargeTickHit);
|
||||
assertSliderJudgement(HitResult.IgnoreHit);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHitEarlyAndReleaseInFollowRegion()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame(time_slider_start - 150, slider_start_position, OsuAction.LeftButton),
|
||||
new OsuReplayFrame(time_slider_start - 100, slider_start_position + offset_inside_follow, OsuAction.LeftButton),
|
||||
new OsuReplayFrame(time_slider_start - 50, slider_start_position + offset_inside_follow),
|
||||
new OsuReplayFrame(time_slider_end - 50, slider_end_position + offset_inside_follow, OsuAction.LeftButton),
|
||||
});
|
||||
|
||||
assertHeadJudgement(HitResult.Meh);
|
||||
assertTickJudgement(HitResult.LargeTickMiss);
|
||||
assertTailJudgement(HitResult.IgnoreMiss);
|
||||
assertSliderJudgement(HitResult.IgnoreHit);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHitEarlyAndRepressInFollowRegion()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame(time_slider_start - 150, slider_start_position, OsuAction.LeftButton),
|
||||
new OsuReplayFrame(time_slider_start - 100, slider_start_position + offset_inside_follow, OsuAction.LeftButton),
|
||||
new OsuReplayFrame(time_slider_start - 75, slider_start_position + offset_inside_follow),
|
||||
new OsuReplayFrame(time_slider_start - 50, slider_start_position + offset_inside_follow, OsuAction.LeftButton),
|
||||
new OsuReplayFrame(time_slider_end - 50, slider_end_position + offset_inside_follow, OsuAction.LeftButton),
|
||||
});
|
||||
|
||||
assertHeadJudgement(HitResult.Meh);
|
||||
assertTickJudgement(HitResult.LargeTickMiss);
|
||||
assertTailJudgement(HitResult.IgnoreMiss);
|
||||
assertSliderJudgement(HitResult.IgnoreHit);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHitEarlyMoveOutsideFollowRegion()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame(time_slider_start - 150, slider_start_position, OsuAction.LeftButton),
|
||||
new OsuReplayFrame(time_slider_start - 100, slider_start_position + offset_outside_follow, OsuAction.LeftButton),
|
||||
new OsuReplayFrame(time_slider_end - 100, slider_end_position + offset_outside_follow, OsuAction.LeftButton),
|
||||
});
|
||||
|
||||
assertHeadJudgement(HitResult.Meh);
|
||||
assertTickJudgement(HitResult.LargeTickMiss);
|
||||
assertTailJudgement(HitResult.IgnoreMiss);
|
||||
assertSliderJudgement(HitResult.IgnoreHit);
|
||||
}
|
||||
|
||||
private void assertHeadJudgement(HitResult result)
|
||||
{
|
||||
AddAssert(
|
||||
"check head result",
|
||||
() => judgementResults.SingleOrDefault(r => r.HitObject is SliderHeadCircle)?.Type,
|
||||
() => Is.EqualTo(result));
|
||||
}
|
||||
|
||||
private void assertTickJudgement(HitResult result)
|
||||
{
|
||||
AddAssert(
|
||||
"check tick result",
|
||||
() => judgementResults.SingleOrDefault(r => r.HitObject is SliderTick)?.Type,
|
||||
() => Is.EqualTo(result));
|
||||
}
|
||||
|
||||
private void assertRepeatJudgement(HitResult result)
|
||||
{
|
||||
AddAssert(
|
||||
"check tick result",
|
||||
() => judgementResults.SingleOrDefault(r => r.HitObject is SliderRepeat)?.Type,
|
||||
() => Is.EqualTo(result));
|
||||
}
|
||||
|
||||
private void assertTailJudgement(HitResult result)
|
||||
{
|
||||
AddAssert(
|
||||
"check tail result",
|
||||
() => judgementResults.SingleOrDefault(r => r.HitObject is SliderTailCircle)?.Type,
|
||||
() => Is.EqualTo(result));
|
||||
}
|
||||
|
||||
private void assertSliderJudgement(HitResult result)
|
||||
{
|
||||
AddAssert(
|
||||
"check slider result",
|
||||
() => judgementResults.SingleOrDefault(r => r.HitObject is Slider)?.Type,
|
||||
() => Is.EqualTo(result));
|
||||
}
|
||||
|
||||
private Vector2 computePositionFromTime(double time)
|
||||
{
|
||||
Vector2 dist = slider_end_position - slider_start_position;
|
||||
double t = (time - time_slider_start) / (time_slider_end - time_slider_start);
|
||||
return slider_start_position + dist * (float)t;
|
||||
}
|
||||
|
||||
private void performTest(List<ReplayFrame> frames, Action<Slider>? adjustSliderFunc = null, bool classic = false)
|
||||
{
|
||||
Slider slider = new Slider
|
||||
{
|
||||
StartTime = time_slider_start,
|
||||
Position = new Vector2(256 - slider_path_length / 2, 192),
|
||||
TickDistanceMultiplier = 3,
|
||||
ClassicSliderBehaviour = classic,
|
||||
Samples = new[]
|
||||
{
|
||||
new HitSampleInfo(HitSampleInfo.HIT_NORMAL)
|
||||
},
|
||||
Path = new SliderPath(PathType.LINEAR, new[]
|
||||
{
|
||||
Vector2.Zero,
|
||||
new Vector2(slider_path_length, 0),
|
||||
}, slider_path_length),
|
||||
};
|
||||
|
||||
adjustSliderFunc?.Invoke(slider);
|
||||
|
||||
AddStep("load player", () =>
|
||||
{
|
||||
Beatmap.Value = CreateWorkingBeatmap(new Beatmap<OsuHitObject>
|
||||
{
|
||||
HitObjects = { slider },
|
||||
BeatmapInfo =
|
||||
{
|
||||
Difficulty = new BeatmapDifficulty
|
||||
{
|
||||
SliderMultiplier = 1,
|
||||
SliderTickRate = 3,
|
||||
OverallDifficulty = 0
|
||||
},
|
||||
Ruleset = new OsuRuleset().RulesetInfo,
|
||||
}
|
||||
});
|
||||
|
||||
var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });
|
||||
|
||||
p.OnLoadComplete += _ =>
|
||||
{
|
||||
p.ScoreProcessor.NewJudgement += result =>
|
||||
{
|
||||
if (currentPlayer == p) judgementResults.Add(result);
|
||||
};
|
||||
};
|
||||
|
||||
LoadScreen(currentPlayer = p);
|
||||
judgementResults.Clear();
|
||||
});
|
||||
|
||||
AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
|
||||
AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
|
||||
AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value);
|
||||
}
|
||||
|
||||
private partial class ScoreAccessibleReplayPlayer : ReplayPlayer
|
||||
{
|
||||
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
|
||||
|
||||
protected override bool PauseOnFocusLost => false;
|
||||
|
||||
public ScoreAccessibleReplayPlayer(Score score)
|
||||
: base(score, new PlayerConfiguration
|
||||
{
|
||||
AllowPause = false,
|
||||
ShowResults = false,
|
||||
})
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,528 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Replays;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Replays;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
public partial class TestSceneSliderLateHitJudgement : RateAdjustedBeatmapTestScene
|
||||
{
|
||||
// Note: In the following tests, the terminology "in range of the follow circle" is used as meaning
|
||||
// the equivalent of "in range of the follow circle as if it were in its expanded state".
|
||||
|
||||
private const double time_slider_start = 1000;
|
||||
private const double time_slider_end = 1500;
|
||||
|
||||
private static readonly Vector2 slider_start_position = new Vector2(256 - slider_path_length / 2, 192);
|
||||
private static readonly Vector2 slider_end_position = new Vector2(256 + slider_path_length / 2, 192);
|
||||
|
||||
private ScoreAccessibleReplayPlayer currentPlayer = null!;
|
||||
|
||||
private const float slider_path_length = 200;
|
||||
|
||||
private readonly List<JudgementResult> judgementResults = new List<JudgementResult>();
|
||||
|
||||
/// <summary>
|
||||
/// If the head circle is hit and the mouse is in range of the follow circle,
|
||||
/// then tracking should be enabled.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestHitLateInRangeTracks()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame(time_slider_start + 100, slider_start_position, OsuAction.LeftButton),
|
||||
new OsuReplayFrame(time_slider_end + 100, slider_end_position, OsuAction.LeftButton),
|
||||
});
|
||||
|
||||
assertHeadJudgement(HitResult.Ok);
|
||||
assertTailJudgement(HitResult.LargeTickHit);
|
||||
assertSliderJudgement(HitResult.IgnoreHit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If the head circle is hit and the mouse is NOT in range of the follow circle,
|
||||
/// then tracking should NOT be enabled.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestHitLateOutOfRangeDoesNotTrack()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame(time_slider_start + 100, slider_start_position, OsuAction.LeftButton),
|
||||
new OsuReplayFrame(time_slider_end + 100, slider_end_position, OsuAction.LeftButton),
|
||||
}, s =>
|
||||
{
|
||||
s.SliderVelocityMultiplier = 2;
|
||||
});
|
||||
|
||||
assertHeadJudgement(HitResult.Ok);
|
||||
assertTailJudgement(HitResult.IgnoreMiss);
|
||||
assertSliderJudgement(HitResult.IgnoreHit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If the head circle is hit late and the mouse is in range of the follow circle,
|
||||
/// then all ticks that the follow circle has passed through should be hit.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestHitLateInRangeHitsTicks()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame(time_slider_start + 150, slider_start_position, OsuAction.LeftButton),
|
||||
new OsuReplayFrame(time_slider_end + 150, slider_end_position, OsuAction.LeftButton),
|
||||
}, s =>
|
||||
{
|
||||
s.TickDistanceMultiplier = 0.2f;
|
||||
});
|
||||
|
||||
assertHeadJudgement(HitResult.Meh);
|
||||
assertTickJudgement(0, HitResult.LargeTickHit);
|
||||
assertTickJudgement(1, HitResult.LargeTickHit);
|
||||
assertTickJudgement(2, HitResult.LargeTickHit);
|
||||
assertTickJudgement(3, HitResult.LargeTickHit);
|
||||
assertTailJudgement(HitResult.LargeTickHit);
|
||||
assertSliderJudgement(HitResult.IgnoreHit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If the head circle is hit late and the mouse is NOT in range of the follow circle,
|
||||
/// then all ticks that the follow circle has passed through should NOT be hit.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestHitLateOutOfRangeDoesNotHitTicks()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame(time_slider_start + 150, slider_start_position, OsuAction.LeftButton),
|
||||
new OsuReplayFrame(time_slider_end + 150, slider_end_position, OsuAction.LeftButton),
|
||||
}, s =>
|
||||
{
|
||||
s.SliderVelocityMultiplier = 2;
|
||||
s.TickDistanceMultiplier = 0.2f;
|
||||
});
|
||||
|
||||
assertHeadJudgement(HitResult.Meh);
|
||||
assertTickJudgement(0, HitResult.LargeTickMiss);
|
||||
assertTickJudgement(1, HitResult.LargeTickMiss);
|
||||
assertTailJudgement(HitResult.IgnoreMiss);
|
||||
assertSliderJudgement(HitResult.IgnoreHit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If the head circle is pressed after it's missed and the mouse is in range of the follow circle,
|
||||
/// then tracking should NOT be enabled.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestMissHeadInRangeDoesNotTrack()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame(time_slider_start + 151, slider_start_position, OsuAction.LeftButton),
|
||||
new OsuReplayFrame(time_slider_end + 151, slider_end_position, OsuAction.LeftButton),
|
||||
}, s =>
|
||||
{
|
||||
s.TickDistanceMultiplier = 0.2f;
|
||||
});
|
||||
|
||||
assertHeadJudgement(HitResult.Miss);
|
||||
assertTickJudgement(0, HitResult.LargeTickMiss);
|
||||
assertTickJudgement(1, HitResult.LargeTickMiss);
|
||||
assertTickJudgement(2, HitResult.LargeTickMiss);
|
||||
assertTickJudgement(3, HitResult.LargeTickMiss);
|
||||
assertTailJudgement(HitResult.IgnoreMiss);
|
||||
assertSliderJudgement(HitResult.IgnoreMiss);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If the head circle is hit late but after the completion of the slider and the mouse is in range of the follow circle,
|
||||
/// then all nested objects (ticks/repeats/tail) should be hit.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestHitLateShortSliderHitsAll()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame(time_slider_start + 150, slider_start_position, OsuAction.LeftButton),
|
||||
new OsuReplayFrame(time_slider_end + 150, slider_start_position, OsuAction.LeftButton),
|
||||
}, s =>
|
||||
{
|
||||
s.Path = new SliderPath(PathType.LINEAR, new[]
|
||||
{
|
||||
Vector2.Zero,
|
||||
new Vector2(20, 0),
|
||||
}, 20);
|
||||
|
||||
s.TickDistanceMultiplier = 0.01f;
|
||||
s.RepeatCount = 1;
|
||||
});
|
||||
|
||||
assertHeadJudgement(HitResult.Meh);
|
||||
assertAllTickJudgements(HitResult.LargeTickHit);
|
||||
assertRepeatJudgement(HitResult.LargeTickHit);
|
||||
assertTailJudgement(HitResult.LargeTickHit);
|
||||
assertSliderJudgement(HitResult.IgnoreHit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If the head circle is hit late and the mouse is in range of the follow circle,
|
||||
/// then all the repeats that the follow circle has passed through should be hit.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestHitLateInRangeHitsRepeat()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame(time_slider_start + 150, slider_start_position, OsuAction.LeftButton),
|
||||
new OsuReplayFrame(time_slider_end + 150, slider_start_position, OsuAction.LeftButton),
|
||||
}, s =>
|
||||
{
|
||||
s.Path = new SliderPath(PathType.LINEAR, new[]
|
||||
{
|
||||
Vector2.Zero,
|
||||
new Vector2(50, 0),
|
||||
}, 50);
|
||||
|
||||
s.RepeatCount = 1;
|
||||
});
|
||||
|
||||
assertHeadJudgement(HitResult.Meh);
|
||||
assertRepeatJudgement(HitResult.LargeTickHit);
|
||||
assertTailJudgement(HitResult.LargeTickHit);
|
||||
assertSliderJudgement(HitResult.IgnoreHit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If the head circle is hit and the mouse is in range of the follow circle,
|
||||
/// then only the ticks that are in range of the cursor position should be hit.
|
||||
/// If any hitobject does not meet this criteria, ALL hitobjects after that one should be missed.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestHitLateDoesNotHitTicksIfAnyOutOfRange()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame(time_slider_start + 150, slider_start_position, OsuAction.LeftButton),
|
||||
new OsuReplayFrame(time_slider_end + 150, slider_start_position, OsuAction.LeftButton),
|
||||
}, s =>
|
||||
{
|
||||
s.Path = new SliderPath(PathType.PERFECT_CURVE, new[]
|
||||
{
|
||||
Vector2.Zero,
|
||||
new Vector2(70, 70),
|
||||
new Vector2(20, 0),
|
||||
});
|
||||
|
||||
s.TickDistanceMultiplier = 0.03f;
|
||||
s.SliderVelocityMultiplier = 6f;
|
||||
});
|
||||
|
||||
assertHeadJudgement(HitResult.Meh);
|
||||
|
||||
// At least one tick was out of range, so they all should be missed.
|
||||
assertAllTickJudgements(HitResult.LargeTickMiss);
|
||||
|
||||
// This particular test actually starts tracking the slider just before the end, so the tail should be hit because of its leniency.
|
||||
assertTailJudgement(HitResult.LargeTickHit);
|
||||
|
||||
assertSliderJudgement(HitResult.IgnoreHit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If the head circle is hit and the mouse is in range of the follow circle,
|
||||
/// then a tick not within the follow radius from the cursor position should not be hit.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestHitLateInRangeDoesNotHitOutOfRangeTick()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame(time_slider_start + 150, slider_start_position, OsuAction.LeftButton),
|
||||
new OsuReplayFrame(time_slider_end + 150, slider_start_position, OsuAction.LeftButton),
|
||||
}, s =>
|
||||
{
|
||||
s.Path = new SliderPath(PathType.PERFECT_CURVE, new[]
|
||||
{
|
||||
Vector2.Zero,
|
||||
new Vector2(50, 50),
|
||||
new Vector2(20, 0),
|
||||
});
|
||||
|
||||
s.TickDistanceMultiplier = 0.3f;
|
||||
s.SliderVelocityMultiplier = 3;
|
||||
});
|
||||
|
||||
assertHeadJudgement(HitResult.Meh);
|
||||
assertTickJudgement(0, HitResult.LargeTickMiss);
|
||||
assertTailJudgement(HitResult.LargeTickHit);
|
||||
assertSliderJudgement(HitResult.IgnoreHit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Same as <see cref="TestHitLateInRangeDoesNotHitOutOfRangeTick"/> except the tracking is limited to the ball
|
||||
/// because the tick was missed.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestHitLateInRangeDoesNotHitOutOfRangeTickAndTrackingLimitedToBall()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame(time_slider_start + 150, slider_start_position, OsuAction.LeftButton),
|
||||
new OsuReplayFrame(time_slider_end + 150, slider_start_position, OsuAction.LeftButton),
|
||||
}, s =>
|
||||
{
|
||||
s.Path = new SliderPath(PathType.PERFECT_CURVE, new[]
|
||||
{
|
||||
Vector2.Zero,
|
||||
new Vector2(50, 50),
|
||||
new Vector2(20, 0),
|
||||
});
|
||||
|
||||
s.TickDistanceMultiplier = 0.25f;
|
||||
s.SliderVelocityMultiplier = 3;
|
||||
});
|
||||
|
||||
assertHeadJudgement(HitResult.Meh);
|
||||
assertTickJudgement(0, HitResult.LargeTickMiss);
|
||||
assertTickJudgement(1, HitResult.LargeTickMiss);
|
||||
assertTailJudgement(HitResult.LargeTickHit);
|
||||
assertSliderJudgement(HitResult.IgnoreHit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If the head circle is hit and the mouse is in range of the follow circle,
|
||||
/// then a tick not within the follow radius from the cursor position should not be hit.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestHitLateWithEdgeHit()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame(time_slider_start + 150, slider_start_position - new Vector2(20), OsuAction.LeftButton),
|
||||
new OsuReplayFrame(time_slider_end + 150, slider_start_position - new Vector2(20), OsuAction.LeftButton),
|
||||
}, s =>
|
||||
{
|
||||
s.Path = new SliderPath(PathType.PERFECT_CURVE, new[]
|
||||
{
|
||||
Vector2.Zero,
|
||||
new Vector2(50, 50),
|
||||
new Vector2(20, 0),
|
||||
});
|
||||
|
||||
s.TickDistanceMultiplier = 0.35f;
|
||||
s.SliderVelocityMultiplier = 4;
|
||||
});
|
||||
|
||||
assertHeadJudgement(HitResult.Meh);
|
||||
assertTickJudgement(0, HitResult.LargeTickMiss);
|
||||
assertTailJudgement(HitResult.IgnoreMiss);
|
||||
assertSliderJudgement(HitResult.IgnoreHit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Late hit and release on each slider head of a slider stream.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestLateHitSliderStream()
|
||||
{
|
||||
var beatmap = new Beatmap<OsuHitObject>();
|
||||
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
beatmap.HitObjects.Add(new Slider
|
||||
{
|
||||
StartTime = time_slider_start + 75 * i, // 200BPM @ 1/4
|
||||
Position = new Vector2(256 - slider_path_length / 2, 192),
|
||||
TickDistanceMultiplier = 3,
|
||||
Path = new SliderPath(PathType.LINEAR, new[]
|
||||
{
|
||||
Vector2.Zero,
|
||||
new Vector2(20, 0),
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
var replay = new List<ReplayFrame>();
|
||||
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
replay.Add(new OsuReplayFrame(time_slider_start + 75 * i + 75, slider_start_position, i % 2 == 0 ? OsuAction.LeftButton : OsuAction.RightButton));
|
||||
replay.Add(new OsuReplayFrame(time_slider_start + 75 * i + 140, slider_start_position));
|
||||
}
|
||||
|
||||
performTest(replay, beatmap);
|
||||
|
||||
AddAssert(
|
||||
$"all heads = {HitResult.Ok}",
|
||||
() => judgementResults.Where(r => r.HitObject is SliderHeadCircle).Select(r => r.Type),
|
||||
() => Has.All.EqualTo(HitResult.Ok));
|
||||
}
|
||||
|
||||
private void assertHeadJudgement(HitResult result)
|
||||
{
|
||||
AddAssert(
|
||||
$"head = {result}",
|
||||
() => judgementResults.SingleOrDefault(r => r.HitObject is SliderHeadCircle)?.Type,
|
||||
() => Is.EqualTo(result));
|
||||
}
|
||||
|
||||
private void assertTickJudgement(int index, HitResult result)
|
||||
{
|
||||
AddAssert(
|
||||
$"tick({index}) = {result}",
|
||||
() => judgementResults.Where(r => r.HitObject is SliderTick).ElementAtOrDefault(index)?.Type,
|
||||
() => Is.EqualTo(result));
|
||||
}
|
||||
|
||||
private void assertAllTickJudgements(HitResult result)
|
||||
{
|
||||
AddAssert(
|
||||
$"all ticks = {result}",
|
||||
() => judgementResults.Where(r => r.HitObject is SliderTick).Select(t => t.Type),
|
||||
() => Has.All.EqualTo(result));
|
||||
}
|
||||
|
||||
private void assertRepeatJudgement(HitResult result)
|
||||
{
|
||||
AddAssert(
|
||||
$"repeat = {result}",
|
||||
() => judgementResults.SingleOrDefault(r => r.HitObject is SliderRepeat)?.Type,
|
||||
() => Is.EqualTo(result));
|
||||
}
|
||||
|
||||
private void assertTailJudgement(HitResult result)
|
||||
{
|
||||
AddAssert(
|
||||
$"tail = {result}",
|
||||
() => judgementResults.SingleOrDefault(r => r.HitObject is SliderTailCircle)?.Type,
|
||||
() => Is.EqualTo(result));
|
||||
}
|
||||
|
||||
private void assertSliderJudgement(HitResult result)
|
||||
{
|
||||
AddAssert(
|
||||
$"slider = {result}",
|
||||
() => judgementResults.SingleOrDefault(r => r.HitObject is Slider)?.Type,
|
||||
() => Is.EqualTo(result));
|
||||
}
|
||||
|
||||
private void performTest(List<ReplayFrame> frames, Action<Slider>? adjustSliderFunc = null, bool classic = false)
|
||||
{
|
||||
Slider slider = new Slider
|
||||
{
|
||||
StartTime = time_slider_start,
|
||||
Position = new Vector2(256 - slider_path_length / 2, 192),
|
||||
TickDistanceMultiplier = 3,
|
||||
ClassicSliderBehaviour = classic,
|
||||
Path = new SliderPath(PathType.LINEAR, new[]
|
||||
{
|
||||
Vector2.Zero,
|
||||
new Vector2(slider_path_length, 0),
|
||||
}, slider_path_length),
|
||||
};
|
||||
|
||||
adjustSliderFunc?.Invoke(slider);
|
||||
|
||||
var beatmap = new Beatmap<OsuHitObject>
|
||||
{
|
||||
HitObjects = { slider },
|
||||
BeatmapInfo =
|
||||
{
|
||||
Difficulty = new BeatmapDifficulty
|
||||
{
|
||||
SliderMultiplier = 4,
|
||||
SliderTickRate = 3
|
||||
},
|
||||
Ruleset = new OsuRuleset().RulesetInfo,
|
||||
}
|
||||
};
|
||||
|
||||
performTest(frames, beatmap);
|
||||
}
|
||||
|
||||
private void performTest(List<ReplayFrame> frames, Beatmap<OsuHitObject> beatmap)
|
||||
{
|
||||
beatmap.BeatmapInfo.Ruleset = new OsuRuleset().RulesetInfo;
|
||||
beatmap.BeatmapInfo.StackLeniency = 0;
|
||||
beatmap.BeatmapInfo.Difficulty = new BeatmapDifficulty
|
||||
{
|
||||
SliderMultiplier = 4,
|
||||
SliderTickRate = 3,
|
||||
};
|
||||
|
||||
AddStep("load player", () =>
|
||||
{
|
||||
Beatmap.Value = CreateWorkingBeatmap(beatmap);
|
||||
|
||||
var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });
|
||||
|
||||
p.OnLoadComplete += _ =>
|
||||
{
|
||||
p.ScoreProcessor.NewJudgement += result =>
|
||||
{
|
||||
if (currentPlayer == p)
|
||||
judgementResults.Add(result);
|
||||
|
||||
DrawableHitObject drawableObj = this.ChildrenOfType<DrawableHitObject>().Single(h => h.HitObject == result.HitObject);
|
||||
|
||||
var text = new OsuSpriteText
|
||||
{
|
||||
Origin = Anchor.Centre,
|
||||
Position = Content.ToLocalSpace(drawableObj.ToScreenSpace(drawableObj.OriginPosition)) - new Vector2(0, 20),
|
||||
Text = result.IsHit ? "hit" : "miss"
|
||||
};
|
||||
|
||||
Add(text);
|
||||
|
||||
text.FadeOutFromOne(1000).Expire();
|
||||
};
|
||||
};
|
||||
|
||||
LoadScreen(currentPlayer = p);
|
||||
judgementResults.Clear();
|
||||
});
|
||||
|
||||
AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
|
||||
AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
|
||||
AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value);
|
||||
}
|
||||
|
||||
private partial class ScoreAccessibleReplayPlayer : ReplayPlayer
|
||||
{
|
||||
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
|
||||
|
||||
protected override bool PauseOnFocusLost => false;
|
||||
|
||||
public ScoreAccessibleReplayPlayer(Score score)
|
||||
: base(score, new PlayerConfiguration
|
||||
{
|
||||
AllowPause = false,
|
||||
ShowResults = false,
|
||||
})
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,10 +58,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
double trackerRotationTolerance = 0;
|
||||
|
||||
addSeekStep(5000);
|
||||
AddStep("calculate rotation tolerance", () =>
|
||||
{
|
||||
trackerRotationTolerance = Math.Abs(drawableSpinner.RotationTracker.Rotation * 0.1f);
|
||||
});
|
||||
AddStep("calculate rotation tolerance", () => { trackerRotationTolerance = Math.Abs(drawableSpinner.RotationTracker.Rotation * 0.1f); });
|
||||
AddAssert("is disc rotation not almost 0", () => drawableSpinner.RotationTracker.Rotation, () => Is.Not.EqualTo(0).Within(100));
|
||||
AddAssert("is disc rotation absolute not almost 0", () => drawableSpinner.Result.TotalRotation, () => Is.Not.EqualTo(0).Within(100));
|
||||
|
||||
@@ -133,9 +130,11 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
|
||||
AddAssert("player score matching expected bonus score", () =>
|
||||
{
|
||||
var scoreProcessor = ((ScoreExposedPlayer)Player).ScoreProcessor;
|
||||
|
||||
// multipled by 2 to nullify the score multiplier. (autoplay mod selected)
|
||||
long totalScore = ((ScoreExposedPlayer)Player).ScoreProcessor.TotalScore.Value * 2;
|
||||
return totalScore == (int)(drawableSpinner.Result.TotalRotation / 360) * new SpinnerTick().CreateJudgement().MaxNumericResult;
|
||||
long totalScore = scoreProcessor.TotalScore.Value * 2;
|
||||
return totalScore == (int)(drawableSpinner.Result.TotalRotation / 360) * scoreProcessor.GetBaseScoreForResult(new SpinnerTick().CreateJudgement().MaxResult);
|
||||
});
|
||||
|
||||
addSeekStep(0);
|
||||
|
||||
@@ -5,12 +5,12 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Scoring;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Scoring.Legacy;
|
||||
|
||||
@@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
{
|
||||
internal class OsuLegacyScoreSimulator : ILegacyScoreSimulator
|
||||
{
|
||||
private readonly ScoreProcessor scoreProcessor = new OsuScoreProcessor();
|
||||
|
||||
private int legacyBonusScore;
|
||||
private int standardisedBonusScore;
|
||||
private int combo;
|
||||
@@ -74,6 +76,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
simulateHit(obj, ref attributes);
|
||||
|
||||
attributes.BonusScoreRatio = legacyBonusScore == 0 ? 0 : (double)standardisedBonusScore / legacyBonusScore;
|
||||
attributes.BonusScore = legacyBonusScore;
|
||||
attributes.MaxCombo = combo;
|
||||
|
||||
return attributes;
|
||||
}
|
||||
@@ -169,7 +173,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
if (isBonus)
|
||||
{
|
||||
legacyBonusScore += scoreIncrease;
|
||||
standardisedBonusScore += Judgement.ToNumericResult(bonusResult);
|
||||
standardisedBonusScore += scoreProcessor.GetBaseScoreForResult(bonusResult);
|
||||
}
|
||||
else
|
||||
attributes.AccuracyScore += scoreIncrease;
|
||||
|
||||
+1
-1
@@ -373,7 +373,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
curveTypeItems.Add(createMenuItemForPathType(PathType.LINEAR));
|
||||
curveTypeItems.Add(createMenuItemForPathType(PathType.PERFECT_CURVE));
|
||||
curveTypeItems.Add(createMenuItemForPathType(PathType.BEZIER));
|
||||
curveTypeItems.Add(createMenuItemForPathType(PathType.BSpline(3)));
|
||||
curveTypeItems.Add(createMenuItemForPathType(PathType.BSpline(4)));
|
||||
|
||||
if (selectedPieces.Any(piece => piece.ControlPoint.Type?.Type == SplineType.Catmull))
|
||||
curveTypeItems.Add(createMenuItemForPathType(PathType.CATMULL));
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
@@ -41,15 +42,18 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
private int currentSegmentLength;
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
[CanBeNull]
|
||||
private IPositionSnapProvider positionSnapProvider { get; set; }
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
[CanBeNull]
|
||||
private IDistanceSnapProvider distanceSnapProvider { get; set; }
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
[CanBeNull]
|
||||
private FreehandSliderToolboxGroup freehandToolboxGroup { get; set; }
|
||||
|
||||
private readonly IncrementalBSplineBuilder bSplineBuilder = new IncrementalBSplineBuilder();
|
||||
private readonly IncrementalBSplineBuilder bSplineBuilder = new IncrementalBSplineBuilder { Degree = 4 };
|
||||
|
||||
protected override bool IsValidForPlacement => HitObject.Path.HasValidLength;
|
||||
|
||||
@@ -94,6 +98,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
bSplineBuilder.CornerThreshold = e.NewValue;
|
||||
Scheduler.AddOnce(updateSliderPathFromBSplineBuilder);
|
||||
}, true);
|
||||
|
||||
freehandToolboxGroup.CircleThreshold.BindValueChanged(e =>
|
||||
{
|
||||
Scheduler.AddOnce(updateSliderPathFromBSplineBuilder);
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,7 +206,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
base.OnDragEnd(e);
|
||||
|
||||
if (state == SliderPlacementState.Drawing)
|
||||
{
|
||||
bSplineBuilder.Finish();
|
||||
updateSliderPathFromBSplineBuilder();
|
||||
|
||||
// Change the state so it will snap the expected distance in endCurve.
|
||||
state = SliderPlacementState.Finishing;
|
||||
endCurve();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnMouseUp(MouseUpEvent e)
|
||||
@@ -232,7 +248,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
{
|
||||
if (state == SliderPlacementState.Drawing)
|
||||
{
|
||||
segmentStart.Type = PathType.BSpline(3);
|
||||
segmentStart.Type = PathType.BSpline(4);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -300,7 +316,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
|
||||
private void updateSlider()
|
||||
{
|
||||
HitObject.Path.ExpectedDistance.Value = distanceSnapProvider?.FindSnappedDistance(HitObject, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance;
|
||||
if (state == SliderPlacementState.Drawing)
|
||||
HitObject.Path.ExpectedDistance.Value = (float)HitObject.Path.CalculatedDistance;
|
||||
else
|
||||
HitObject.Path.ExpectedDistance.Value = distanceSnapProvider?.FindSnappedDistance(HitObject, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance;
|
||||
|
||||
bodyPiece.UpdateFrom(HitObject);
|
||||
headCirclePiece.UpdateFrom(HitObject.HeadCircle);
|
||||
@@ -309,53 +328,126 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
|
||||
private void updateSliderPathFromBSplineBuilder()
|
||||
{
|
||||
IReadOnlyList<Vector2> builderPoints = bSplineBuilder.ControlPoints;
|
||||
IReadOnlyList<List<Vector2>> builderPoints = bSplineBuilder.ControlPoints;
|
||||
|
||||
if (builderPoints.Count == 0)
|
||||
if (builderPoints.Count == 0 || builderPoints[0].Count == 0)
|
||||
return;
|
||||
|
||||
int lastSegmentStart = 0;
|
||||
PathType? lastPathType = null;
|
||||
|
||||
HitObject.Path.ControlPoints.Clear();
|
||||
|
||||
// Iterate through generated points, finding each segment and adding non-inheriting path types where appropriate.
|
||||
// Importantly, the B-Spline builder returns three Vector2s at the same location when a new segment is to be started.
|
||||
// Iterate through generated segments and adding non-inheriting path types where appropriate.
|
||||
for (int i = 0; i < builderPoints.Count; i++)
|
||||
{
|
||||
bool isLastPoint = i == builderPoints.Count - 1;
|
||||
bool isNewSegment = i < builderPoints.Count - 2 && builderPoints[i] == builderPoints[i + 1] && builderPoints[i] == builderPoints[i + 2];
|
||||
bool isLastSegment = i == builderPoints.Count - 1;
|
||||
var segment = builderPoints[i];
|
||||
|
||||
if (isNewSegment || isLastPoint)
|
||||
if (segment.Count == 0)
|
||||
continue;
|
||||
|
||||
// Replace this segment with a circular arc if it is a reasonable substitute.
|
||||
var circleArcSegment = tryCircleArc(segment);
|
||||
|
||||
if (circleArcSegment is not null)
|
||||
{
|
||||
int pointsInSegment = i - lastSegmentStart;
|
||||
|
||||
// Where possible, we can use the simpler LINEAR path type.
|
||||
PathType? pathType = pointsInSegment == 1 ? PathType.LINEAR : PathType.BSpline(3);
|
||||
|
||||
// Linear segments can be combined, as two adjacent linear sections are computationally the same as one with the points combined.
|
||||
if (lastPathType == pathType && lastPathType == PathType.LINEAR)
|
||||
pathType = null;
|
||||
|
||||
HitObject.Path.ControlPoints.Add(new PathControlPoint(builderPoints[lastSegmentStart], pathType));
|
||||
for (int j = lastSegmentStart + 1; j < i; j++)
|
||||
HitObject.Path.ControlPoints.Add(new PathControlPoint(builderPoints[j]));
|
||||
|
||||
if (isLastPoint)
|
||||
HitObject.Path.ControlPoints.Add(new PathControlPoint(builderPoints[i]));
|
||||
|
||||
// Skip the redundant duplicated points (see isNewSegment above) which have been coalesced into a path type.
|
||||
lastSegmentStart = (i += 2);
|
||||
if (pathType != null) lastPathType = pathType;
|
||||
HitObject.Path.ControlPoints.Add(new PathControlPoint(circleArcSegment[0], PathType.PERFECT_CURVE));
|
||||
HitObject.Path.ControlPoints.Add(new PathControlPoint(circleArcSegment[1]));
|
||||
}
|
||||
else
|
||||
{
|
||||
HitObject.Path.ControlPoints.Add(new PathControlPoint(segment[0], PathType.BSpline(4)));
|
||||
for (int j = 1; j < segment.Count - 1; j++)
|
||||
HitObject.Path.ControlPoints.Add(new PathControlPoint(segment[j]));
|
||||
}
|
||||
|
||||
if (isLastSegment)
|
||||
HitObject.Path.ControlPoints.Add(new PathControlPoint(segment[^1]));
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2[] tryCircleArc(List<Vector2> segment)
|
||||
{
|
||||
if (segment.Count < 3 || freehandToolboxGroup?.CircleThreshold.Value == 0) return null;
|
||||
|
||||
// Assume the segment creates a reasonable circular arc and then check if it reasonable
|
||||
var points = PathApproximator.BSplineToPiecewiseLinear(segment.ToArray(), bSplineBuilder.Degree);
|
||||
var circleArcControlPoints = new[] { points[0], points[points.Count / 2], points[^1] };
|
||||
var circleArc = new CircularArcProperties(circleArcControlPoints);
|
||||
|
||||
if (!circleArc.IsValid) return null;
|
||||
|
||||
double length = circleArc.ThetaRange * circleArc.Radius;
|
||||
|
||||
if (length > 1000) return null;
|
||||
|
||||
double loss = 0;
|
||||
Vector2? lastPoint = null;
|
||||
Vector2? lastVec = null;
|
||||
Vector2? lastVec2 = null;
|
||||
int? lastDir = null;
|
||||
int? lastDir2 = null;
|
||||
double totalWinding = 0;
|
||||
|
||||
// Loop through the points and check if they are not too far away from the circular arc.
|
||||
// Also make sure it curves monotonically in one direction and at most one loop is done.
|
||||
foreach (var point in points)
|
||||
{
|
||||
var vec = point - circleArc.Centre;
|
||||
loss += Math.Pow((vec.Length - circleArc.Radius) / length, 2);
|
||||
|
||||
if (lastVec.HasValue)
|
||||
{
|
||||
double det = lastVec.Value.X * vec.Y - lastVec.Value.Y * vec.X;
|
||||
int dir = Math.Sign(det);
|
||||
|
||||
if (dir == 0)
|
||||
continue;
|
||||
|
||||
if (lastDir.HasValue && dir != lastDir)
|
||||
return null; // Circle center is not inside the polygon
|
||||
|
||||
lastDir = dir;
|
||||
}
|
||||
|
||||
lastVec = vec;
|
||||
|
||||
if (lastPoint.HasValue)
|
||||
{
|
||||
var vec2 = point - lastPoint.Value;
|
||||
|
||||
if (lastVec2.HasValue)
|
||||
{
|
||||
double dot = Vector2.Dot(vec2, lastVec2.Value);
|
||||
double det = lastVec2.Value.X * vec2.Y - lastVec2.Value.Y * vec2.X;
|
||||
double angle = Math.Atan2(det, dot);
|
||||
int dir2 = Math.Sign(angle);
|
||||
|
||||
if (dir2 == 0)
|
||||
continue;
|
||||
|
||||
if (lastDir2.HasValue && dir2 != lastDir2)
|
||||
return null; // Curvature changed, like in an S-shape
|
||||
|
||||
totalWinding += Math.Abs(angle);
|
||||
lastDir2 = dir2;
|
||||
}
|
||||
|
||||
lastVec2 = vec2;
|
||||
}
|
||||
|
||||
lastPoint = point;
|
||||
}
|
||||
|
||||
loss /= points.Count;
|
||||
|
||||
return loss > freehandToolboxGroup?.CircleThreshold.Value || totalWinding > MathHelper.TwoPi ? null : circleArcControlPoints;
|
||||
}
|
||||
|
||||
private enum SliderPlacementState
|
||||
{
|
||||
Initial,
|
||||
ControlPoints,
|
||||
Drawing
|
||||
Drawing,
|
||||
Finishing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,10 +17,10 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
}
|
||||
|
||||
public BindableFloat Tolerance { get; } = new BindableFloat(1.5f)
|
||||
public BindableFloat Tolerance { get; } = new BindableFloat(1.8f)
|
||||
{
|
||||
MinValue = 0.05f,
|
||||
MaxValue = 3f,
|
||||
MaxValue = 2.0f,
|
||||
Precision = 0.01f
|
||||
};
|
||||
|
||||
@@ -31,8 +31,15 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
Precision = 0.01f
|
||||
};
|
||||
|
||||
public BindableFloat CircleThreshold { get; } = new BindableFloat(0.0015f)
|
||||
{
|
||||
MinValue = 0f,
|
||||
MaxValue = 0.005f,
|
||||
Precision = 0.0001f
|
||||
};
|
||||
|
||||
// We map internal ranges to a more standard range of values for display to the user.
|
||||
private readonly BindableInt displayTolerance = new BindableInt(40)
|
||||
private readonly BindableInt displayTolerance = new BindableInt(90)
|
||||
{
|
||||
MinValue = 5,
|
||||
MaxValue = 100
|
||||
@@ -44,8 +51,15 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
MaxValue = 100
|
||||
};
|
||||
|
||||
private readonly BindableInt displayCircleThreshold = new BindableInt(30)
|
||||
{
|
||||
MinValue = 0,
|
||||
MaxValue = 100
|
||||
};
|
||||
|
||||
private ExpandableSlider<int> toleranceSlider = null!;
|
||||
private ExpandableSlider<int> cornerThresholdSlider = null!;
|
||||
private ExpandableSlider<int> circleThresholdSlider = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
@@ -59,6 +73,10 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
cornerThresholdSlider = new ExpandableSlider<int>
|
||||
{
|
||||
Current = displayCornerThreshold
|
||||
},
|
||||
circleThresholdSlider = new ExpandableSlider<int>
|
||||
{
|
||||
Current = displayCircleThreshold
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -83,18 +101,32 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
CornerThreshold.Value = displayToInternalCornerThreshold(threshold.NewValue);
|
||||
}, true);
|
||||
|
||||
displayCircleThreshold.BindValueChanged(threshold =>
|
||||
{
|
||||
circleThresholdSlider.ContractedLabelText = $"P. C. T.: {threshold.NewValue:N0}";
|
||||
circleThresholdSlider.ExpandedLabelText = $"Perfect Curve Threshold: {threshold.NewValue:N0}";
|
||||
|
||||
CircleThreshold.Value = displayToInternalCircleThreshold(threshold.NewValue);
|
||||
}, true);
|
||||
|
||||
Tolerance.BindValueChanged(tolerance =>
|
||||
displayTolerance.Value = internalToDisplayTolerance(tolerance.NewValue)
|
||||
);
|
||||
CornerThreshold.BindValueChanged(threshold =>
|
||||
displayCornerThreshold.Value = internalToDisplayCornerThreshold(threshold.NewValue)
|
||||
);
|
||||
CircleThreshold.BindValueChanged(threshold =>
|
||||
displayCircleThreshold.Value = internalToDisplayCircleThreshold(threshold.NewValue)
|
||||
);
|
||||
|
||||
float displayToInternalTolerance(float v) => v / 33f;
|
||||
int internalToDisplayTolerance(float v) => (int)Math.Round(v * 33f);
|
||||
float displayToInternalTolerance(float v) => v / 50f;
|
||||
int internalToDisplayTolerance(float v) => (int)Math.Round(v * 50f);
|
||||
|
||||
float displayToInternalCornerThreshold(float v) => v / 100f;
|
||||
int internalToDisplayCornerThreshold(float v) => (int)Math.Round(v * 100f);
|
||||
|
||||
float displayToInternalCircleThreshold(float v) => v / 20000f;
|
||||
int internalToDisplayCircleThreshold(float v) => (int)Math.Round(v * 20000f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Edit.Tools;
|
||||
@@ -54,7 +55,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
.Concat(DistanceSnapProvider.CreateTernaryButtons())
|
||||
.Concat(new[]
|
||||
{
|
||||
new TernaryButton(rectangularGridSnapToggle, "Grid Snap", () => new SpriteIcon { Icon = FontAwesome.Solid.Th })
|
||||
new TernaryButton(rectangularGridSnapToggle, "Grid Snap", () => new SpriteIcon { Icon = OsuIcon.EditorGridSnap })
|
||||
});
|
||||
|
||||
private BindableList<HitObject> selectedHitObjects;
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
// 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.Game.Rulesets.Mods;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public class OsuModAccuracyChallenge : ModAccuracyChallenge
|
||||
{
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ using osu.Game.Rulesets.UI;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public class OsuModAutopilot : Mod, IApplicableFailOverride, IUpdatableByPlayfield, IApplicableToDrawableRuleset<OsuHitObject>
|
||||
public class OsuModAutopilot : Mod, IUpdatableByPlayfield, IApplicableToDrawableRuleset<OsuHitObject>
|
||||
{
|
||||
public override string Name => "Autopilot";
|
||||
public override string Acronym => "AP";
|
||||
@@ -29,18 +29,12 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
typeof(OsuModSpunOut),
|
||||
typeof(ModRelax),
|
||||
typeof(ModFailCondition),
|
||||
typeof(ModNoFail),
|
||||
typeof(ModAutoplay),
|
||||
typeof(OsuModMagnetised),
|
||||
typeof(OsuModRepel),
|
||||
typeof(ModTouchDevice)
|
||||
};
|
||||
|
||||
public bool PerformFail() => false;
|
||||
|
||||
public bool RestartOnFail => false;
|
||||
|
||||
private OsuInputManager inputManager = null!;
|
||||
|
||||
private List<OsuReplayFrame> replayFrames = null!;
|
||||
|
||||
@@ -18,7 +18,7 @@ using osu.Game.Rulesets.UI;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public class OsuModClassic : ModClassic, IApplicableToHitObject, IApplicableToDrawableHitObject, IApplicableToDrawableRuleset<OsuHitObject>
|
||||
public class OsuModClassic : ModClassic, IApplicableToHitObject, IApplicableToDrawableHitObject, IApplicableToDrawableRuleset<OsuHitObject>, IApplicableHealthProcessor
|
||||
{
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModStrictTracking)).ToArray();
|
||||
|
||||
@@ -34,6 +34,9 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
[SettingSource("Fade out hit circles earlier", "Make hit circles fade out into a miss, rather than after it.")]
|
||||
public Bindable<bool> FadeHitCircleEarly { get; } = new Bindable<bool>(true);
|
||||
|
||||
[SettingSource("Classic health", "More closely resembles the original HP drain mechanics.")]
|
||||
public Bindable<bool> ClassicHealth { get; } = new Bindable<bool>(true);
|
||||
|
||||
private bool usingHiddenFading;
|
||||
|
||||
public void ApplyToHitObject(HitObject hitObject)
|
||||
@@ -115,5 +118,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public HealthProcessor? CreateHealthProcessor(double drainStartTime) => ClassicHealth.Value ? new OsuLegacyHealthProcessor(drainStartTime) : null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
// 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.Bindables;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public class OsuModDepth : ModWithVisibilityAdjustment, IUpdatableByPlayfield, IApplicableToDrawableRuleset<OsuHitObject>
|
||||
{
|
||||
public override string Name => "Depth";
|
||||
public override string Acronym => "DP";
|
||||
public override IconUsage? Icon => FontAwesome.Solid.Cube;
|
||||
public override ModType Type => ModType.Fun;
|
||||
public override LocalisableString Description => "3D. Almost.";
|
||||
public override double ScoreMultiplier => 1;
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModMagnetised), typeof(OsuModRepel), typeof(OsuModFreezeFrame), typeof(ModWithVisibilityAdjustment) }).ToArray();
|
||||
|
||||
private static readonly Vector3 camera_position = new Vector3(OsuPlayfield.BASE_SIZE.X * 0.5f, OsuPlayfield.BASE_SIZE.Y * 0.5f, -200);
|
||||
private readonly float sliderMinDepth = depthForScale(1.5f); // Depth at which slider's scale will be 1.5f
|
||||
|
||||
[SettingSource("Maximum depth", "How far away objects appear.", 0)]
|
||||
public BindableFloat MaxDepth { get; } = new BindableFloat(100)
|
||||
{
|
||||
Precision = 10,
|
||||
MinValue = 50,
|
||||
MaxValue = 200
|
||||
};
|
||||
|
||||
[SettingSource("Show Approach Circles", "Whether approach circles should be visible.", 1)]
|
||||
public BindableBool ShowApproachCircles { get; } = new BindableBool(true);
|
||||
|
||||
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) => applyTransform(hitObject, state);
|
||||
|
||||
protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) => applyTransform(hitObject, state);
|
||||
|
||||
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
||||
{
|
||||
// Hide judgment displays and follow points as they won't make any sense.
|
||||
// Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart.
|
||||
drawableRuleset.Playfield.DisplayJudgements.Value = false;
|
||||
(drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide();
|
||||
}
|
||||
|
||||
private void applyTransform(DrawableHitObject drawable, ArmedState state)
|
||||
{
|
||||
switch (drawable)
|
||||
{
|
||||
case DrawableHitCircle circle:
|
||||
if (!ShowApproachCircles.Value)
|
||||
{
|
||||
var hitObject = (OsuHitObject)drawable.HitObject;
|
||||
double appearTime = hitObject.StartTime - hitObject.TimePreempt;
|
||||
|
||||
using (circle.BeginAbsoluteSequence(appearTime))
|
||||
circle.ApproachCircle.Hide();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void Update(Playfield playfield)
|
||||
{
|
||||
double time = playfield.Time.Current;
|
||||
|
||||
foreach (var drawable in playfield.HitObjectContainer.AliveObjects)
|
||||
{
|
||||
switch (drawable)
|
||||
{
|
||||
case DrawableHitCircle circle:
|
||||
processHitObject(time, circle);
|
||||
break;
|
||||
|
||||
case DrawableSlider slider:
|
||||
processSlider(time, slider);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processHitObject(double time, DrawableOsuHitObject drawable)
|
||||
{
|
||||
var hitObject = drawable.HitObject;
|
||||
|
||||
// Circles are always moving at the constant speed. They'll fade out before reaching the camera even at extreme conditions (AR 11, max depth).
|
||||
double speed = MaxDepth.Value / hitObject.TimePreempt;
|
||||
double appearTime = hitObject.StartTime - hitObject.TimePreempt;
|
||||
float z = MaxDepth.Value - (float)((Math.Max(time, appearTime) - appearTime) * speed);
|
||||
|
||||
float scale = scaleForDepth(z);
|
||||
drawable.Position = toPlayfieldPosition(scale, hitObject.StackedPosition);
|
||||
drawable.Scale = new Vector2(scale);
|
||||
}
|
||||
|
||||
private void processSlider(double time, DrawableSlider drawableSlider)
|
||||
{
|
||||
var hitObject = drawableSlider.HitObject;
|
||||
|
||||
double baseSpeed = MaxDepth.Value / hitObject.TimePreempt;
|
||||
double appearTime = hitObject.StartTime - hitObject.TimePreempt;
|
||||
|
||||
// Allow slider to move at a constant speed if its scale at the end time will be lower than 1.5f
|
||||
float zEnd = MaxDepth.Value - (float)((Math.Max(hitObject.StartTime + hitObject.Duration, appearTime) - appearTime) * baseSpeed);
|
||||
|
||||
if (zEnd > sliderMinDepth)
|
||||
{
|
||||
processHitObject(time, drawableSlider);
|
||||
return;
|
||||
}
|
||||
|
||||
double offsetAfterStartTime = hitObject.Duration + 500;
|
||||
double slowSpeed = Math.Min(-sliderMinDepth / offsetAfterStartTime, baseSpeed);
|
||||
|
||||
double decelerationTime = hitObject.TimePreempt * 0.2;
|
||||
float decelerationDistance = (float)(decelerationTime * (baseSpeed + slowSpeed) * 0.5);
|
||||
|
||||
float z;
|
||||
|
||||
if (time < hitObject.StartTime - decelerationTime)
|
||||
{
|
||||
float fullDistance = decelerationDistance + (float)(baseSpeed * (hitObject.TimePreempt - decelerationTime));
|
||||
z = fullDistance - (float)((Math.Max(time, appearTime) - appearTime) * baseSpeed);
|
||||
}
|
||||
else if (time < hitObject.StartTime)
|
||||
{
|
||||
double timeOffset = time - (hitObject.StartTime - decelerationTime);
|
||||
double deceleration = (slowSpeed - baseSpeed) / decelerationTime;
|
||||
z = decelerationDistance - (float)(baseSpeed * timeOffset + deceleration * timeOffset * timeOffset * 0.5);
|
||||
}
|
||||
else
|
||||
{
|
||||
double endTime = hitObject.StartTime + offsetAfterStartTime;
|
||||
z = -(float)((Math.Min(time, endTime) - hitObject.StartTime) * slowSpeed);
|
||||
}
|
||||
|
||||
float scale = scaleForDepth(z);
|
||||
drawableSlider.Position = toPlayfieldPosition(scale, hitObject.StackedPosition);
|
||||
drawableSlider.Scale = new Vector2(scale);
|
||||
}
|
||||
|
||||
private static float scaleForDepth(float depth) => -camera_position.Z / Math.Max(1f, depth - camera_position.Z);
|
||||
|
||||
private static float depthForScale(float scale) => -camera_position.Z / scale + camera_position.Z;
|
||||
|
||||
private static Vector2 toPlayfieldPosition(float scale, Vector2 positionAtZeroDepth)
|
||||
{
|
||||
return (positionAtZeroDepth - camera_position.Xy) * scale + camera_position.Xy;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public override LocalisableString Description => "Burn the notes into your memory.";
|
||||
|
||||
//Alters the transforms of the approach circles, breaking the effects of these mods.
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModApproachDifferent), typeof(OsuModTransform) }).ToArray();
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModApproachDifferent), typeof(OsuModTransform), typeof(OsuModDepth) }).ToArray();
|
||||
|
||||
public override ModType Type => ModType.Fun;
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public override LocalisableString Description => @"Play with no approach circles and fading circles/sliders.";
|
||||
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1;
|
||||
|
||||
public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn) };
|
||||
public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn), typeof(OsuModDepth) };
|
||||
|
||||
public const double FADE_IN_DURATION_MULTIPLIER = 0.4;
|
||||
public const double FADE_OUT_DURATION_MULTIPLIER = 0.3;
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public override ModType Type => ModType.Fun;
|
||||
public override LocalisableString Description => "No need to chase the circles – your cursor is a magnet!";
|
||||
public override double ScoreMultiplier => 0.5;
|
||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModRelax), typeof(OsuModRepel), typeof(OsuModBubbles) };
|
||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModRelax), typeof(OsuModRepel), typeof(OsuModBubbles), typeof(OsuModDepth) };
|
||||
|
||||
[SettingSource("Attraction strength", "How strong the pull is.", 0)]
|
||||
public BindableFloat AttractionStrength { get; } = new BindableFloat(0.5f)
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
// 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.Game.Rulesets.Mods;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public class OsuModNoFail : ModNoFail
|
||||
{
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
|
||||
protected virtual float EndScale => 1;
|
||||
|
||||
public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn), typeof(OsuModObjectScaleTween) };
|
||||
public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn), typeof(OsuModObjectScaleTween), typeof(OsuModDepth) };
|
||||
|
||||
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state)
|
||||
{
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
// 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.Game.Rulesets.Mods;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public class OsuModPerfect : ModPerfect
|
||||
{
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot) }).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
if (!slider.HeadCircle.IsHit)
|
||||
handleHitCircle(slider.HeadCircle);
|
||||
|
||||
requiresHold |= slider.Ball.IsHovered || h.IsHovered;
|
||||
requiresHold |= slider.SliderInputManager.IsMouseInFollowArea(true);
|
||||
break;
|
||||
|
||||
case DrawableSpinner spinner:
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public override ModType Type => ModType.Fun;
|
||||
public override LocalisableString Description => "Hit objects run away!";
|
||||
public override double ScoreMultiplier => 1;
|
||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModMagnetised), typeof(OsuModBubbles) };
|
||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModMagnetised), typeof(OsuModBubbles), typeof(OsuModDepth) };
|
||||
|
||||
[SettingSource("Repulsion strength", "How strong the repulsion is.", 0)]
|
||||
public BindableFloat RepulsionStrength { get; } = new BindableFloat(0.5f)
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
|
||||
// todo: this mod needs to be incompatible with "hidden" due to forcing the circle to remain opaque,
|
||||
// further implementation will be required for supporting that.
|
||||
public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModObjectScaleTween), typeof(OsuModHidden) };
|
||||
public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModObjectScaleTween), typeof(OsuModHidden), typeof(OsuModDepth) };
|
||||
|
||||
private const int rotate_offset = 360;
|
||||
private const float rotate_starting_width = 2;
|
||||
|
||||
@@ -36,6 +36,9 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
if (e.NewValue || slider.Judged) return;
|
||||
|
||||
if (slider.Time.Current < slider.HitObject.StartTime)
|
||||
return;
|
||||
|
||||
var tail = slider.NestedHitObjects.OfType<StrictTrackingDrawableSliderTail>().First();
|
||||
|
||||
if (!tail.Judged)
|
||||
|
||||
@@ -11,7 +11,6 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[]
|
||||
{
|
||||
typeof(OsuModAutopilot),
|
||||
typeof(OsuModTargetPractice),
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
@@ -47,7 +47,8 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
typeof(OsuModRandom),
|
||||
typeof(OsuModSpunOut),
|
||||
typeof(OsuModStrictTracking),
|
||||
typeof(OsuModSuddenDeath)
|
||||
typeof(OsuModSuddenDeath),
|
||||
typeof(OsuModDepth)
|
||||
}).ToArray();
|
||||
|
||||
[SettingSource("Seed", "Use a custom seed instead of a random one", SettingControlType = typeof(SettingsNumberBox))]
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public override LocalisableString Description => "Put your faith in the approach circles...";
|
||||
public override double ScoreMultiplier => 1;
|
||||
|
||||
public override Type[] IncompatibleMods => new[] { typeof(IHidesApproachCircles) };
|
||||
public override Type[] IncompatibleMods => new[] { typeof(IHidesApproachCircles), typeof(OsuModDepth) };
|
||||
|
||||
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state)
|
||||
{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user