Merge branch 'master' into fix-ios-import

This commit is contained in:
Dean Herbert
2019-08-12 21:38:22 +09:00
committed by GitHub
78 changed files with 1286 additions and 562 deletions

0
.gitmodules vendored
View File

View File

@ -1,2 +0,0 @@
language: csharp
solution: osu.sln

View File

@ -34,7 +34,7 @@ If you are not interested in developing the game, you can still consume our [bin
| ------------- | ------------- | | ------------- | ------------- |
- **Linux** users are recommended to self-compile until we have official deployment in place. - **Linux** users are recommended to self-compile until we have official deployment in place.
- **iOS** users can join the [TestFlight beta program](https://t.co/PasE1zrHhw) (note that due to high demand this is regularly full). - **iOS** users can join the [TestFlight beta program](https://testflight.apple.com/join/2tLcjWlF) (note that due to high demand this is regularly full).
- **Android** users can self-compile, and expect a public beta soon. - **Android** users can self-compile, and expect a public beta soon.
If your platform is not listed above, there is still a chance you can manually build it by following the instructions below. If your platform is not listed above, there is still a chance you can manually build it by following the instructions below.

View File

@ -62,7 +62,7 @@
<Reference Include="Java.Interop" /> <Reference Include="Java.Interop" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.702.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2019.809.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2019.730.0" /> <PackageReference Include="ppy.osu.Framework.Android" Version="2019.809.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -4,7 +4,7 @@
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" /> <PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
<PackageReference Include="NUnit" Version="3.12.0" /> <PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.13.0" /> <PackageReference Include="NUnit3TestAdapter" Version="3.14.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" /> <PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
</ItemGroup> </ItemGroup>
<PropertyGroup Label="Project"> <PropertyGroup Label="Project">

View File

@ -4,7 +4,7 @@
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" /> <PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
<PackageReference Include="NUnit" Version="3.12.0" /> <PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.13.0" /> <PackageReference Include="NUnit3TestAdapter" Version="3.14.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" /> <PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
</ItemGroup> </ItemGroup>
<PropertyGroup Label="Project"> <PropertyGroup Label="Project">

View File

@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
/// <summary> /// <summary>
/// Keep the same as last row. /// Keep the same as last row.
/// </summary> /// </summary>
ForceStack = 1 << 0, ForceStack = 1,
/// <summary> /// <summary>
/// Keep different from last row. /// Keep different from last row.

View File

@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
} }
} }
private Cached subtractionCache = new Cached(); private readonly Cached subtractionCache = new Cached();
public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true)
{ {

View File

@ -8,7 +8,6 @@ using osu.Framework.MathUtils;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Osu.Tests namespace osu.Game.Rulesets.Osu.Tests
@ -23,6 +22,7 @@ namespace osu.Game.Rulesets.Osu.Tests
[TestCase("slider-ticks")] [TestCase("slider-ticks")]
[TestCase("repeat-slider")] [TestCase("repeat-slider")]
[TestCase("uneven-repeat-slider")] [TestCase("uneven-repeat-slider")]
[TestCase("old-stacking")]
public void Test(string name) => base.Test(name); public void Test(string name) => base.Test(name);
protected override IEnumerable<ConvertValue> CreateConvertValue(HitObject hitObject) protected override IEnumerable<ConvertValue> CreateConvertValue(HitObject hitObject)
@ -31,22 +31,22 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
case Slider slider: case Slider slider:
foreach (var nested in slider.NestedHitObjects) foreach (var nested in slider.NestedHitObjects)
yield return createConvertValue(nested); yield return createConvertValue((OsuHitObject)nested);
break; break;
default: default:
yield return createConvertValue(hitObject); yield return createConvertValue((OsuHitObject)hitObject);
break; break;
} }
ConvertValue createConvertValue(HitObject obj) => new ConvertValue ConvertValue createConvertValue(OsuHitObject obj) => new ConvertValue
{ {
StartTime = obj.StartTime, StartTime = obj.StartTime,
EndTime = (obj as IHasEndTime)?.EndTime ?? obj.StartTime, EndTime = (obj as IHasEndTime)?.EndTime ?? obj.StartTime,
X = (obj as IHasPosition)?.X ?? OsuPlayfield.BASE_SIZE.X / 2, X = obj.StackedPosition.X,
Y = (obj as IHasPosition)?.Y ?? OsuPlayfield.BASE_SIZE.Y / 2, Y = obj.StackedPosition.Y
}; };
} }

View File

@ -4,7 +4,7 @@
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" /> <PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
<PackageReference Include="NUnit" Version="3.12.0" /> <PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.13.0" /> <PackageReference Include="NUnit3TestAdapter" Version="3.14.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" /> <PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
</ItemGroup> </ItemGroup>
<PropertyGroup Label="Project"> <PropertyGroup Label="Project">

View File

@ -6,6 +6,7 @@ using osu.Framework.Graphics;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osuTK;
namespace osu.Game.Rulesets.Osu.Beatmaps namespace osu.Game.Rulesets.Osu.Beatmaps
{ {
@ -208,17 +209,22 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
if (beatmap.HitObjects[j].StartTime - stackThreshold > startTime) if (beatmap.HitObjects[j].StartTime - stackThreshold > startTime)
break; break;
// The start position of the hitobject, or the position at the end of the path if the hitobject is a slider
Vector2 position2 = currHitObject is Slider currSlider
? currSlider.Position + currSlider.Path.PositionAt(1)
: currHitObject.Position;
if (Vector2Extensions.Distance(beatmap.HitObjects[j].Position, currHitObject.Position) < stack_distance) if (Vector2Extensions.Distance(beatmap.HitObjects[j].Position, currHitObject.Position) < stack_distance)
{ {
currHitObject.StackHeight++; currHitObject.StackHeight++;
startTime = (beatmap.HitObjects[j] as IHasEndTime)?.EndTime ?? beatmap.HitObjects[i].StartTime; startTime = (beatmap.HitObjects[j] as IHasEndTime)?.EndTime ?? beatmap.HitObjects[j].StartTime;
} }
else if (Vector2Extensions.Distance(beatmap.HitObjects[j].Position, currHitObject.EndPosition) < stack_distance) else if (Vector2Extensions.Distance(beatmap.HitObjects[j].Position, position2) < stack_distance)
{ {
//Case for sliders - bump notes down and right, rather than up and left. //Case for sliders - bump notes down and right, rather than up and left.
sliderStack++; sliderStack++;
beatmap.HitObjects[j].StackHeight -= sliderStack; beatmap.HitObjects[j].StackHeight -= sliderStack;
startTime = (beatmap.HitObjects[j] as IHasEndTime)?.EndTime ?? beatmap.HitObjects[i].StartTime; startTime = (beatmap.HitObjects[j] as IHasEndTime)?.EndTime ?? beatmap.HitObjects[j].StartTime;
} }
} }
} }

View File

@ -2,13 +2,19 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.StateChanges;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Replays;
using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Osu.Mods namespace osu.Game.Rulesets.Osu.Mods
{ {
public class OsuModAutopilot : Mod public class OsuModAutopilot : Mod, IApplicableFailOverride, IUpdatableByPlayfield, IApplicableToDrawableRuleset<OsuHitObject>
{ {
public override string Name => "Autopilot"; public override string Name => "Autopilot";
public override string Acronym => "AP"; public override string Acronym => "AP";
@ -17,5 +23,40 @@ namespace osu.Game.Rulesets.Osu.Mods
public override string Description => @"Automatic cursor movement - just follow the rhythm."; public override string Description => @"Automatic cursor movement - just follow the rhythm.";
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail), typeof(ModAutoplay) }; public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail), typeof(ModAutoplay) };
public bool AllowFail => false;
private OsuInputManager inputManager;
private List<OsuReplayFrame> replayFrames;
private int currentFrame;
public void Update(Playfield playfield)
{
if (currentFrame == replayFrames.Count - 1) return;
double time = playfield.Time.Current;
// Very naive implementation of autopilot based on proximity to replay frames.
// TODO: this needs to be based on user interactions to better match stable (pausing until judgement is registered).
if (Math.Abs(replayFrames[currentFrame + 1].Time - time) <= Math.Abs(replayFrames[currentFrame].Time - time))
{
currentFrame++;
new MousePositionAbsoluteInput { Position = playfield.ToScreenSpace(replayFrames[currentFrame].Position) }.Apply(inputManager.CurrentState, inputManager);
}
// TODO: Implement the functionality to automatically spin spinners
}
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
{
// Grab the input manager to disable the user's cursor, and for future use
inputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager;
inputManager.AllowUserCursorMovement = false;
// Generate the replay frames the cursor should follow
replayFrames = new OsuAutoGenerator(drawableRuleset.Beatmap).Generate().Frames.Cast<OsuReplayFrame>().ToList();
}
} }
} }

View File

@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
var spanProgress = slider.ProgressAt(completionProgress); var spanProgress = slider.ProgressAt(completionProgress);
double start = 0; double start = 0;
double end = SnakingIn.Value ? MathHelper.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / slider.TimeFadeIn, 0, 1) : 1; double end = SnakingIn.Value ? MathHelper.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / (slider.TimePreempt / 3), 0, 1) : 1;
if (span >= slider.SpanCount() - 1) if (span >= slider.SpanCount() - 1)
{ {

View File

@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Osu.Objects
base.ApplyDefaultsToSelf(controlPointInfo, difficulty); base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
TimePreempt = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450); TimePreempt = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450);
TimeFadeIn = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1200, 800, 300); TimeFadeIn = 400; // as per osu-stable
Scale = (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5) / 2; Scale = (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5) / 2;
} }

View File

@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Objects
public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity; public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity;
public double Duration => EndTime - StartTime; public double Duration => EndTime - StartTime;
private Cached<Vector2> endPositionCache; private readonly Cached<Vector2> endPositionCache = new Cached<Vector2>();
public override Vector2 EndPosition => endPositionCache.IsValid ? endPositionCache.Value : endPositionCache.Value = Position + this.CurvePositionAt(1); public override Vector2 EndPosition => endPositionCache.IsValid ? endPositionCache.Value : endPositionCache.Value = Position + this.CurvePositionAt(1);

View File

@ -18,6 +18,12 @@ namespace osu.Game.Rulesets.Osu
set => ((OsuKeyBindingContainer)KeyBindingContainer).AllowUserPresses = value; set => ((OsuKeyBindingContainer)KeyBindingContainer).AllowUserPresses = value;
} }
/// <summary>
/// Whether the user's cursor movement events should be accepted.
/// Can be used to block only movement while still accepting button input.
/// </summary>
public bool AllowUserCursorMovement { get; set; } = true;
protected override RulesetKeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) protected override RulesetKeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
=> new OsuKeyBindingContainer(ruleset, variant, unique); => new OsuKeyBindingContainer(ruleset, variant, unique);
@ -26,6 +32,13 @@ namespace osu.Game.Rulesets.Osu
{ {
} }
protected override bool Handle(UIEvent e)
{
if (e is MouseMoveEvent && !AllowUserCursorMovement) return false;
return base.Handle(e);
}
private class OsuKeyBindingContainer : RulesetKeyBindingContainer private class OsuKeyBindingContainer : RulesetKeyBindingContainer
{ {
public bool AllowUserPresses = true; public bool AllowUserPresses = true;

View File

@ -0,0 +1,278 @@
{
"Mappings": [{
"StartTime": 32165,
"Objects": [{
"StartTime": 32165,
"EndTime": 32165,
"X": 32,
"Y": 320
}]
},
{
"StartTime": 32517,
"Objects": [{
"StartTime": 32517,
"EndTime": 32517,
"X": 246.396057,
"Y": 182.396057
}]
},
{
"StartTime": 32605,
"Objects": [{
"StartTime": 32605,
"EndTime": 32605,
"X": 249.597382,
"Y": 185.597382
}]
},
{
"StartTime": 32693,
"Objects": [{
"StartTime": 32693,
"EndTime": 32693,
"X": 252.798691,
"Y": 188.798691
}]
},
{
"StartTime": 32781,
"Objects": [{
"StartTime": 32781,
"EndTime": 32781,
"X": 256,
"Y": 192
}]
},
{
"StartTime": 33248,
"Objects": [{
"StartTime": 33248,
"EndTime": 33248,
"X": 39.3960648,
"Y": 76.3960648
}]
},
{
"StartTime": 33307,
"Objects": [{
"StartTime": 33307,
"EndTime": 33307,
"X": 42.5973778,
"Y": 79.597374
}]
},
{
"StartTime": 33383,
"Objects": [{
"StartTime": 33383,
"EndTime": 33383,
"X": 45.798687,
"Y": 82.79869
}]
},
{
"StartTime": 33459,
"Objects": [{
"StartTime": 33459,
"EndTime": 33459,
"X": 49,
"Y": 86
},
{
"StartTime": 33635,
"EndTime": 33635,
"X": 123.847847,
"Y": 85.7988
},
{
"StartTime": 33811,
"EndTime": 33811,
"X": 198.6957,
"Y": 85.5975952
},
{
"StartTime": 33988,
"EndTime": 33988,
"X": 273.9688,
"Y": 85.39525
},
{
"StartTime": 34164,
"EndTime": 34164,
"X": 348.816681,
"Y": 85.19404
},
{
"StartTime": 34246,
"EndTime": 34246,
"X": 398.998718,
"Y": 85.05914
}
]
},
{
"StartTime": 34341,
"Objects": [{
"StartTime": 34341,
"EndTime": 34341,
"X": 401.201324,
"Y": 88.20131
}]
},
{
"StartTime": 34400,
"Objects": [{
"StartTime": 34400,
"EndTime": 34400,
"X": 404.402618,
"Y": 91.402626
}]
},
{
"StartTime": 34459,
"Objects": [{
"StartTime": 34459,
"EndTime": 34459,
"X": 407.603943,
"Y": 94.6039352
}]
},
{
"StartTime": 34989,
"Objects": [{
"StartTime": 34989,
"EndTime": 34989,
"X": 163,
"Y": 138
},
{
"StartTime": 35018,
"EndTime": 35018,
"X": 188,
"Y": 138
}
]
},
{
"StartTime": 35106,
"Objects": [{
"StartTime": 35106,
"EndTime": 35106,
"X": 163,
"Y": 138
},
{
"StartTime": 35135,
"EndTime": 35135,
"X": 188,
"Y": 138
}
]
},
{
"StartTime": 35224,
"Objects": [{
"StartTime": 35224,
"EndTime": 35224,
"X": 163,
"Y": 138
},
{
"StartTime": 35253,
"EndTime": 35253,
"X": 188,
"Y": 138
}
]
},
{
"StartTime": 35695,
"Objects": [{
"StartTime": 35695,
"EndTime": 35695,
"X": 166,
"Y": 76
},
{
"StartTime": 35871,
"EndTime": 35871,
"X": 240.99855,
"Y": 75.53417
},
{
"StartTime": 36011,
"EndTime": 36011,
"X": 315.9971,
"Y": 75.0683441
}
]
},
{
"StartTime": 36106,
"Objects": [{
"StartTime": 36106,
"EndTime": 36106,
"X": 315,
"Y": 75
},
{
"StartTime": 36282,
"EndTime": 36282,
"X": 240.001526,
"Y": 75.47769
},
{
"StartTime": 36422,
"EndTime": 36422,
"X": 165.003052,
"Y": 75.95539
}
]
},
{
"StartTime": 36518,
"Objects": [{
"StartTime": 36518,
"EndTime": 36518,
"X": 166,
"Y": 76
},
{
"StartTime": 36694,
"EndTime": 36694,
"X": 240.99855,
"Y": 75.53417
},
{
"StartTime": 36834,
"EndTime": 36834,
"X": 315.9971,
"Y": 75.0683441
}
]
},
{
"StartTime": 36929,
"Objects": [{
"StartTime": 36929,
"EndTime": 36929,
"X": 315,
"Y": 75
},
{
"StartTime": 37105,
"EndTime": 37105,
"X": 240.001526,
"Y": 75.47769
},
{
"StartTime": 37245,
"EndTime": 37245,
"X": 165.003052,
"Y": 75.95539
}
]
}
]
}

View File

@ -0,0 +1,40 @@
osu file format v3
[Difficulty]
HPDrainRate:3
CircleSize:5
OverallDifficulty:8
ApproachRate:8
SliderMultiplier:1.5
SliderTickRate:2
[TimingPoints]
48,352.941176470588,4,1,1,100,1,0
[HitObjects]
// Hit circles
32,320,32165,5,2,0:0:0:0:
256,192,32517,5,0,0:0:0:0:
256,192,32605,1,0,0:0:0:0:
256,192,32693,1,0,0:0:0:0:
256,192,32781,1,0,0:0:0:0:
// Hit circles on slider endpoints
49,86,33248,1,0,0:0:0:0:
49,86,33307,1,0,0:0:0:0:
49,86,33383,1,0,0:0:0:0:
49,86,33459,2,0,L|421:85,1,350
398,85,34341,1,0,0:0:0:0:
398,85,34400,1,0,0:0:0:0:
398,85,34459,1,0,0:0:0:0:
// Sliders
163,138,34989,2,0,L|196:138,1,25
163,138,35106,2,0,L|196:138,1,25
163,138,35224,2,0,L|196:138,1,25
// Reversed sliders
166,76,35695,2,0,L|327:75,1,150
315,75,36106,2,0,L|158:76,1,150
166,76,36518,2,0,L|327:75,1,150
315,75,36929,2,0,L|158:76,1,150

View File

@ -13,13 +13,15 @@ namespace osu.Game.Rulesets.Osu.UI
protected override Container<Drawable> Content => content; protected override Container<Drawable> Content => content;
private readonly Container content; private readonly Container content;
private const float playfield_size_adjust = 0.8f;
public OsuPlayfieldAdjustmentContainer() public OsuPlayfieldAdjustmentContainer()
{ {
Anchor = Anchor.Centre; Anchor = Anchor.Centre;
Origin = Anchor.Centre; Origin = Anchor.Centre;
// Calculated from osu!stable as 512 (default gamefield size) / 640 (default window size) // Calculated from osu!stable as 512 (default gamefield size) / 640 (default window size)
Size = new Vector2(0.8f); Size = new Vector2(playfield_size_adjust);
InternalChild = new Container InternalChild = new Container
{ {
@ -41,7 +43,19 @@ namespace osu.Game.Rulesets.Osu.UI
{ {
base.Update(); base.Update();
// The following calculation results in a constant of 1.6 when OsuPlayfieldAdjustmentContainer
// is consuming the full game_size. This matches the osu-stable "magic ratio".
//
// game_size = DrawSizePreservingFillContainer.TargetSize = new Vector2(1024, 768)
//
// Parent is a 4:3 aspect enforced, using height as the constricting dimension
// Parent.ChildSize.X = min(game_size.X, game_size.Y * (4 / 3)) * playfield_size_adjust
// Parent.ChildSize.X = 819.2
//
// Scale = 819.2 / 512
// Scale = 1.6
Scale = new Vector2(Parent.ChildSize.X / OsuPlayfield.BASE_SIZE.X); Scale = new Vector2(Parent.ChildSize.X / OsuPlayfield.BASE_SIZE.X);
// Size = 0.625
Size = Vector2.Divide(Vector2.One, Scale); Size = Vector2.Divide(Vector2.One, Scale);
} }
} }

View File

@ -4,7 +4,7 @@
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" /> <PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
<PackageReference Include="NUnit" Version="3.12.0" /> <PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.13.0" /> <PackageReference Include="NUnit3TestAdapter" Version="3.14.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" /> <PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
</ItemGroup> </ItemGroup>
<PropertyGroup Label="Project"> <PropertyGroup Label="Project">

View File

@ -482,5 +482,17 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual(hitObjects[0].Samples[0].Bank, hitObjects[0].Samples[1].Bank); Assert.AreEqual(hitObjects[0].Samples[0].Bank, hitObjects[0].Samples[1].Bank);
} }
} }
[Test]
public void TestInvalidEventStillPasses()
{
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
using (var badResStream = TestResources.OpenResource("invalid-events.osu"))
using (var badStream = new StreamReader(badResStream))
{
Assert.DoesNotThrow(() => decoder.Decode(badStream));
}
}
} }
} }

View File

@ -0,0 +1,14 @@
osu file format v14
[Events]
bad,event,this,should,fail
//Background and Video events
0,0,"machinetop_background.jpg",0,0
//Break Periods
2,122474,140135
//Storyboard Layer 0 (Background)
this,is,also,bad
//Storyboard Layer 1 (Fail)
//Storyboard Layer 2 (Pass)
//Storyboard Layer 3 (Foreground)
//Storyboard Sound Samples

View File

@ -119,14 +119,14 @@ namespace osu.Game.Tests.Visual.Background
{ {
performFullSetup(); performFullSetup();
createFakeStoryboard(); createFakeStoryboard();
AddStep("Storyboard Enabled", () => AddStep("Enable Storyboard", () =>
{ {
player.ReplacesBackground.Value = true; player.ReplacesBackground.Value = true;
player.StoryboardEnabled.Value = true; player.StoryboardEnabled.Value = true;
}); });
waitForDim(); waitForDim();
AddAssert("Background is invisible, storyboard is visible", () => songSelect.IsBackgroundInvisible() && player.IsStoryboardVisible); AddAssert("Background is invisible, storyboard is visible", () => songSelect.IsBackgroundInvisible() && player.IsStoryboardVisible);
AddStep("Storyboard Disabled", () => AddStep("Disable Storyboard", () =>
{ {
player.ReplacesBackground.Value = false; player.ReplacesBackground.Value = false;
player.StoryboardEnabled.Value = false; player.StoryboardEnabled.Value = false;
@ -149,22 +149,44 @@ namespace osu.Game.Tests.Visual.Background
} }
/// <summary> /// <summary>
/// Check if the <see cref="UserDimContainer"/> is properly accepting user-defined visual changes at all. /// Ensure <see cref="UserDimContainer"/> is properly accepting user-defined visual changes for a background.
/// </summary> /// </summary>
[Test] [Test]
public void DisableUserDimTest() public void DisableUserDimBackgroundTest()
{ {
performFullSetup(); performFullSetup();
waitForDim(); waitForDim();
AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
AddStep("EnableUserDim disabled", () => songSelect.DimEnabled.Value = false); AddStep("Enable user dim", () => songSelect.DimEnabled.Value = false);
waitForDim(); waitForDim();
AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsUserBlurDisabled()); AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsUserBlurDisabled());
AddStep("EnableUserDim enabled", () => songSelect.DimEnabled.Value = true); AddStep("Disable user dim", () => songSelect.DimEnabled.Value = true);
waitForDim(); waitForDim();
AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
} }
/// <summary>
/// Ensure <see cref="UserDimContainer"/> is properly accepting user-defined visual changes for a storyboard.
/// </summary>
[Test]
public void DisableUserDimStoryboardTest()
{
performFullSetup();
createFakeStoryboard();
AddStep("Enable Storyboard", () =>
{
player.ReplacesBackground.Value = true;
player.StoryboardEnabled.Value = true;
});
AddStep("Enable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = true);
AddStep("Set dim level to 1", () => songSelect.DimLevel.Value = 1f);
waitForDim();
AddAssert("Storyboard is invisible", () => !player.IsStoryboardVisible);
AddStep("Disable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = false);
waitForDim();
AddAssert("Storyboard is visible", () => player.IsStoryboardVisible);
}
/// <summary> /// <summary>
/// Check if the visual settings container retains dim and blur when pausing /// Check if the visual settings container retains dim and blur when pausing
/// </summary> /// </summary>

View File

@ -1,8 +1,11 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Timing;
using osu.Game.Beatmaps.Timing; using osu.Game.Beatmaps.Timing;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
@ -11,78 +14,172 @@ namespace osu.Game.Tests.Visual.Gameplay
[TestFixture] [TestFixture]
public class TestSceneBreakOverlay : OsuTestScene public class TestSceneBreakOverlay : OsuTestScene
{ {
private readonly BreakOverlay breakOverlay; public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(BreakOverlay),
};
private readonly TestBreakOverlay breakOverlay;
private readonly IReadOnlyList<BreakPeriod> testBreaks = new List<BreakPeriod>
{
new BreakPeriod
{
StartTime = 1000,
EndTime = 5000,
},
new BreakPeriod
{
StartTime = 6000,
EndTime = 13500,
},
};
public TestSceneBreakOverlay() public TestSceneBreakOverlay()
{ {
Child = breakOverlay = new BreakOverlay(true); Add(breakOverlay = new TestBreakOverlay(true));
AddStep("2s break", () => startBreak(2000));
AddStep("5s break", () => startBreak(5000));
AddStep("10s break", () => startBreak(10000));
AddStep("15s break", () => startBreak(15000));
AddStep("2s, 2s", startMultipleBreaks);
AddStep("0.5s, 0.7s, 1s, 2s", startAnotherMultipleBreaks);
} }
private void startBreak(double duration) [Test]
public void TestShowBreaks()
{ {
breakOverlay.Breaks = new List<BreakPeriod> setClock(false);
addShowBreakStep(2);
addShowBreakStep(5);
addShowBreakStep(15);
}
[Test]
public void TestNoEffectsBreak()
{
var shortBreak = new BreakPeriod { EndTime = 500 };
setClock(true);
loadBreaksStep("short break", new[] { shortBreak });
addBreakSeeks(shortBreak, false);
}
[Test]
public void TestMultipleBreaks()
{
setClock(true);
loadBreaksStep("multiple breaks", testBreaks);
foreach (var b in testBreaks)
addBreakSeeks(b, false);
}
[Test]
public void TestRewindBreaks()
{
setClock(true);
loadBreaksStep("multiple breaks", testBreaks);
foreach (var b in testBreaks.Reverse())
addBreakSeeks(b, true);
}
[Test]
public void TestSkipBreaks()
{
setClock(true);
loadBreaksStep("multiple breaks", testBreaks);
seekAndAssertBreak("seek to break start", testBreaks[1].StartTime, true);
AddAssert("is skipped to break #2", () => breakOverlay.CurrentBreakIndex == 1);
seekAndAssertBreak("seek to break middle", testBreaks[1].StartTime + testBreaks[1].Duration / 2, true);
seekAndAssertBreak("seek to break end", testBreaks[1].EndTime, false);
seekAndAssertBreak("seek to break after end", testBreaks[1].EndTime + 500, false);
}
private void addShowBreakStep(double seconds)
{
AddStep($"show '{seconds}s' break", () => breakOverlay.Breaks = new List<BreakPeriod>
{ {
new BreakPeriod new BreakPeriod
{ {
StartTime = Clock.CurrentTime, StartTime = Clock.CurrentTime,
EndTime = Clock.CurrentTime + duration, EndTime = Clock.CurrentTime + seconds * 1000,
} }
}; });
} }
private void startMultipleBreaks() private void setClock(bool useManual)
{ {
double currentTime = Clock.CurrentTime; AddStep($"set {(useManual ? "manual" : "realtime")} clock", () => breakOverlay.SwitchClock(useManual));
breakOverlay.Breaks = new List<BreakPeriod>
{
new BreakPeriod
{
StartTime = currentTime,
EndTime = currentTime + 2000,
},
new BreakPeriod
{
StartTime = currentTime + 4000,
EndTime = currentTime + 6000,
}
};
} }
private void startAnotherMultipleBreaks() private void loadBreaksStep(string breakDescription, IReadOnlyList<BreakPeriod> breaks)
{ {
double currentTime = Clock.CurrentTime; AddStep($"load {breakDescription}", () => breakOverlay.Breaks = breaks);
seekAndAssertBreak("seek back to 0", 0, false);
breakOverlay.Breaks = new List<BreakPeriod> }
{
new BreakPeriod // Duration is less than 650 - too short to appear private void addBreakSeeks(BreakPeriod b, bool isReversed)
{ {
StartTime = currentTime, if (isReversed)
EndTime = currentTime + 500, {
}, seekAndAssertBreak("seek to break after end", b.EndTime + 500, false);
new BreakPeriod seekAndAssertBreak("seek to break end", b.EndTime, false);
{ seekAndAssertBreak("seek to break middle", b.StartTime + b.Duration / 2, b.HasEffect);
StartTime = currentTime + 1500, seekAndAssertBreak("seek to break start", b.StartTime, b.HasEffect);
EndTime = currentTime + 2200, }
}, else
new BreakPeriod {
{ seekAndAssertBreak("seek to break start", b.StartTime, b.HasEffect);
StartTime = currentTime + 3200, seekAndAssertBreak("seek to break middle", b.StartTime + b.Duration / 2, b.HasEffect);
EndTime = currentTime + 4200, seekAndAssertBreak("seek to break end", b.EndTime, false);
}, seekAndAssertBreak("seek to break after end", b.EndTime + 500, false);
new BreakPeriod }
{ }
StartTime = currentTime + 5200,
EndTime = currentTime + 7200, private void seekAndAssertBreak(string seekStepDescription, double time, bool shouldBeBreak)
{
AddStep(seekStepDescription, () => breakOverlay.ManualClockTime = time);
AddAssert($"is{(!shouldBeBreak ? " not" : string.Empty)} break time", () =>
{
breakOverlay.ProgressTime();
return breakOverlay.IsBreakTime.Value == shouldBeBreak;
});
}
private class TestBreakOverlay : BreakOverlay
{
private readonly FramedClock framedManualClock;
private readonly ManualClock manualClock;
private IFrameBasedClock originalClock;
public new int CurrentBreakIndex => base.CurrentBreakIndex;
public double ManualClockTime
{
get => manualClock.CurrentTime;
set => manualClock.CurrentTime = value;
}
public TestBreakOverlay(bool letterboxing)
: base(letterboxing)
{
framedManualClock = new FramedClock(manualClock = new ManualClock());
ProcessCustomClock = false;
}
public void ProgressTime()
{
framedManualClock.ProcessFrame();
Update();
}
public void SwitchClock(bool setManual) => Clock = setManual ? framedManualClock : originalClock;
protected override void LoadComplete()
{
base.LoadComplete();
originalClock = Clock;
} }
};
} }
} }
} }

View File

@ -69,28 +69,22 @@ namespace osu.Game.Tests.Visual.Online
} }
}); });
AddStep("null user", () => graph.User.Value = null); AddStep("null user", () => graph.Statistics.Value = null);
AddStep("rank only", () => AddStep("rank only", () =>
{ {
graph.User.Value = new User graph.Statistics.Value = new UserStatistics
{
Statistics = new UserStatistics
{ {
Ranks = new UserStatistics.UserRanks { Global = 123456 }, Ranks = new UserStatistics.UserRanks { Global = 123456 },
PP = 12345, PP = 12345,
}
}; };
}); });
AddStep("with rank history", () => AddStep("with rank history", () =>
{ {
graph.User.Value = new User graph.Statistics.Value = new UserStatistics
{
Statistics = new UserStatistics
{ {
Ranks = new UserStatistics.UserRanks { Global = 89000 }, Ranks = new UserStatistics.UserRanks { Global = 89000 },
PP = 12345, PP = 12345,
},
RankHistory = new User.RankHistoryData RankHistory = new User.RankHistoryData
{ {
Data = data, Data = data,
@ -100,13 +94,10 @@ namespace osu.Game.Tests.Visual.Online
AddStep("with zero values", () => AddStep("with zero values", () =>
{ {
graph.User.Value = new User graph.Statistics.Value = new UserStatistics
{
Statistics = new UserStatistics
{ {
Ranks = new UserStatistics.UserRanks { Global = 89000 }, Ranks = new UserStatistics.UserRanks { Global = 89000 },
PP = 12345, PP = 12345,
},
RankHistory = new User.RankHistoryData RankHistory = new User.RankHistoryData
{ {
Data = dataWithZeros, Data = dataWithZeros,
@ -116,13 +107,10 @@ namespace osu.Game.Tests.Visual.Online
AddStep("small amount of data", () => AddStep("small amount of data", () =>
{ {
graph.User.Value = new User graph.Statistics.Value = new UserStatistics
{
Statistics = new UserStatistics
{ {
Ranks = new UserStatistics.UserRanks { Global = 12000 }, Ranks = new UserStatistics.UserRanks { Global = 12000 },
PP = 12345, PP = 12345,
},
RankHistory = new User.RankHistoryData RankHistory = new User.RankHistoryData
{ {
Data = smallData, Data = smallData,
@ -132,13 +120,10 @@ namespace osu.Game.Tests.Visual.Online
AddStep("graph with edges", () => AddStep("graph with edges", () =>
{ {
graph.User.Value = new User graph.Statistics.Value = new UserStatistics
{
Statistics = new UserStatistics
{ {
Ranks = new UserStatistics.UserRanks { Global = 12000 }, Ranks = new UserStatistics.UserRanks { Global = 12000 },
PP = 12345, PP = 12345,
},
RankHistory = new User.RankHistoryData RankHistory = new User.RankHistoryData
{ {
Data = edgyData, Data = edgyData,

View File

@ -50,13 +50,13 @@ namespace osu.Game.Tests.Visual.Online
{ {
Current = 727, Current = 727,
Progress = 69, Progress = 69,
}
}, },
RankHistory = new User.RankHistoryData RankHistory = new User.RankHistoryData
{ {
Mode = @"osu", Mode = @"osu",
Data = Enumerable.Range(2345, 45).Concat(Enumerable.Range(2109, 40)).ToArray() Data = Enumerable.Range(2345, 45).Concat(Enumerable.Range(2109, 40)).ToArray()
}, },
},
Badges = new[] Badges = new[]
{ {
new Badge new Badge

View File

@ -0,0 +1,109 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mania;
using osu.Game.Users;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Game.Rulesets.Taiko;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Tests.Visual.Online
{
[TestFixture]
public class TestSceneUserRequest : OsuTestScene
{
[Resolved]
private IAPIProvider api { get; set; }
private readonly Bindable<User> user = new Bindable<User>();
private GetUserRequest request;
private readonly DimmedLoadingLayer loading;
public TestSceneUserRequest()
{
Add(new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
new UserTestContainer
{
User = { BindTarget = user }
},
loading = new DimmedLoadingLayer
{
Alpha = 0
}
}
});
}
protected override void LoadComplete()
{
base.LoadComplete();
AddStep(@"local user", () => getUser());
AddStep(@"local user with taiko ruleset", () => getUser(ruleset: new TaikoRuleset().RulesetInfo));
AddStep(@"cookiezi", () => getUser(124493));
AddStep(@"cookiezi with mania ruleset", () => getUser(124493, new ManiaRuleset().RulesetInfo));
}
private void getUser(long? userId = null, RulesetInfo ruleset = null)
{
loading.Show();
request?.Cancel();
request = new GetUserRequest(userId, ruleset);
request.Success += user =>
{
this.user.Value = user;
loading.Hide();
};
api.Queue(request);
}
private class UserTestContainer : FillFlowContainer
{
public readonly Bindable<User> User = new Bindable<User>();
public UserTestContainer()
{
AutoSizeAxes = Axes.Both;
Direction = FillDirection.Vertical;
}
protected override void LoadComplete()
{
base.LoadComplete();
User.BindValueChanged(onUserUpdate, true);
}
private void onUserUpdate(ValueChangedEvent<User> user)
{
Clear();
AddRange(new Drawable[]
{
new SpriteText
{
Text = $@"Username: {user.NewValue?.Username}"
},
new SpriteText
{
Text = $@"RankedScore: {user.NewValue?.Statistics.RankedScore}"
},
});
}
}
}
}

View File

@ -82,14 +82,13 @@ namespace osu.Game.Tests.Visual.UserInterface
var easierMods = instance.GetModsFor(ModType.DifficultyReduction); var easierMods = instance.GetModsFor(ModType.DifficultyReduction);
var harderMods = instance.GetModsFor(ModType.DifficultyIncrease); var harderMods = instance.GetModsFor(ModType.DifficultyIncrease);
var assistMods = instance.GetModsFor(ModType.Automation);
var noFailMod = easierMods.FirstOrDefault(m => m is OsuModNoFail); var noFailMod = easierMods.FirstOrDefault(m => m is OsuModNoFail);
var hiddenMod = harderMods.FirstOrDefault(m => m is OsuModHidden); var hiddenMod = harderMods.FirstOrDefault(m => m is OsuModHidden);
var doubleTimeMod = harderMods.OfType<MultiMod>().FirstOrDefault(m => m.Mods.Any(a => a is OsuModDoubleTime)); var doubleTimeMod = harderMods.OfType<MultiMod>().FirstOrDefault(m => m.Mods.Any(a => a is OsuModDoubleTime));
var autoPilotMod = assistMods.FirstOrDefault(m => m is OsuModAutopilot); var spunOutMod = easierMods.FirstOrDefault(m => m is OsuModSpunOut);
var easy = easierMods.FirstOrDefault(m => m is OsuModEasy); var easy = easierMods.FirstOrDefault(m => m is OsuModEasy);
var hardRock = harderMods.FirstOrDefault(m => m is OsuModHardRock); var hardRock = harderMods.FirstOrDefault(m => m is OsuModHardRock);
@ -101,7 +100,7 @@ namespace osu.Game.Tests.Visual.UserInterface
testMultiplierTextColour(noFailMod, modSelect.LowMultiplierColour); testMultiplierTextColour(noFailMod, modSelect.LowMultiplierColour);
testMultiplierTextColour(hiddenMod, modSelect.HighMultiplierColour); testMultiplierTextColour(hiddenMod, modSelect.HighMultiplierColour);
testUnimplementedMod(autoPilotMod); testUnimplementedMod(spunOutMod);
} }
[Test] [Test]

View File

@ -5,7 +5,7 @@
<PackageReference Include="DeepEqual" Version="2.0.0" /> <PackageReference Include="DeepEqual" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
<PackageReference Include="NUnit" Version="3.12.0" /> <PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.13.0" /> <PackageReference Include="NUnit3TestAdapter" Version="3.14.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" /> <PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
</ItemGroup> </ItemGroup>
<PropertyGroup Label="Project"> <PropertyGroup Label="Project">

View File

@ -7,7 +7,7 @@
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" /> <PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
<PackageReference Include="NUnit" Version="3.12.0" /> <PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.13.0" /> <PackageReference Include="NUnit3TestAdapter" Version="3.14.0" />
</ItemGroup> </ItemGroup>
<PropertyGroup Label="Project"> <PropertyGroup Label="Project">
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>

View File

@ -88,7 +88,7 @@ namespace osu.Game.Tournament.Screens.Ladder
}; };
} }
private Cached layout = new Cached(); private readonly Cached layout = new Cached();
protected override void Update() protected override void Update()
{ {

View File

@ -60,5 +60,7 @@ namespace osu.Game.Beatmaps
public class Beatmap : Beatmap<HitObject> public class Beatmap : Beatmap<HitObject>
{ {
public new Beatmap Clone() => (Beatmap)base.Clone(); public new Beatmap Clone() => (Beatmap)base.Clone();
public override string ToString() => BeatmapInfo?.ToString() ?? base.ToString();
} }
} }

View File

@ -5,7 +5,6 @@ using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using osu.Framework.IO.File; using osu.Framework.IO.File;
using osu.Framework.Logging;
using osu.Game.Beatmaps.Timing; using osu.Game.Beatmaps.Timing;
using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Rulesets.Objects.Legacy;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
@ -290,8 +289,9 @@ namespace osu.Game.Beatmaps.Formats
string[] split = line.Split(','); string[] split = line.Split(',');
EventType type; EventType type;
if (!Enum.TryParse(split[0], out type)) if (!Enum.TryParse(split[0], out type))
throw new InvalidDataException($@"Unknown event type {split[0]}"); throw new InvalidDataException($@"Unknown event type: {split[0]}");
switch (type) switch (type)
{ {
@ -318,8 +318,6 @@ namespace osu.Game.Beatmaps.Formats
} }
private void handleTimingPoint(string line) private void handleTimingPoint(string line)
{
try
{ {
string[] split = line.Split(','); string[] split = line.Split(',');
@ -395,15 +393,6 @@ namespace osu.Game.Beatmaps.Formats
AutoGenerated = timingChange AutoGenerated = timingChange
}); });
} }
catch (FormatException)
{
Logger.Log("A timing point could not be parsed correctly and will be ignored", LoggingTarget.Runtime, LogLevel.Important);
}
catch (OverflowException)
{
Logger.Log("A timing point could not be parsed correctly and will be ignored", LoggingTarget.Runtime, LogLevel.Important);
}
}
private void handleTimingControlPoint(TimingControlPoint newPoint) private void handleTimingControlPoint(TimingControlPoint newPoint)
{ {

View File

@ -36,7 +36,7 @@ namespace osu.Game.Beatmaps.Formats
{ {
if (!Enum.TryParse(line.Substring(1, line.Length - 2), out section)) if (!Enum.TryParse(line.Substring(1, line.Length - 2), out section))
{ {
Logger.Log($"Unknown section \"{line}\" in {output}"); Logger.Log($"Unknown section \"{line}\" in \"{output}\"");
section = Section.None; section = Section.None;
} }
@ -49,7 +49,7 @@ namespace osu.Game.Beatmaps.Formats
} }
catch (Exception e) catch (Exception e)
{ {
Logger.Error(e, $"Failed to process line \"{line}\" into {output}"); Logger.Log($"Failed to process line \"{line}\" into \"{output}\": {e.Message}", LoggingTarget.Runtime, LogLevel.Important);
} }
} }
} }

View File

@ -82,8 +82,9 @@ namespace osu.Game.Beatmaps.Formats
storyboardSprite = null; storyboardSprite = null;
EventType type; EventType type;
if (!Enum.TryParse(split[0], out type)) if (!Enum.TryParse(split[0], out type))
throw new InvalidDataException($@"Unknown event type {split[0]}"); throw new InvalidDataException($@"Unknown event type: {split[0]}");
switch (type) switch (type)
{ {

View File

@ -9,7 +9,7 @@ namespace osu.Game.Beatmaps.Legacy
public enum LegacyMods public enum LegacyMods
{ {
None = 0, None = 0,
NoFail = 1 << 0, NoFail = 1,
Easy = 1 << 1, Easy = 1 << 1,
TouchDevice = 1 << 2, TouchDevice = 1 << 2,
Hidden = 1 << 3, Hidden = 1 << 3,

View File

@ -1,6 +1,8 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Game.Screens.Play;
namespace osu.Game.Beatmaps.Timing namespace osu.Game.Beatmaps.Timing
{ {
public class BreakPeriod public class BreakPeriod
@ -35,6 +37,6 @@ namespace osu.Game.Beatmaps.Timing
/// </summary> /// </summary>
/// <param name="time">The time to check in milliseconds.</param> /// <param name="time">The time to check in milliseconds.</param>
/// <returns>Whether the time falls within this <see cref="BreakPeriod"/>.</returns> /// <returns>Whether the time falls within this <see cref="BreakPeriod"/>.</returns>
public bool Contains(double time) => time >= StartTime && time <= EndTime; public bool Contains(double time) => time >= StartTime && time <= EndTime - BreakOverlay.BREAK_FADE_DURATION;
} }
} }

View File

@ -31,10 +31,21 @@ namespace osu.Game.Database
/// </summary> /// </summary>
/// <typeparam name="TModel">The model type.</typeparam> /// <typeparam name="TModel">The model type.</typeparam>
/// <typeparam name="TFileModel">The associated file join type.</typeparam> /// <typeparam name="TFileModel">The associated file join type.</typeparam>
public abstract class ArchiveModelManager<TModel, TFileModel> : ArchiveModelManager, ICanAcceptFiles, IModelManager<TModel> public abstract class ArchiveModelManager<TModel, TFileModel> : ICanAcceptFiles, IModelManager<TModel>
where TModel : class, IHasFiles<TFileModel>, IHasPrimaryKey, ISoftDelete where TModel : class, IHasFiles<TFileModel>, IHasPrimaryKey, ISoftDelete
where TFileModel : INamedFileInfo, new() where TFileModel : INamedFileInfo, new()
{ {
private const int import_queue_request_concurrency = 1;
/// <summary>
/// A singleton scheduler shared by all <see cref="ArchiveModelManager{TModel,TFileModel}"/>.
/// </summary>
/// <remarks>
/// This scheduler generally performs IO and CPU intensive work so concurrency is limited harshly.
/// It is mainly being used as a queue mechanism for large imports.
/// </remarks>
private static readonly ThreadedTaskScheduler import_scheduler = new ThreadedTaskScheduler(import_queue_request_concurrency, nameof(ArchiveModelManager<TModel, TFileModel>));
/// <summary> /// <summary>
/// Set an endpoint for notifications to be posted to. /// Set an endpoint for notifications to be posted to.
/// </summary> /// </summary>
@ -336,7 +347,7 @@ namespace osu.Game.Database
flushEvents(true); flushEvents(true);
return item; return item;
}, cancellationToken, TaskCreationOptions.HideScheduler, IMPORT_SCHEDULER).Unwrap(); }, cancellationToken, TaskCreationOptions.HideScheduler, import_scheduler).Unwrap();
/// <summary> /// <summary>
/// Perform an update of the specified item. /// Perform an update of the specified item.
@ -646,18 +657,4 @@ namespace osu.Game.Database
#endregion #endregion
} }
public abstract class ArchiveModelManager
{
private const int import_queue_request_concurrency = 1;
/// <summary>
/// A singleton scheduler shared by all <see cref="ArchiveModelManager{TModel,TFileModel}"/>.
/// </summary>
/// <remarks>
/// This scheduler generally performs IO and CPU intensive work so concurrency is limited harshly.
/// It is mainly being used as a queue mechanism for large imports.
/// </remarks>
protected static readonly ThreadedTaskScheduler IMPORT_SCHEDULER = new ThreadedTaskScheduler(import_queue_request_concurrency, nameof(ArchiveModelManager));
}
} }

View File

@ -191,7 +191,7 @@ namespace osu.Game.Graphics.Backgrounds
private readonly List<TriangleParticle> parts = new List<TriangleParticle>(); private readonly List<TriangleParticle> parts = new List<TriangleParticle>();
private Vector2 size; private Vector2 size;
private TriangleBatch<TexturedVertex2D> vertexBatch; private QuadBatch<TexturedVertex2D> vertexBatch;
public TrianglesDrawNode(Triangles source) public TrianglesDrawNode(Triangles source)
: base(source) : base(source)
@ -217,7 +217,7 @@ namespace osu.Game.Graphics.Backgrounds
if (Source.AimCount > 0 && (vertexBatch == null || vertexBatch.Size != Source.AimCount)) if (Source.AimCount > 0 && (vertexBatch == null || vertexBatch.Size != Source.AimCount))
{ {
vertexBatch?.Dispose(); vertexBatch?.Dispose();
vertexBatch = new TriangleBatch<TexturedVertex2D>(Source.AimCount, 1); vertexBatch = new QuadBatch<TexturedVertex2D>(Source.AimCount, 1);
} }
shader.Bind(); shader.Bind();

View File

@ -6,7 +6,6 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Configuration; using osu.Game.Configuration;
using osuTK.Graphics;
namespace osu.Game.Graphics.Containers namespace osu.Game.Graphics.Containers
{ {
@ -36,6 +35,8 @@ namespace osu.Game.Graphics.Containers
protected Bindable<bool> ShowStoryboard { get; private set; } protected Bindable<bool> ShowStoryboard { get; private set; }
protected double DimLevel => EnableUserDim.Value ? UserDimLevel.Value : 0;
protected override Container<Drawable> Content => dimContent; protected override Container<Drawable> Content => dimContent;
private Container dimContent { get; } private Container dimContent { get; }
@ -78,8 +79,8 @@ namespace osu.Game.Graphics.Containers
{ {
ContentDisplayed = ShowDimContent; ContentDisplayed = ShowDimContent;
dimContent.FadeTo((ContentDisplayed) ? 1 : 0, BACKGROUND_FADE_DURATION, Easing.OutQuint); dimContent.FadeTo(ContentDisplayed ? 1 : 0, BACKGROUND_FADE_DURATION, Easing.OutQuint);
dimContent.FadeColour(EnableUserDim.Value ? OsuColour.Gray(1 - (float)UserDimLevel.Value) : Color4.White, BACKGROUND_FADE_DURATION, Easing.OutQuint); dimContent.FadeColour(OsuColour.Gray(1 - (float)DimLevel), BACKGROUND_FADE_DURATION, Easing.OutQuint);
} }
} }
} }

View File

@ -22,8 +22,8 @@ namespace osu.Game.Graphics.UserInterface
Child = button = new TwoLayerButton Child = button = new TwoLayerButton
{ {
Anchor = Anchor.CentreLeft, Anchor = Anchor.TopLeft,
Origin = Anchor.CentreLeft, Origin = Anchor.TopLeft,
Text = @"back", Text = @"back",
Icon = OsuIcon.LeftCircle, Icon = OsuIcon.LeftCircle,
Action = () => Action?.Invoke() Action = () => Action?.Invoke()

View File

@ -110,7 +110,7 @@ namespace osu.Game.Graphics.UserInterface
[Flags] [Flags]
public enum BarDirection public enum BarDirection
{ {
LeftToRight = 1 << 0, LeftToRight = 1,
RightToLeft = 1 << 1, RightToLeft = 1 << 1,
TopToBottom = 1 << 2, TopToBottom = 1 << 2,
BottomToTop = 1 << 3, BottomToTop = 1 << 3,

View File

@ -93,7 +93,7 @@ namespace osu.Game.Graphics.UserInterface
return base.Invalidate(invalidation, source, shallPropagate); return base.Invalidate(invalidation, source, shallPropagate);
} }
private Cached pathCached = new Cached(); private readonly Cached pathCached = new Cached();
protected override void Update() protected override void Update()
{ {

View File

@ -15,7 +15,7 @@ namespace osu.Game.Graphics.UserInterface
{ {
public const float ICON_WIDTH = ICON_SIZE + icon_spacing; public const float ICON_WIDTH = ICON_SIZE + icon_spacing;
protected const float ICON_SIZE = 25; public const float ICON_SIZE = 25;
private SpriteIcon iconSprite; private SpriteIcon iconSprite;
private readonly OsuSpriteText titleText, pageText; private readonly OsuSpriteText titleText, pageText;

View File

@ -0,0 +1,64 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osuTK;
namespace osu.Game.Graphics.UserInterface
{
/// <summary>
/// A custom icon class for use with <see cref="ScreenTitle.CreateIcon()"/> based off a texture resource.
/// </summary>
public class ScreenTitleTextureIcon : CompositeDrawable
{
private const float circle_allowance = 0.8f;
private readonly string textureName;
public ScreenTitleTextureIcon(string textureName)
{
this.textureName = textureName;
}
[BackgroundDependencyLoader]
private void load(TextureStore textures, OsuColour colours)
{
Size = new Vector2(ScreenTitle.ICON_SIZE / circle_allowance);
InternalChildren = new Drawable[]
{
new CircularContainer
{
Masking = true,
BorderColour = colours.Violet,
BorderThickness = 3,
MaskingSmoothness = 1,
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Sprite
{
RelativeSizeAxes = Axes.Both,
Texture = textures.Get(textureName),
Size = new Vector2(circle_allowance),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colours.Violet,
Alpha = 0,
AlwaysPresent = true,
},
}
},
};
}
}
}

View File

@ -15,6 +15,7 @@ using System;
using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Screens.Select;
namespace osu.Game.Graphics.UserInterface namespace osu.Game.Graphics.UserInterface
{ {
@ -28,7 +29,9 @@ namespace osu.Game.Graphics.UserInterface
private const int transform_time = 600; private const int transform_time = 600;
private const int pulse_length = 250; private const int pulse_length = 250;
private const float shear = 0.1f; private const float shear_width = 5f;
private static readonly Vector2 shear = new Vector2(shear_width / Footer.HEIGHT, 0);
public static readonly Vector2 SIZE_EXTENDED = new Vector2(140, 50); public static readonly Vector2 SIZE_EXTENDED = new Vector2(140, 50);
public static readonly Vector2 SIZE_RETRACTED = new Vector2(100, 50); public static readonly Vector2 SIZE_RETRACTED = new Vector2(100, 50);
@ -56,7 +59,7 @@ namespace osu.Game.Graphics.UserInterface
c1.Origin = c1.Anchor = value.HasFlag(Anchor.x2) ? Anchor.TopLeft : Anchor.TopRight; c1.Origin = c1.Anchor = value.HasFlag(Anchor.x2) ? Anchor.TopLeft : Anchor.TopRight;
c2.Origin = c2.Anchor = value.HasFlag(Anchor.x2) ? Anchor.TopRight : Anchor.TopLeft; c2.Origin = c2.Anchor = value.HasFlag(Anchor.x2) ? Anchor.TopRight : Anchor.TopLeft;
X = value.HasFlag(Anchor.x2) ? SIZE_RETRACTED.X * shear * 0.5f : 0; X = value.HasFlag(Anchor.x2) ? SIZE_RETRACTED.X * shear.X * 0.5f : 0;
Remove(c1); Remove(c1);
Remove(c2); Remove(c2);
@ -70,6 +73,7 @@ namespace osu.Game.Graphics.UserInterface
public TwoLayerButton() public TwoLayerButton()
{ {
Size = SIZE_RETRACTED; Size = SIZE_RETRACTED;
Shear = shear;
Children = new Drawable[] Children = new Drawable[]
{ {
@ -82,7 +86,6 @@ namespace osu.Game.Graphics.UserInterface
new Container new Container
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Shear = new Vector2(shear, 0),
Masking = true, Masking = true,
MaskingSmoothness = 2, MaskingSmoothness = 2,
EdgeEffect = new EdgeEffectParameters EdgeEffect = new EdgeEffectParameters
@ -105,6 +108,7 @@ namespace osu.Game.Graphics.UserInterface
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Shear = -shear,
}, },
} }
}, },
@ -119,7 +123,6 @@ namespace osu.Game.Graphics.UserInterface
new Container new Container
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Shear = new Vector2(shear, 0),
Masking = true, Masking = true,
MaskingSmoothness = 2, MaskingSmoothness = 2,
EdgeEffect = new EdgeEffectParameters EdgeEffect = new EdgeEffectParameters
@ -144,6 +147,7 @@ namespace osu.Game.Graphics.UserInterface
{ {
Origin = Anchor.Centre, Origin = Anchor.Centre,
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Shear = -shear,
} }
} }
}, },
@ -188,7 +192,6 @@ namespace osu.Game.Graphics.UserInterface
var flash = new Box var flash = new Box
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Shear = new Vector2(shear, 0),
Colour = Color4.White.Opacity(0.5f), Colour = Color4.White.Opacity(0.5f),
}; };
Add(flash); Add(flash);

View File

@ -2,18 +2,21 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Game.Users; using osu.Game.Users;
using osu.Game.Rulesets;
namespace osu.Game.Online.API.Requests namespace osu.Game.Online.API.Requests
{ {
public class GetUserRequest : APIRequest<User> public class GetUserRequest : APIRequest<User>
{ {
private readonly long? userId; private readonly long? userId;
private readonly RulesetInfo ruleset;
public GetUserRequest(long? userId = null) public GetUserRequest(long? userId = null, RulesetInfo ruleset = null)
{ {
this.userId = userId; this.userId = userId;
this.ruleset = ruleset;
} }
protected override string Target => userId.HasValue ? $@"users/{userId}" : @"me"; protected override string Target => userId.HasValue ? $@"users/{userId}/{ruleset?.ShortName}" : $@"me/{ruleset?.ShortName}";
} }
} }

View File

@ -2,7 +2,9 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.IO.Network;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
using osu.Game.Rulesets;
namespace osu.Game.Online.API.Requests namespace osu.Game.Online.API.Requests
{ {
@ -10,12 +12,24 @@ namespace osu.Game.Online.API.Requests
{ {
private readonly long userId; private readonly long userId;
private readonly ScoreType type; private readonly ScoreType type;
private readonly RulesetInfo ruleset;
public GetUserScoresRequest(long userId, ScoreType type, int page = 0, int itemsPerPage = 5) public GetUserScoresRequest(long userId, ScoreType type, int page = 0, int itemsPerPage = 5, RulesetInfo ruleset = null)
: base(page, itemsPerPage) : base(page, itemsPerPage)
{ {
this.userId = userId; this.userId = userId;
this.type = type; this.type = type;
this.ruleset = ruleset;
}
protected override WebRequest CreateWebRequest()
{
var req = base.CreateWebRequest();
if (ruleset != null)
req.AddParameter("mode", ruleset.ShortName);
return req;
} }
protected override string Target => $@"users/{userId}/scores/{type.ToString().ToLowerInvariant()}"; protected override string Target => $@"users/{userId}/scores/{type.ToString().ToLowerInvariant()}";

View File

@ -213,8 +213,27 @@ namespace osu.Game.Online.Chat
PostMessage(content, true); PostMessage(content, true);
break; break;
case "join":
if (string.IsNullOrWhiteSpace(content))
{
target.AddNewMessages(new ErrorMessage("Usage: /join [channel]"));
break;
}
var channel = availableChannels.Where(c => c.Name == content || c.Name == $"#{content}").FirstOrDefault();
if (channel == null)
{
target.AddNewMessages(new ErrorMessage($"Channel '{content}' not found."));
break;
}
JoinChannel(channel);
CurrentChannel.Value = channel;
break;
case "help": case "help":
target.AddNewMessages(new InfoMessage("Supported commands: /help, /me [action]")); target.AddNewMessages(new InfoMessage("Supported commands: /help, /me [action], /join [channel]"));
break; break;
default: default:

View File

@ -8,7 +8,7 @@ namespace osu.Game.Online.Chat
public ErrorMessage(string message) public ErrorMessage(string message)
: base(message) : base(message)
{ {
Sender.Colour = @"ff0000"; // todo: this should likely be styled differently in the future.
} }
} }
} }

View File

@ -132,7 +132,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
Scores = null; Scores = null;
if (beatmap?.OnlineBeatmapID.HasValue != true) if (beatmap?.OnlineBeatmapID.HasValue != true || beatmap.Status <= BeatmapSetOnlineStatus.Pending)
return; return;
loadingAnimation.Show(); loadingAnimation.Show();

View File

@ -7,13 +7,11 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
using osuTK;
namespace osu.Game.Overlays.Changelog namespace osu.Game.Overlays.Changelog
{ {
@ -123,48 +121,7 @@ namespace osu.Game.Overlays.Changelog
AccentColour = colours.Violet; AccentColour = colours.Violet;
} }
protected override Drawable CreateIcon() => new ChangelogIcon(); protected override Drawable CreateIcon() => new ScreenTitleTextureIcon(@"Icons/changelog");
internal class ChangelogIcon : CompositeDrawable
{
private const float circle_allowance = 0.8f;
[BackgroundDependencyLoader]
private void load(TextureStore textures, OsuColour colours)
{
Size = new Vector2(ICON_SIZE / circle_allowance);
InternalChildren = new Drawable[]
{
new CircularContainer
{
Masking = true,
BorderColour = colours.Violet,
BorderThickness = 3,
MaskingSmoothness = 1,
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Sprite
{
RelativeSizeAxes = Axes.Both,
Texture = textures.Get(@"Icons/changelog"),
Size = new Vector2(circle_allowance),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colours.Violet,
Alpha = 0,
AlwaysPresent = true,
},
}
},
};
}
}
} }
} }
} }

View File

@ -256,6 +256,9 @@ namespace osu.Game.Overlays
loadedChannels.Add(loaded); loadedChannels.Add(loaded);
LoadComponentAsync(loaded, l => LoadComponentAsync(loaded, l =>
{ {
if (currentChannel.Value != e.NewValue)
return;
loading.Hide(); loading.Hide();
currentChannelContainer.Clear(false); currentChannelContainer.Clear(false);
@ -381,7 +384,18 @@ namespace osu.Game.Overlays
foreach (Channel channel in channels) foreach (Channel channel in channels)
{ {
ChannelTabControl.RemoveChannel(channel); ChannelTabControl.RemoveChannel(channel);
loadedChannels.Remove(loadedChannels.Find(c => c.Channel == channel));
var loaded = loadedChannels.Find(c => c.Channel == channel);
if (loaded != null)
{
loadedChannels.Remove(loaded);
// Because the container is only cleared in the async load callback of a new channel, it is forcefully cleared
// to ensure that the previous channel doesn't get updated after it's disposed
currentChannelContainer.Remove(loaded);
loaded.Dispose();
}
} }
} }

View File

@ -96,7 +96,7 @@ namespace osu.Game.Overlays.Mods
} }
} }
foregroundIcon.Highlighted = Selected; foregroundIcon.Highlighted.Value = Selected;
SelectionChanged?.Invoke(SelectedMod); SelectionChanged?.Invoke(SelectedMod);
return true; return true;

View File

@ -31,7 +31,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
private KeyValuePair<int, int>[] ranks; private KeyValuePair<int, int>[] ranks;
private int dayIndex; private int dayIndex;
public Bindable<User> User = new Bindable<User>(); public readonly Bindable<UserStatistics> Statistics = new Bindable<UserStatistics>();
public RankGraph() public RankGraph()
{ {
@ -56,8 +56,6 @@ namespace osu.Game.Overlays.Profile.Header.Components
}; };
graph.OnBallMove += i => dayIndex = i; graph.OnBallMove += i => dayIndex = i;
User.ValueChanged += userChanged;
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -66,18 +64,25 @@ namespace osu.Game.Overlays.Profile.Header.Components
graph.LineColour = colours.Yellow; graph.LineColour = colours.Yellow;
} }
private void userChanged(ValueChangedEvent<User> e) protected override void LoadComplete()
{
base.LoadComplete();
Statistics.BindValueChanged(statistics => updateStatistics(statistics.NewValue), true);
}
private void updateStatistics(UserStatistics statistics)
{ {
placeholder.FadeIn(fade_duration, Easing.Out); placeholder.FadeIn(fade_duration, Easing.Out);
if (e.NewValue?.Statistics?.Ranks.Global == null) if (statistics?.Ranks.Global == null)
{ {
graph.FadeOut(fade_duration, Easing.Out); graph.FadeOut(fade_duration, Easing.Out);
ranks = null; ranks = null;
return; return;
} }
int[] userRanks = e.NewValue.RankHistory?.Data ?? new[] { e.NewValue.Statistics.Ranks.Global.Value }; int[] userRanks = statistics.RankHistory?.Data ?? new[] { statistics.Ranks.Global.Value };
ranks = userRanks.Select((x, index) => new KeyValuePair<int, int>(index, x)).Where(x => x.Value != 0).ToArray(); ranks = userRanks.Select((x, index) => new KeyValuePair<int, int>(index, x)).Where(x => x.Value != 0).ToArray();
if (ranks.Length > 1) if (ranks.Length > 1)
@ -191,7 +196,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
} }
} }
public string TooltipText => User.Value?.Statistics?.Ranks.Global == null ? "" : $"#{ranks[dayIndex].Value:#,##0}|{ranked_days - ranks[dayIndex].Key + 1}"; public string TooltipText => Statistics.Value?.Ranks.Global == null ? "" : $"#{ranks[dayIndex].Value:#,##0}|{ranked_days - ranks[dayIndex].Key + 1}";
public ITooltip GetCustomTooltip() => new RankGraphTooltip(); public ITooltip GetCustomTooltip() => new RankGraphTooltip();

View File

@ -179,7 +179,7 @@ namespace osu.Game.Overlays.Profile.Header
detailGlobalRank.Content = user?.Statistics?.Ranks.Global?.ToString("\\##,##0") ?? "-"; detailGlobalRank.Content = user?.Statistics?.Ranks.Global?.ToString("\\##,##0") ?? "-";
detailCountryRank.Content = user?.Statistics?.Ranks.Country?.ToString("\\##,##0") ?? "-"; detailCountryRank.Content = user?.Statistics?.Ranks.Country?.ToString("\\##,##0") ?? "-";
rankGraph.User.Value = user; rankGraph.Statistics.Value = user?.Statistics;
} }
private class ScoreRankInfo : CompositeDrawable private class ScoreRankInfo : CompositeDrawable

View File

@ -193,6 +193,12 @@ namespace osu.Game.Rulesets.Mods
shader.Unbind(); shader.Unbind();
} }
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
quadBatch?.Dispose();
}
} }
} }
} }

View File

@ -10,7 +10,6 @@ using osu.Game.Beatmaps.Formats;
using osu.Game.Audio; using osu.Game.Audio;
using System.Linq; using System.Linq;
using JetBrains.Annotations; using JetBrains.Annotations;
using osu.Framework.Logging;
using osu.Framework.MathUtils; using osu.Framework.MathUtils;
namespace osu.Game.Rulesets.Objects.Legacy namespace osu.Game.Rulesets.Objects.Legacy
@ -40,8 +39,6 @@ namespace osu.Game.Rulesets.Objects.Legacy
[CanBeNull] [CanBeNull]
public override HitObject Parse(string text) public override HitObject Parse(string text)
{
try
{ {
string[] split = text.Split(','); string[] split = text.Split(',');
@ -219,10 +216,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
} }
if (result == null) if (result == null)
{ throw new InvalidDataException($"Unknown hit object type: {split[3]}");
Logger.Log($"Unknown hit object type: {type}. Skipped.", level: LogLevel.Error);
return null;
}
result.StartTime = startTime; result.StartTime = startTime;
@ -233,17 +227,6 @@ namespace osu.Game.Rulesets.Objects.Legacy
return result; return result;
} }
catch (FormatException)
{
Logger.Log("A hitobject could not be parsed correctly and will be ignored", LoggingTarget.Runtime, LogLevel.Important);
}
catch (OverflowException)
{
Logger.Log("A hitobject could not be parsed correctly and will be ignored", LoggingTarget.Runtime, LogLevel.Important);
}
return null;
}
private void readCustomSampleBanks(string str, SampleBankInfo bankInfo) private void readCustomSampleBanks(string str, SampleBankInfo bankInfo)
{ {

View File

@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
[Flags] [Flags]
internal enum ConvertHitObjectType internal enum ConvertHitObjectType
{ {
Circle = 1 << 0, Circle = 1,
Slider = 1 << 1, Slider = 1 << 1,
NewCombo = 1 << 2, NewCombo = 1 << 2,
Spinner = 1 << 3, Spinner = 1 << 3,

View File

@ -313,6 +313,9 @@ namespace osu.Game.Rulesets.Scoring
/// <summary> /// <summary>
/// Applies the score change of a <see cref="JudgementResult"/> to this <see cref="ScoreProcessor"/>. /// Applies the score change of a <see cref="JudgementResult"/> to this <see cref="ScoreProcessor"/>.
/// </summary> /// </summary>
/// <remarks>
/// Any changes applied via this method can be reverted via <see cref="RevertResult"/>.
/// </remarks>
/// <param name="result">The <see cref="JudgementResult"/> to apply.</param> /// <param name="result">The <see cref="JudgementResult"/> to apply.</param>
protected virtual void ApplyResult(JudgementResult result) protected virtual void ApplyResult(JudgementResult result)
{ {
@ -357,7 +360,7 @@ namespace osu.Game.Rulesets.Scoring
} }
/// <summary> /// <summary>
/// Reverts the score change of a <see cref="JudgementResult"/> that was applied to this <see cref="ScoreProcessor"/>. /// Reverts the score change of a <see cref="JudgementResult"/> that was applied to this <see cref="ScoreProcessor"/> via <see cref="ApplyResult"/>.
/// </summary> /// </summary>
/// <param name="result">The judgement scoring result.</param> /// <param name="result">The judgement scoring result.</param>
protected virtual void RevertResult(JudgementResult result) protected virtual void RevertResult(JudgementResult result)

View File

@ -11,11 +11,14 @@ using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osuTK; using osuTK;
using osu.Framework.Bindables;
namespace osu.Game.Rulesets.UI namespace osu.Game.Rulesets.UI
{ {
public class ModIcon : Container, IHasTooltip public class ModIcon : Container, IHasTooltip
{ {
public readonly BindableBool Highlighted = new BindableBool();
private readonly SpriteIcon modIcon; private readonly SpriteIcon modIcon;
private readonly SpriteIcon background; private readonly SpriteIcon background;
@ -97,26 +100,12 @@ namespace osu.Game.Rulesets.UI
highlightedColour = colours.PinkLight; highlightedColour = colours.PinkLight;
break; break;
} }
applyStyle();
} }
private bool highlighted; protected override void LoadComplete()
public bool Highlighted
{ {
get => highlighted; base.LoadComplete();
Highlighted.BindValueChanged(highlighted => background.Colour = highlighted.NewValue ? highlightedColour : backgroundColour, true);
set
{
highlighted = value;
applyStyle();
}
}
private void applyStyle()
{
background.Colour = highlighted ? highlightedColour : backgroundColour;
} }
} }
} }

View File

@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.UI.Scrolling
[Resolved] [Resolved]
private IScrollingInfo scrollingInfo { get; set; } private IScrollingInfo scrollingInfo { get; set; }
private Cached initialStateCache = new Cached(); private readonly Cached initialStateCache = new Cached();
public ScrollingHitObjectContainer() public ScrollingHitObjectContainer()
{ {

View File

@ -1,9 +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.Screens.Direct
{
public class OnlineListing : ScreenWhiteBox
{
}
}

View File

@ -14,7 +14,6 @@ using osu.Game.Graphics.Containers;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Backgrounds;
using osu.Game.Screens.Charts; using osu.Game.Screens.Charts;
using osu.Game.Screens.Direct;
using osu.Game.Screens.Edit; using osu.Game.Screens.Edit;
using osu.Game.Screens.Multi; using osu.Game.Screens.Multi;
using osu.Game.Screens.Select; using osu.Game.Screens.Select;
@ -65,7 +64,6 @@ namespace osu.Game.Screens.Menu
buttons = new ButtonSystem buttons = new ButtonSystem
{ {
OnChart = delegate { this.Push(new ChartListing()); }, OnChart = delegate { this.Push(new ChartListing()); },
OnDirect = delegate { this.Push(new OnlineListing()); },
OnEdit = delegate { this.Push(new Editor()); }, OnEdit = delegate { this.Push(new Editor()); },
OnSolo = onSolo, OnSolo = onSolo,
OnMulti = delegate { this.Push(new Multiplayer()); }, OnMulti = delegate { this.Push(new Multiplayer()); },

View File

@ -3,6 +3,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
@ -15,7 +16,11 @@ namespace osu.Game.Screens.Play
{ {
public class BreakOverlay : Container public class BreakOverlay : Container
{ {
private const double fade_duration = BreakPeriod.MIN_BREAK_DURATION / 2; /// <summary>
/// The duration of the break overlay fading.
/// </summary>
public const double BREAK_FADE_DURATION = BreakPeriod.MIN_BREAK_DURATION / 2;
private const float remaining_time_container_max_size = 0.3f; private const float remaining_time_container_max_size = 0.3f;
private const int vertical_margin = 25; private const int vertical_margin = 25;
@ -29,12 +34,27 @@ namespace osu.Game.Screens.Play
set set
{ {
breaks = value; breaks = value;
// reset index in case the new breaks list is smaller than last one
isBreakTime.Value = false;
CurrentBreakIndex = 0;
if (IsLoaded)
initializeBreaks(); initializeBreaks();
} }
} }
public override bool RemoveCompletedTransforms => false; public override bool RemoveCompletedTransforms => false;
/// <summary>
/// Whether the gameplay is currently in a break.
/// </summary>
public IBindable<bool> IsBreakTime => isBreakTime;
protected int CurrentBreakIndex;
private readonly BindableBool isBreakTime = new BindableBool();
private readonly Container remainingTimeAdjustmentBox; private readonly Container remainingTimeAdjustmentBox;
private readonly Container remainingTimeBox; private readonly Container remainingTimeBox;
private readonly RemainingTimeCounter remainingTimeCounter; private readonly RemainingTimeCounter remainingTimeCounter;
@ -109,10 +129,36 @@ namespace osu.Game.Screens.Play
initializeBreaks(); initializeBreaks();
} }
protected override void Update()
{
base.Update();
updateBreakTimeBindable();
}
private void updateBreakTimeBindable()
{
if (breaks == null || breaks.Count == 0)
return;
var time = Clock.CurrentTime;
if (time > breaks[CurrentBreakIndex].EndTime)
{
while (time > breaks[CurrentBreakIndex].EndTime && CurrentBreakIndex < breaks.Count - 1)
CurrentBreakIndex++;
}
else
{
while (time < breaks[CurrentBreakIndex].StartTime && CurrentBreakIndex > 0)
CurrentBreakIndex--;
}
var currentBreak = breaks[CurrentBreakIndex];
isBreakTime.Value = currentBreak.HasEffect && currentBreak.Contains(time);
}
private void initializeBreaks() private void initializeBreaks()
{ {
if (!IsLoaded) return; // we need a clock.
FinishTransforms(true); FinishTransforms(true);
Scheduler.CancelDelayedTasks(); Scheduler.CancelDelayedTasks();
@ -125,25 +171,25 @@ namespace osu.Game.Screens.Play
using (BeginAbsoluteSequence(b.StartTime, true)) using (BeginAbsoluteSequence(b.StartTime, true))
{ {
fadeContainer.FadeIn(fade_duration); fadeContainer.FadeIn(BREAK_FADE_DURATION);
breakArrows.Show(fade_duration); breakArrows.Show(BREAK_FADE_DURATION);
remainingTimeAdjustmentBox remainingTimeAdjustmentBox
.ResizeWidthTo(remaining_time_container_max_size, fade_duration, Easing.OutQuint) .ResizeWidthTo(remaining_time_container_max_size, BREAK_FADE_DURATION, Easing.OutQuint)
.Delay(b.Duration - fade_duration) .Delay(b.Duration - BREAK_FADE_DURATION)
.ResizeWidthTo(0); .ResizeWidthTo(0);
remainingTimeBox remainingTimeBox
.ResizeWidthTo(0, b.Duration - fade_duration) .ResizeWidthTo(0, b.Duration - BREAK_FADE_DURATION)
.Then() .Then()
.ResizeWidthTo(1); .ResizeWidthTo(1);
remainingTimeCounter.CountTo(b.Duration).CountTo(0, b.Duration); remainingTimeCounter.CountTo(b.Duration).CountTo(0, b.Duration);
using (BeginDelayedSequence(b.Duration - fade_duration, true)) using (BeginDelayedSequence(b.Duration - BREAK_FADE_DURATION, true))
{ {
fadeContainer.FadeOut(fade_duration); fadeContainer.FadeOut(BREAK_FADE_DURATION);
breakArrows.Hide(fade_duration); breakArrows.Hide(BREAK_FADE_DURATION);
} }
} }
} }

View File

@ -33,7 +33,7 @@ namespace osu.Game.Screens.Play
base.LoadComplete(); base.LoadComplete();
} }
protected override bool ShowDimContent => ShowStoryboard.Value && UserDimLevel.Value < 1; protected override bool ShowDimContent => ShowStoryboard.Value && DimLevel < 1;
private void initializeStoryboard(bool async) private void initializeStoryboard(bool async)
{ {

View File

@ -66,6 +66,8 @@ namespace osu.Game.Screens.Play
private SampleChannel sampleRestart; private SampleChannel sampleRestart;
private BreakOverlay breakOverlay;
protected ScoreProcessor ScoreProcessor { get; private set; } protected ScoreProcessor ScoreProcessor { get; private set; }
protected DrawableRuleset DrawableRuleset { get; private set; } protected DrawableRuleset DrawableRuleset { get; private set; }
@ -134,7 +136,7 @@ namespace osu.Game.Screens.Play
} }
} }
}, },
new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor) breakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor)
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
@ -255,7 +257,7 @@ namespace osu.Game.Screens.Play
private void performImmediateExit() private void performImmediateExit()
{ {
// if a restart has been requested, cancel any pending completion (user has shown intent to restart). // if a restart has been requested, cancel any pending completion (user has shown intent to restart).
onCompletionEvent = null; completionProgressDelegate?.Cancel();
ValidForResume = false; ValidForResume = false;
@ -275,20 +277,16 @@ namespace osu.Game.Screens.Play
sampleRestart?.Play(); sampleRestart?.Play();
// if a restart has been requested, cancel any pending completion (user has shown intent to restart).
onCompletionEvent = null;
ValidForResume = false;
RestartRequested?.Invoke(); RestartRequested?.Invoke();
this.Exit(); performImmediateExit();
} }
private ScheduledDelegate onCompletionEvent; private ScheduledDelegate completionProgressDelegate;
private void onCompletion() private void onCompletion()
{ {
// Only show the completion screen if the player hasn't failed // Only show the completion screen if the player hasn't failed
if (ScoreProcessor.HasFailed || onCompletionEvent != null) if (ScoreProcessor.HasFailed || completionProgressDelegate != null)
return; return;
ValidForResume = false; ValidForResume = false;
@ -297,7 +295,7 @@ namespace osu.Game.Screens.Play
using (BeginDelayedSequence(1000)) using (BeginDelayedSequence(1000))
{ {
onCompletionEvent = Schedule(delegate completionProgressDelegate = Schedule(delegate
{ {
if (!this.IsCurrentScreen()) return; if (!this.IsCurrentScreen()) return;
@ -306,8 +304,6 @@ namespace osu.Game.Screens.Play
scoreManager.Import(score).Wait(); scoreManager.Import(score).Wait();
this.Push(CreateResults(score)); this.Push(CreateResults(score));
onCompletionEvent = null;
}); });
} }
} }
@ -417,8 +413,7 @@ namespace osu.Game.Screens.Play
PauseOverlay.Hide(); PauseOverlay.Hide();
// breaks and time-based conditions may allow instant resume. // breaks and time-based conditions may allow instant resume.
double time = GameplayClockContainer.GameplayClock.CurrentTime; if (breakOverlay.IsBreakTime.Value || GameplayClockContainer.GameplayClock.CurrentTime < Beatmap.Value.Beatmap.HitObjects.First().StartTime)
if (Beatmap.Value.Beatmap.Breaks.Any(b => b.Contains(time)) || time < Beatmap.Value.Beatmap.HitObjects.First().StartTime)
completeResume(); completeResume();
else else
DrawableRuleset.RequestResume(completeResume); DrawableRuleset.RequestResume(completeResume);
@ -471,10 +466,10 @@ namespace osu.Game.Screens.Play
public override bool OnExiting(IScreen next) public override bool OnExiting(IScreen next)
{ {
if (onCompletionEvent != null) if (completionProgressDelegate != null && !completionProgressDelegate.Cancelled && !completionProgressDelegate.Completed)
{ {
// Proceed to result screen if beatmap already finished playing // proceed to result screen if beatmap already finished playing
onCompletionEvent.RunTask(); completionProgressDelegate.RunTask();
return true; return true;
} }

View File

@ -75,7 +75,7 @@ namespace osu.Game.Screens.Play
return base.Invalidate(invalidation, source, shallPropagate); return base.Invalidate(invalidation, source, shallPropagate);
} }
private Cached layout = new Cached(); private readonly Cached layout = new Cached();
private ScheduledDelegate scheduledCreate; private ScheduledDelegate scheduledCreate;
protected override void Update() protected override void Update()

View File

@ -93,8 +93,8 @@ namespace osu.Game.Screens.Select
} }
private readonly List<float> yPositions = new List<float>(); private readonly List<float> yPositions = new List<float>();
private Cached itemsCache = new Cached(); private readonly Cached itemsCache = new Cached();
private Cached scrollPositionCache = new Cached(); private readonly Cached scrollPositionCache = new Cached();
private readonly Container<DrawableCarouselItem> scrollableContent; private readonly Container<DrawableCarouselItem> scrollableContent;

View File

@ -94,7 +94,7 @@ namespace osu.Game.Screens.Select
buttons = new FillFlowContainer<FooterButton> buttons = new FillFlowContainer<FooterButton>
{ {
Direction = FillDirection.Horizontal, Direction = FillDirection.Horizontal,
Spacing = new Vector2(0.2f, 0), Spacing = new Vector2(-FooterButton.SHEAR_WIDTH, 0),
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
} }
} }

View File

@ -17,7 +17,9 @@ namespace osu.Game.Screens.Select
{ {
public class FooterButton : OsuClickableContainer public class FooterButton : OsuClickableContainer
{ {
private static readonly Vector2 shearing = new Vector2(0.15f, 0); public static readonly float SHEAR_WIDTH = 7.5f;
protected static readonly Vector2 SHEAR = new Vector2(SHEAR_WIDTH / Footer.HEIGHT, 0);
public string Text public string Text
{ {
@ -59,37 +61,35 @@ namespace osu.Game.Screens.Select
private readonly Box box; private readonly Box box;
private readonly Box light; private readonly Box light;
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => box.ReceivePositionalInputAt(screenSpacePos);
public FooterButton() public FooterButton()
{ {
AutoSizeAxes = Axes.Both; AutoSizeAxes = Axes.Both;
Shear = SHEAR;
Children = new Drawable[] Children = new Drawable[]
{ {
TextContainer = new Container
{
Size = new Vector2(100, 50),
Child = SpriteText = new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
}
},
box = new Box box = new Box
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Shear = shearing,
EdgeSmoothness = new Vector2(2, 0), EdgeSmoothness = new Vector2(2, 0),
Colour = Color4.White, Colour = Color4.White,
Alpha = 0, Alpha = 0,
}, },
light = new Box light = new Box
{ {
Shear = shearing,
Height = 4, Height = 4,
EdgeSmoothness = new Vector2(2, 0), EdgeSmoothness = new Vector2(2, 0),
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
}, },
TextContainer = new Container
{
Size = new Vector2(100 - SHEAR_WIDTH, 50),
Shear = -SHEAR,
Child = SpriteText = new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
}
},
}; };
} }

View File

@ -32,6 +32,7 @@ namespace osu.Game.Screens.Select
{ {
Anchor = Anchor.CentreLeft, Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft, Origin = Anchor.CentreLeft,
Shear = -SHEAR,
Child = modDisplay = new FooterModDisplay Child = modDisplay = new FooterModDisplay
{ {
DisplayUnrankedText = false, DisplayUnrankedText = false,

View File

@ -20,6 +20,7 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.UI;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
@ -360,6 +361,10 @@ namespace osu.Game.Skinning
} }
} }
/// <summary>
/// A sprite which is displayed within the playfield, but historically was not considered part of the playfield.
/// Performs scale adjustment to undo the scale applied by <see cref="PlayfieldAdjustmentContainer"/> (osu! ruleset specifically).
/// </summary>
private class NonPlayfieldSprite : Sprite private class NonPlayfieldSprite : Sprite
{ {
public override Texture Texture public override Texture Texture
@ -368,7 +373,8 @@ namespace osu.Game.Skinning
set set
{ {
if (value != null) if (value != null)
value.ScaleAdjust *= 2f; // stable "magic ratio". see OsuPlayfieldAdjustmentContainer for full explanation.
value.ScaleAdjust *= 1.6f;
base.Texture = value; base.Texture = value;
} }
} }

View File

@ -15,10 +15,10 @@ namespace osu.Game.Storyboards
public IEnumerable<TypedCommand> Commands => commands.OrderBy(c => c.StartTime); public IEnumerable<TypedCommand> Commands => commands.OrderBy(c => c.StartTime);
public bool HasCommands => commands.Count > 0; public bool HasCommands => commands.Count > 0;
private Cached<double> startTimeBacking; private readonly Cached<double> startTimeBacking = new Cached<double>();
public double StartTime => startTimeBacking.IsValid ? startTimeBacking : startTimeBacking.Value = HasCommands ? commands.Min(c => c.StartTime) : double.MinValue; public double StartTime => startTimeBacking.IsValid ? startTimeBacking : startTimeBacking.Value = HasCommands ? commands.Min(c => c.StartTime) : double.MinValue;
private Cached<double> endTimeBacking; private readonly Cached<double> endTimeBacking = new Cached<double>();
public double EndTime => endTimeBacking.IsValid ? endTimeBacking : endTimeBacking.Value = HasCommands ? commands.Max(c => c.EndTime) : double.MaxValue; public double EndTime => endTimeBacking.IsValid ? endTimeBacking : endTimeBacking.Value = HasCommands ? commands.Max(c => c.EndTime) : double.MaxValue;
public T StartValue => HasCommands ? commands.OrderBy(c => c.StartTime).First().StartValue : default; public T StartValue => HasCommands ? commands.OrderBy(c => c.StartTime).First().StartValue : default;

View File

@ -159,7 +159,10 @@ namespace osu.Game.Users
} }
[JsonProperty(@"rankHistory")] [JsonProperty(@"rankHistory")]
public RankHistoryData RankHistory; private RankHistoryData rankHistory
{
set => Statistics.RankHistory = value;
}
[JsonProperty("badges")] [JsonProperty("badges")]
public Badge[] Badges; public Badge[] Badges;
@ -184,6 +187,7 @@ namespace osu.Game.Users
public static readonly User SYSTEM_USER = new User public static readonly User SYSTEM_USER = new User
{ {
Username = "system", Username = "system",
Colour = @"9c0101",
Id = 0 Id = 0
}; };

View File

@ -4,6 +4,7 @@
using System; using System;
using Newtonsoft.Json; using Newtonsoft.Json;
using osu.Game.Scoring; using osu.Game.Scoring;
using static osu.Game.Users.User;
namespace osu.Game.Users namespace osu.Game.Users
{ {
@ -113,5 +114,7 @@ namespace osu.Game.Users
[JsonProperty(@"country")] [JsonProperty(@"country")]
public int? Country; public int? Country;
} }
public RankHistoryData RankHistory;
} }
} }

View File

@ -14,8 +14,8 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.702.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2019.809.0" />
<PackageReference Include="ppy.osu.Framework" Version="2019.730.0" /> <PackageReference Include="ppy.osu.Framework" Version="2019.809.0" />
<PackageReference Include="SharpCompress" Version="0.23.0" /> <PackageReference Include="SharpCompress" Version="0.23.0" />
<PackageReference Include="NUnit" Version="3.12.0" /> <PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="SharpRaven" Version="2.4.0" /> <PackageReference Include="SharpRaven" Version="2.4.0" />

View File

@ -104,9 +104,9 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.1" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.702.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2019.809.0" />
<PackageReference Include="ppy.osu.Framework" Version="2019.730.0" /> <PackageReference Include="ppy.osu.Framework" Version="2019.809.0" />
<PackageReference Include="ppy.osu.Framework.iOS" Version="2019.730.0" /> <PackageReference Include="ppy.osu.Framework.iOS" Version="2019.809.0" />
<PackageReference Include="SharpCompress" Version="0.22.0" /> <PackageReference Include="SharpCompress" Version="0.22.0" />
<PackageReference Include="NUnit" Version="3.11.0" /> <PackageReference Include="NUnit" Version="3.11.0" />
<PackageReference Include="SharpRaven" Version="2.4.0" /> <PackageReference Include="SharpRaven" Version="2.4.0" />