1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-28 02:02:53 +08:00

Merge branch 'master' into disallow-beatmap-change

This commit is contained in:
Dean Herbert 2018-06-04 10:53:04 +09:00 committed by GitHub
commit 4b604bf055
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
65 changed files with 1289 additions and 498 deletions

12
.vscode/launch.json vendored
View File

@ -22,7 +22,7 @@
}, },
"type": "mono", "type": "mono",
"request": "launch", "request": "launch",
"program": "${workspaceRoot}/osu.Game.Tests/bin/Debug/net471/osu.Game.Tests.exe", "program": "${workspaceRoot}/osu.Game.Tests/bin/Release/net471/osu.Game.Tests.exe",
"cwd": "${workspaceRoot}", "cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Release, msbuild)", "preLaunchTask": "Build (Release, msbuild)",
"runtimeExecutable": null, "runtimeExecutable": null,
@ -66,7 +66,7 @@
"${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp2.0/osu.Game.Tests.dll" "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp2.0/osu.Game.Tests.dll"
], ],
"cwd": "${workspaceRoot}", "cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Debug, dotnet)", "preLaunchTask": "Build tests (Debug, dotnet)",
"env": {}, "env": {},
"console": "internalConsole" "console": "internalConsole"
}, },
@ -76,10 +76,10 @@
"request": "launch", "request": "launch",
"program": "dotnet", "program": "dotnet",
"args": [ "args": [
"${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp2.0/osu.Game.Tests.dll" "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp2.0/osu.Game.Tests.dll"
], ],
"cwd": "${workspaceRoot}", "cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Release, dotnet)", "preLaunchTask": "Build tests (Release, dotnet)",
"env": {}, "env": {},
"console": "internalConsole" "console": "internalConsole"
}, },
@ -92,7 +92,7 @@
"${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.0/osu!.dll", "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.0/osu!.dll",
], ],
"cwd": "${workspaceRoot}", "cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Debug, dotnet)", "preLaunchTask": "Build osu! (Debug, dotnet)",
"env": {}, "env": {},
"console": "internalConsole" "console": "internalConsole"
}, },
@ -105,7 +105,7 @@
"${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.0/osu!.dll", "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.0/osu!.dll",
], ],
"cwd": "${workspaceRoot}", "cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Release, dotnet)", "preLaunchTask": "Build osu! (Release, dotnet)",
"env": {}, "env": {},
"console": "internalConsole" "console": "internalConsole"
} }

37
.vscode/tasks.json vendored
View File

@ -31,7 +31,7 @@
"problemMatcher": "$msCompile" "problemMatcher": "$msCompile"
}, },
{ {
"label": "Build (Debug, dotnet)", "label": "Build osu! (Debug, dotnet)",
"type": "shell", "type": "shell",
"command": "dotnet", "command": "dotnet",
"args": [ "args": [
@ -47,7 +47,7 @@
"problemMatcher": "$msCompile" "problemMatcher": "$msCompile"
}, },
{ {
"label": "Build (Release, dotnet)", "label": "Build osu! (Release, dotnet)",
"type": "shell", "type": "shell",
"command": "dotnet", "command": "dotnet",
"args": [ "args": [
@ -63,6 +63,39 @@
"group": "build", "group": "build",
"problemMatcher": "$msCompile" "problemMatcher": "$msCompile"
}, },
{
"label": "Build tests (Debug, dotnet)",
"type": "shell",
"command": "dotnet",
"args": [
"build",
"--no-restore",
"osu.Game.Tests",
"/p:TargetFramework=netcoreapp2.0",
"/p:GenerateFullPaths=true",
"/m",
"/verbosity:m"
],
"group": "build",
"problemMatcher": "$msCompile"
},
{
"label": "Build tests (Release, dotnet)",
"type": "shell",
"command": "dotnet",
"args": [
"build",
"--no-restore",
"osu.Game.Tests",
"/p:TargetFramework=netcoreapp2.0",
"/p:Configuration=Release",
"/p:GenerateFullPaths=true",
"/m",
"/verbosity:m"
],
"group": "build",
"problemMatcher": "$msCompile"
},
{ {
"label": "Restore (net471)", "label": "Restore (net471)",
"type": "shell", "type": "shell",

@ -1 +1 @@
Subproject commit 84fdfc77a86d581638e69f5e8061c118de4b30f9 Subproject commit 804a4b81b89cb4569af5221e6fa2296d559c28fb

View File

@ -12,6 +12,7 @@ using osu.Framework.Platform;
using osu.Game; using osu.Game;
using OpenTK.Input; using OpenTK.Input;
using Microsoft.Win32; using Microsoft.Win32;
using osu.Framework.Platform.Windows;
namespace osu.Desktop namespace osu.Desktop
{ {
@ -40,7 +41,7 @@ namespace osu.Desktop
/// <summary> /// <summary>
/// A method of accessing an osu-stable install in a controlled fashion. /// A method of accessing an osu-stable install in a controlled fashion.
/// </summary> /// </summary>
private class StableStorage : DesktopStorage private class StableStorage : WindowsStorage
{ {
protected override string LocateBasePath() protected override string LocateBasePath()
{ {

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\osu.Game.props" /> <Import Project="..\osu.Game.props" />
<PropertyGroup Label="Project"> <PropertyGroup Label="Project">
<TargetFrameworks>net471;netcoreapp2.0</TargetFrameworks> <TargetFrameworks>net471;netcoreapp2.0</TargetFrameworks>
@ -20,6 +20,8 @@
<StartupObject>osu.Desktop.Program</StartupObject> <StartupObject>osu.Desktop.Program</StartupObject>
</PropertyGroup> </PropertyGroup>
<ItemGroup Label="Project References"> <ItemGroup Label="Project References">
<!-- This can be removed after .NET Core SDK version 2.1.300; see https://docs.microsoft.com/en-us/ef/core/miscellaneous/cli/dotnet -->
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.0.3" />
<ProjectReference Include="..\osu-framework\osu.Framework\osu.Framework.csproj" /> <ProjectReference Include="..\osu-framework\osu.Framework\osu.Framework.csproj" />
<ProjectReference Include="..\osu.Game\osu.Game.csproj" /> <ProjectReference Include="..\osu.Game\osu.Game.csproj" />
<ProjectReference Include="..\osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj" /> <ProjectReference Include="..\osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj" />
@ -30,10 +32,14 @@
<PackageReference Include="Microsoft.Win32.Registry" Version="4.4.0" /> <PackageReference Include="Microsoft.Win32.Registry" Version="4.4.0" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.0.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.0.3" />
<PackageReference Include="squirrel.windows" Version="1.8.0" Condition="'$(TargetFramework)' == 'net471'" /> <PackageReference Include="squirrel.windows" Version="1.8.0" Condition="'$(TargetFramework)' == 'net471'" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Resources"> <ItemGroup Label="Resources">
<EmbeddedResource Include="lazer.ico" /> <EmbeddedResource Include="lazer.ico" />
</ItemGroup> </ItemGroup>
</Project> <ItemGroup>
<!-- This can be removed after .NET Core SDK version 2.1.300; see https://docs.microsoft.com/en-us/ef/core/miscellaneous/cli/dotnet -->
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.0" />
</ItemGroup>
</Project>

View File

@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Catch.Tests
Child = catcherArea = new TestCatcherArea(new BeatmapDifficulty { CircleSize = size }) Child = catcherArea = new TestCatcherArea(new BeatmapDifficulty { CircleSize = size })
{ {
Anchor = Anchor.CentreLeft, Anchor = Anchor.CentreLeft,
Origin = Anchor.BottomLeft Origin = Anchor.TopLeft
}, },
}; };
} }

View File

@ -25,6 +25,7 @@ namespace osu.Game.Rulesets.Catch.Tests
typeof(DrawableCatchHitObject), typeof(DrawableCatchHitObject),
typeof(DrawableFruit), typeof(DrawableFruit),
typeof(DrawableDroplet), typeof(DrawableDroplet),
typeof(BananaShower),
typeof(Pulp), typeof(Pulp),
}; };
@ -53,12 +54,19 @@ namespace osu.Game.Rulesets.Catch.Tests
private DrawableFruit createDrawable(int index) private DrawableFruit createDrawable(int index)
{ {
var fruit = new Fruit Fruit fruit = index == 5
{ ? new BananaShower.Banana
StartTime = 1000000000000, {
IndexInBeatmap = index, StartTime = 1000000000000,
Scale = 1.5f, IndexInBeatmap = index,
}; Scale = 1.5f,
}
: new Fruit
{
StartTime = 1000000000000,
IndexInBeatmap = index,
Scale = 1.5f,
};
return new DrawableFruit(fruit) return new DrawableFruit(fruit)
{ {

View File

@ -18,12 +18,19 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
{ {
private Circle border; private Circle border;
private const float drawable_radius = (float)CatchHitObject.OBJECT_RADIUS * radius_adjust;
/// <summary>
/// Because we're adding a border around the fruit, we need to scale down some.
/// </summary>
private const float radius_adjust = 1.1f;
public DrawableFruit(Fruit h) public DrawableFruit(Fruit h)
: base(h) : base(h)
{ {
Origin = Anchor.Centre; Origin = Anchor.Centre;
Size = new Vector2((float)CatchHitObject.OBJECT_RADIUS); Size = new Vector2(drawable_radius);
Masking = false; Masking = false;
Rotation = (float)(RNG.NextDouble() - 0.5f) * 40; Rotation = (float)(RNG.NextDouble() - 0.5f) * 40;
@ -44,14 +51,14 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
{ {
Hollow = !HitObject.HyperDash, Hollow = !HitObject.HyperDash,
Type = EdgeEffectType.Glow, Type = EdgeEffectType.Glow,
Radius = 4, Radius = 4 * radius_adjust,
Colour = HitObject.HyperDash ? Color4.Red : AccentColour.Darken(1).Opacity(0.6f) Colour = HitObject.HyperDash ? Color4.Red : AccentColour.Darken(1).Opacity(0.6f)
}, },
Size = new Vector2(Height * 1.5f), Size = new Vector2(Height),
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
BorderColour = Color4.White, BorderColour = Color4.White,
BorderThickness = 4f, BorderThickness = 3f * radius_adjust,
Children = new Framework.Graphics.Drawable[] Children = new Framework.Graphics.Drawable[]
{ {
new Box new Box
@ -82,8 +89,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
private Framework.Graphics.Drawable createPulp(FruitVisualRepresentation representation) private Framework.Graphics.Drawable createPulp(FruitVisualRepresentation representation)
{ {
const float large_pulp_3 = 13f; const float large_pulp_3 = 8f * radius_adjust;
const float distance_from_centre_3 = 0.23f; const float distance_from_centre_3 = 0.15f;
const float large_pulp_4 = large_pulp_3 * 0.925f; const float large_pulp_4 = large_pulp_3 * 0.925f;
const float distance_from_centre_4 = distance_from_centre_3 / 0.925f; const float distance_from_centre_4 = distance_from_centre_3 / 0.925f;
@ -106,11 +113,9 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
{ {
new Pulp new Pulp
{ {
Anchor = Anchor.TopCentre,
Origin = Anchor.BottomCentre,
AccentColour = AccentColour, AccentColour = AccentColour,
Size = new Vector2(small_pulp), Size = new Vector2(small_pulp),
Y = 0.05f, Y = -0.34f,
}, },
new Pulp new Pulp
{ {
@ -146,11 +151,9 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
{ {
new Pulp new Pulp
{ {
Anchor = Anchor.TopCentre,
Origin = Anchor.BottomCentre,
AccentColour = AccentColour, AccentColour = AccentColour,
Size = new Vector2(small_pulp), Size = new Vector2(small_pulp),
Y = 0.1f, Y = -0.3f,
}, },
new Pulp new Pulp
{ {
@ -186,11 +189,9 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
{ {
new Pulp new Pulp
{ {
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
AccentColour = AccentColour, AccentColour = AccentColour,
Size = new Vector2(small_pulp), Size = new Vector2(small_pulp),
Y = -0.1f, Y = -0.33f,
}, },
new Pulp new Pulp
{ {
@ -220,10 +221,9 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
{ {
new Pulp new Pulp
{ {
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
AccentColour = AccentColour, AccentColour = AccentColour,
Size = new Vector2(small_pulp), Size = new Vector2(small_pulp),
Y = -0.25f,
}, },
new Pulp new Pulp
{ {
@ -253,16 +253,15 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
{ {
new Pulp new Pulp
{ {
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
AccentColour = AccentColour, AccentColour = AccentColour,
Size = new Vector2(small_pulp), Size = new Vector2(small_pulp),
Y = -0.15f Y = -0.3f
}, },
new Pulp new Pulp
{ {
AccentColour = AccentColour, AccentColour = AccentColour,
Size = new Vector2(large_pulp_4 * 1.2f, large_pulp_4 * 3), Size = new Vector2(large_pulp_4 * 0.8f, large_pulp_4 * 2.5f),
Y = 0.05f,
}, },
} }
}; };

View File

@ -29,14 +29,24 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable.Pieces
set set
{ {
accentColour = value; accentColour = value;
if (IsLoaded) updateAccentColour();
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Radius = 8,
Colour = accentColour.Darken(0.2f).Opacity(0.75f)
};
} }
} }
private void updateAccentColour()
{
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Radius = Size.X / 2,
Colour = accentColour.Darken(0.2f).Opacity(0.75f)
};
}
protected override void LoadComplete()
{
base.LoadComplete();
updateAccentColour();
}
} }
} }

View File

@ -32,6 +32,8 @@ namespace osu.Game.Rulesets.Catch.UI
public override PassThroughInputManager CreateInputManager() => new CatchInputManager(Ruleset.RulesetInfo); public override PassThroughInputManager CreateInputManager() => new CatchInputManager(Ruleset.RulesetInfo);
protected override Vector2 PlayfieldArea => new Vector2(0.86f); // matches stable's vertical offset for catcher plate
protected override DrawableHitObject<CatchHitObject> GetVisualRepresentation(CatchHitObject h) protected override DrawableHitObject<CatchHitObject> GetVisualRepresentation(CatchHitObject h)
{ {
switch (h) switch (h)

View File

@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Catch.UI
{ {
public class CatcherArea : Container public class CatcherArea : Container
{ {
public const float CATCHER_SIZE = 172; public const float CATCHER_SIZE = 84;
protected readonly Catcher MovableCatcher; protected readonly Catcher MovableCatcher;
@ -99,8 +99,6 @@ namespace osu.Game.Rulesets.Catch.UI
public class Catcher : Container, IKeyBindingHandler<CatchAction> public class Catcher : Container, IKeyBindingHandler<CatchAction>
{ {
private Texture texture;
private Container<DrawableHitObject> caughtFruit; private Container<DrawableHitObject> caughtFruit;
public Container ExplodingFruitTarget; public Container ExplodingFruitTarget;
@ -121,10 +119,8 @@ namespace osu.Game.Rulesets.Catch.UI
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(TextureStore textures) private void load()
{ {
texture = textures.Get(@"Play/Catch/fruit-catcher-idle");
Children = new Drawable[] Children = new Drawable[]
{ {
caughtFruit = new Container<DrawableHitObject> caughtFruit = new Container<DrawableHitObject>
@ -196,13 +192,7 @@ namespace osu.Game.Rulesets.Catch.UI
Scheduler.AddDelayed(beginTrail, HyperDashing ? 25 : 50); Scheduler.AddDelayed(beginTrail, HyperDashing ? 25 : 50);
} }
private Sprite createCatcherSprite() => new Sprite private Sprite createCatcherSprite() => new CatcherSprite();
{
Size = new Vector2(CATCHER_SIZE),
FillMode = FillMode.Fill,
Texture = texture,
OriginPosition = new Vector2(-3, 10) // temporary until the sprite is aligned correctly.
};
/// <summary> /// <summary>
/// Add a caught fruit to the catcher's stack. /// Add a caught fruit to the catcher's stack.
@ -411,6 +401,23 @@ namespace osu.Game.Rulesets.Catch.UI
f.Expire(); f.Expire();
} }
} }
private class CatcherSprite : Sprite
{
public CatcherSprite()
{
Size = new Vector2(CATCHER_SIZE);
// Sets the origin roughly to the centre of the catcher's plate to allow for correct scaling.
OriginPosition = new Vector2(-0.02f, 0.06f) * CATCHER_SIZE;
}
[BackgroundDependencyLoader]
private void load(TextureStore textures)
{
Texture = textures.Get(@"Play/Catch/fruit-catcher-idle");
}
}
} }
} }
} }

View File

@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public override string Name => "Dual Stages"; public override string Name => "Dual Stages";
public override string ShortenedName => "DS"; public override string ShortenedName => "DS";
public override string Description => @"Double the stages, double the fun!"; public override string Description => @"Double the stages, double the fun!";
public override double ScoreMultiplier => 0; public override double ScoreMultiplier => 1;
private bool isForCurrentRuleset; private bool isForCurrentRuleset;

View File

@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public override string ShortenedName => "RD"; public override string ShortenedName => "RD";
public override FontAwesome Icon => FontAwesome.fa_osu_dice; public override FontAwesome Icon => FontAwesome.fa_osu_dice;
public override string Description => @"Shuffle around the keys!"; public override string Description => @"Shuffle around the keys!";
public override double ScoreMultiplier => 0; public override double ScoreMultiplier => 1;
public void ApplyToRulesetContainer(RulesetContainer<ManiaHitObject> rulesetContainer) public void ApplyToRulesetContainer(RulesetContainer<ManiaHitObject> rulesetContainer)
{ {

View File

@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
/// </summary> /// </summary>
public class DrawableHoldNote : DrawableManiaHitObject<HoldNote>, IKeyBindingHandler<ManiaAction> public class DrawableHoldNote : DrawableManiaHitObject<HoldNote>, IKeyBindingHandler<ManiaAction>
{ {
public override bool DisplayJudgement => false;
private readonly DrawableNote head; private readonly DrawableNote head;
private readonly DrawableNote tail; private readonly DrawableNote tail;

View File

@ -213,7 +213,7 @@ namespace osu.Game.Rulesets.Mania.UI
internal void OnJudgement(DrawableHitObject judgedObject, Judgement judgement) internal void OnJudgement(DrawableHitObject judgedObject, Judgement judgement)
{ {
if (!judgement.IsHit) if (!judgement.IsHit || !judgedObject.DisplayJudgement)
return; return;
explosionContainer.Add(new HitExplosion(judgedObject)); explosionContainer.Add(new HitExplosion(judgedObject));

View File

@ -171,6 +171,9 @@ namespace osu.Game.Rulesets.Mania.UI
internal void OnJudgement(DrawableHitObject judgedObject, Judgement judgement) internal void OnJudgement(DrawableHitObject judgedObject, Judgement judgement)
{ {
if (!judgedObject.DisplayJudgement)
return;
judgements.Clear(); judgements.Clear();
judgements.Add(new DrawableManiaJudgement(judgement, judgedObject) judgements.Add(new DrawableManiaJudgement(judgement, judgedObject)
{ {

View File

@ -13,8 +13,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override string ShortenedName => "AP"; public override string ShortenedName => "AP";
public override FontAwesome Icon => FontAwesome.fa_osu_mod_autopilot; public override FontAwesome Icon => FontAwesome.fa_osu_mod_autopilot;
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 => 0; public override double ScoreMultiplier => 1;
public override bool Ranked => false;
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) };
} }
} }

View File

@ -12,6 +12,7 @@ using osu.Framework.Platform;
using osu.Game.IPC; using osu.Game.IPC;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using SharpCompress.Archives.Zip;
namespace osu.Game.Tests.Beatmaps.IO namespace osu.Game.Tests.Beatmaps.IO
{ {
@ -77,8 +78,69 @@ namespace osu.Game.Tests.Beatmaps.IO
var manager = osu.Dependencies.Get<BeatmapManager>(); var manager = osu.Dependencies.Get<BeatmapManager>();
Assert.IsTrue(manager.GetAllUsableBeatmapSets().Count == 1); Assert.AreEqual(1, manager.GetAllUsableBeatmapSets().Count);
Assert.IsTrue(manager.QueryBeatmapSets(_ => true).ToList().Count == 1); Assert.AreEqual(1, manager.QueryBeatmapSets(_ => true).ToList().Count);
}
finally
{
host.Exit();
}
}
}
[Test]
public void TestRollbackOnFailure()
{
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestRollbackOnFailure"))
{
try
{
var osu = loadOsu(host);
var manager = osu.Dependencies.Get<BeatmapManager>();
int fireCount = 0;
// ReSharper disable once AccessToModifiedClosure
manager.ItemAdded += _ => fireCount++;
manager.ItemRemoved += _ => fireCount++;
var imported = loadOszIntoOsu(osu);
Assert.AreEqual(0, fireCount -= 1);
imported.Hash += "-changed";
manager.Update(imported);
Assert.AreEqual(0, fireCount -= 2);
var breakTemp = createTemporaryBeatmap();
MemoryStream brokenOsu = new MemoryStream(new byte[] { 1, 3, 3, 7 });
MemoryStream brokenOsz = new MemoryStream(File.ReadAllBytes(breakTemp));
File.Delete(breakTemp);
using (var outStream = File.Open(breakTemp, FileMode.CreateNew))
using (var zip = ZipArchive.Open(brokenOsz))
{
zip.AddEntry("broken.osu", brokenOsu, false);
zip.SaveTo(outStream, SharpCompress.Common.CompressionType.Deflate);
}
Assert.AreEqual(1, manager.GetAllUsableBeatmapSets().Count);
Assert.AreEqual(1, manager.QueryBeatmapSets(_ => true).ToList().Count);
Assert.AreEqual(12, manager.QueryBeatmaps(_ => true).ToList().Count);
// this will trigger purging of the existing beatmap (online set id match) but should rollback due to broken osu.
manager.Import(breakTemp);
// no events should be fired in the case of a rollback.
Assert.AreEqual(0, fireCount);
Assert.AreEqual(1, manager.GetAllUsableBeatmapSets().Count);
Assert.AreEqual(1, manager.QueryBeatmapSets(_ => true).ToList().Count);
Assert.AreEqual(12, manager.QueryBeatmaps(_ => true).ToList().Count);
} }
finally finally
{ {
@ -100,18 +162,17 @@ namespace osu.Game.Tests.Beatmaps.IO
var imported = loadOszIntoOsu(osu); var imported = loadOszIntoOsu(osu);
//var change = manager.QueryBeatmapSets(_ => true).First();
imported.Hash += "-changed"; imported.Hash += "-changed";
manager.Update(imported); manager.Update(imported);
var importedSecondTime = loadOszIntoOsu(osu); var importedSecondTime = loadOszIntoOsu(osu);
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
Assert.IsTrue(imported.ID != importedSecondTime.ID); Assert.IsTrue(imported.ID != importedSecondTime.ID);
Assert.IsTrue(imported.Beatmaps.First().ID < importedSecondTime.Beatmaps.First().ID); Assert.IsTrue(imported.Beatmaps.First().ID < importedSecondTime.Beatmaps.First().ID);
Assert.IsTrue(manager.GetAllUsableBeatmapSets().Count == 1); // only one beatmap will exist as the online set ID matched, causing purging of the first import.
Assert.IsTrue(manager.QueryBeatmapSets(_ => true).ToList().Count == 1); Assert.AreEqual(1, manager.GetAllUsableBeatmapSets().Count);
Assert.AreEqual(1, manager.QueryBeatmapSets(_ => true).ToList().Count);
} }
finally finally
{ {
@ -162,8 +223,7 @@ namespace osu.Game.Tests.Beatmaps.IO
var osu = loadOsu(host); var osu = loadOsu(host);
var temp = prepareTempCopy(osz_path); var temp = createTemporaryBeatmap();
Assert.IsTrue(File.Exists(temp));
var importer = new ArchiveImportIPCChannel(client); var importer = new ArchiveImportIPCChannel(client);
if (!importer.ImportAsync(temp).Wait(10000)) if (!importer.ImportAsync(temp).Wait(10000))
@ -188,8 +248,7 @@ namespace osu.Game.Tests.Beatmaps.IO
try try
{ {
var osu = loadOsu(host); var osu = loadOsu(host);
var temp = prepareTempCopy(osz_path); var temp = createTemporaryBeatmap();
Assert.IsTrue(File.Exists(temp), "Temporary file copy never substantiated");
using (File.OpenRead(temp)) using (File.OpenRead(temp))
osu.Dependencies.Get<BeatmapManager>().Import(temp); osu.Dependencies.Get<BeatmapManager>().Import(temp);
ensureLoaded(osu); ensureLoaded(osu);
@ -203,11 +262,16 @@ namespace osu.Game.Tests.Beatmaps.IO
} }
} }
private BeatmapSetInfo loadOszIntoOsu(OsuGameBase osu) private string createTemporaryBeatmap()
{ {
var temp = prepareTempCopy(osz_path); var temp = new FileInfo(osz_path).CopyTo(Path.GetTempFileName(), true).FullName;
Assert.IsTrue(File.Exists(temp)); Assert.IsTrue(File.Exists(temp));
return temp;
}
private BeatmapSetInfo loadOszIntoOsu(OsuGameBase osu, string path = null)
{
var temp = path ?? createTemporaryBeatmap();
var manager = osu.Dependencies.Get<BeatmapManager>(); var manager = osu.Dependencies.Get<BeatmapManager>();
@ -219,7 +283,7 @@ namespace osu.Game.Tests.Beatmaps.IO
waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000); waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000);
return imported.FirstOrDefault(); return imported.LastOrDefault();
} }
private void deleteBeatmapSet(BeatmapSetInfo imported, OsuGameBase osu) private void deleteBeatmapSet(BeatmapSetInfo imported, OsuGameBase osu)
@ -228,16 +292,10 @@ namespace osu.Game.Tests.Beatmaps.IO
manager.Delete(imported); manager.Delete(imported);
Assert.IsTrue(manager.GetAllUsableBeatmapSets().Count == 0); Assert.IsTrue(manager.GetAllUsableBeatmapSets().Count == 0);
Assert.IsTrue(manager.QueryBeatmapSets(_ => true).ToList().Count == 1); Assert.AreEqual(1, manager.QueryBeatmapSets(_ => true).ToList().Count);
Assert.IsTrue(manager.QueryBeatmapSets(_ => true).First().DeletePending); Assert.IsTrue(manager.QueryBeatmapSets(_ => true).First().DeletePending);
} }
private string prepareTempCopy(string path)
{
var temp = Path.GetTempFileName();
return new FileInfo(path).CopyTo(temp, true).FullName;
}
private OsuGameBase loadOsu(GameHost host) private OsuGameBase loadOsu(GameHost host)
{ {
var osu = new OsuGameBase(); var osu = new OsuGameBase();

View File

@ -39,8 +39,6 @@ namespace osu.Game.Tests.Visual
typeof(SpecialSection), typeof(SpecialSection),
}; };
private const string unranked_suffix = " (Unranked)";
private RulesetStore rulesets; private RulesetStore rulesets;
private ModDisplay modDisplay; private ModDisplay modDisplay;
private TestModSelectOverlay modSelect; private TestModSelectOverlay modSelect;
@ -121,7 +119,7 @@ namespace osu.Game.Tests.Visual
private void testManiaMods(ManiaRuleset ruleset) private void testManiaMods(ManiaRuleset ruleset)
{ {
testMultiplierTextUnranked(ruleset.GetModsFor(ModType.Special).First(m => m is ManiaModRandom)); testRankedText(ruleset.GetModsFor(ModType.Special).First(m => m is ManiaModRandom));
} }
private void testSingleMod(Mod mod) private void testSingleMod(Mod mod)
@ -198,13 +196,16 @@ namespace osu.Game.Tests.Visual
checkLabelColor(Color4.White); checkLabelColor(Color4.White);
} }
private void testMultiplierTextUnranked(Mod mod) private void testRankedText(Mod mod)
{ {
AddAssert("check for ranked", () => !modSelect.MultiplierLabel.Text.EndsWith(unranked_suffix)); AddWaitStep(1, "wait for fade");
AddAssert("check for ranked", () => modSelect.UnrankedLabel.Alpha == 0);
selectNext(mod); selectNext(mod);
AddAssert("check for unranked", () => modSelect.MultiplierLabel.Text.EndsWith(unranked_suffix)); AddWaitStep(1, "wait for fade");
AddAssert("check for unranked", () => modSelect.UnrankedLabel.Alpha != 0);
selectPrevious(mod); selectPrevious(mod);
AddAssert("check for ranked", () => !modSelect.MultiplierLabel.Text.EndsWith(unranked_suffix)); AddWaitStep(1, "wait for fade");
AddAssert("check for ranked", () => modSelect.UnrankedLabel.Alpha == 0);
} }
private void selectNext(Mod mod) => AddStep($"left click {mod.Name}", () => modSelect.GetModButton(mod)?.SelectNext(1)); private void selectNext(Mod mod) => AddStep($"left click {mod.Name}", () => modSelect.GetModButton(mod)?.SelectNext(1));
@ -240,6 +241,7 @@ namespace osu.Game.Tests.Visual
} }
public new OsuSpriteText MultiplierLabel => base.MultiplierLabel; public new OsuSpriteText MultiplierLabel => base.MultiplierLabel;
public new OsuSpriteText UnrankedLabel => base.UnrankedLabel;
public new TriangleButton DeselectAllButton => base.DeselectAllButton; public new TriangleButton DeselectAllButton => base.DeselectAllButton;
public new Color4 LowMultiplierColour => base.LowMultiplierColour; public new Color4 LowMultiplierColour => base.LowMultiplierColour;

View File

@ -81,7 +81,7 @@ namespace osu.Game.Beatmaps
protected override void Populate(BeatmapSetInfo model, ArchiveReader archive) protected override void Populate(BeatmapSetInfo model, ArchiveReader archive)
{ {
model.Beatmaps = createBeatmapDifficulties(archive); model.Beatmaps = createBeatmapDifficulties(model, archive);
// remove metadata from difficulties where it matches the set // remove metadata from difficulties where it matches the set
foreach (BeatmapInfo b in model.Beatmaps) foreach (BeatmapInfo b in model.Beatmaps)
@ -107,6 +107,7 @@ namespace osu.Game.Beatmaps
{ {
Delete(existingOnlineId); Delete(existingOnlineId);
beatmaps.PurgeDeletable(s => s.ID == existingOnlineId.ID); beatmaps.PurgeDeletable(s => s.ID == existingOnlineId.ID);
Logger.Log($"Found existing beatmap set with same OnlineBeatmapSetID ({model.OnlineBeatmapSetID}). It has been purged.", LoggingTarget.Database);
} }
} }
@ -241,7 +242,13 @@ namespace osu.Game.Beatmaps
/// Returns a list of all usable <see cref="BeatmapSetInfo"/>s. /// Returns a list of all usable <see cref="BeatmapSetInfo"/>s.
/// </summary> /// </summary>
/// <returns>A list of available <see cref="BeatmapSetInfo"/>.</returns> /// <returns>A list of available <see cref="BeatmapSetInfo"/>.</returns>
public List<BeatmapSetInfo> GetAllUsableBeatmapSets() => beatmaps.ConsumableItems.Where(s => !s.DeletePending && !s.Protected).ToList(); public List<BeatmapSetInfo> GetAllUsableBeatmapSets() => GetAllUsableBeatmapSetsEnumerable().ToList();
/// <summary>
/// Returns a list of all usable <see cref="BeatmapSetInfo"/>s.
/// </summary>
/// <returns>A list of available <see cref="BeatmapSetInfo"/>.</returns>
public IQueryable<BeatmapSetInfo> GetAllUsableBeatmapSetsEnumerable() => beatmaps.ConsumableItems.Where(s => !s.DeletePending && !s.Protected);
/// <summary> /// <summary>
/// Perform a lookup query on available <see cref="BeatmapSetInfo"/>s. /// Perform a lookup query on available <see cref="BeatmapSetInfo"/>s.
@ -303,7 +310,7 @@ namespace osu.Game.Beatmaps
{ {
// let's make sure there are actually .osu files to import. // let's make sure there are actually .osu files to import.
string mapName = reader.Filenames.FirstOrDefault(f => f.EndsWith(".osu")); string mapName = reader.Filenames.FirstOrDefault(f => f.EndsWith(".osu"));
if (string.IsNullOrEmpty(mapName)) throw new InvalidOperationException("No beatmap files found in the map folder."); if (string.IsNullOrEmpty(mapName)) throw new InvalidOperationException("No beatmap files found in this beatmap archive.");
BeatmapMetadata metadata; BeatmapMetadata metadata;
using (var stream = new StreamReader(reader.GetStream(mapName))) using (var stream = new StreamReader(reader.GetStream(mapName)))
@ -321,7 +328,7 @@ namespace osu.Game.Beatmaps
/// <summary> /// <summary>
/// Create all required <see cref="BeatmapInfo"/>s for the provided archive. /// Create all required <see cref="BeatmapInfo"/>s for the provided archive.
/// </summary> /// </summary>
private List<BeatmapInfo> createBeatmapDifficulties(ArchiveReader reader) private List<BeatmapInfo> createBeatmapDifficulties(BeatmapSetInfo model, ArchiveReader reader)
{ {
var beatmapInfos = new List<BeatmapInfo>(); var beatmapInfos = new List<BeatmapInfo>();
@ -341,6 +348,14 @@ namespace osu.Game.Beatmaps
beatmap.BeatmapInfo.Hash = ms.ComputeSHA2Hash(); beatmap.BeatmapInfo.Hash = ms.ComputeSHA2Hash();
beatmap.BeatmapInfo.MD5Hash = ms.ComputeMD5Hash(); beatmap.BeatmapInfo.MD5Hash = ms.ComputeMD5Hash();
// ensure we have the same online set ID as the set itself.
beatmap.BeatmapInfo.OnlineBeatmapSetID = model.OnlineBeatmapSetID;
beatmap.BeatmapInfo.Metadata.OnlineBeatmapSetID = model.OnlineBeatmapSetID;
// check that no existing beatmap exists that is imported with the same online beatmap ID. if so, give it precedence.
if (beatmap.BeatmapInfo.OnlineBeatmapID.HasValue && QueryBeatmap(b => b.OnlineBeatmapID.Value == beatmap.BeatmapInfo.OnlineBeatmapID.Value) != null)
beatmap.BeatmapInfo.OnlineBeatmapID = null;
RulesetInfo ruleset = rulesets.GetRuleset(beatmap.BeatmapInfo.RulesetID); RulesetInfo ruleset = rulesets.GetRuleset(beatmap.BeatmapInfo.RulesetID);
beatmap.BeatmapInfo.Ruleset = ruleset; beatmap.BeatmapInfo.Ruleset = ruleset;

View File

@ -36,6 +36,11 @@ namespace osu.Game.Beatmaps
/// </summary> /// </summary>
public bool HasVideo { get; set; } public bool HasVideo { get; set; }
/// <summary>
/// Whether or not this beatmap set has a storyboard.
/// </summary>
public bool HasStoryboard { get; set; }
/// <summary> /// <summary>
/// The different sizes of cover art for this beatmap set. /// The different sizes of cover art for this beatmap set.
/// </summary> /// </summary>

View File

@ -0,0 +1,79 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using OpenTK.Graphics;
namespace osu.Game.Beatmaps.Drawables
{
public class UpdateableBeatmapSetCover : Container
{
private Drawable displayedCover;
private BeatmapSetInfo beatmapSet;
public BeatmapSetInfo BeatmapSet
{
get { return beatmapSet; }
set
{
if (value == beatmapSet) return;
beatmapSet = value;
if (IsLoaded)
updateCover();
}
}
private BeatmapSetCoverType coverType = BeatmapSetCoverType.Cover;
public BeatmapSetCoverType CoverType
{
get { return coverType; }
set
{
if (value == coverType) return;
coverType = value;
if (IsLoaded)
updateCover();
}
}
public UpdateableBeatmapSetCover()
{
Child = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
};
}
protected override void LoadComplete()
{
base.LoadComplete();
updateCover();
}
private void updateCover()
{
displayedCover?.FadeOut(400);
displayedCover?.Expire();
displayedCover = null;
if (beatmapSet != null)
{
Add(displayedCover = new DelayedLoadWrapper(
new BeatmapSetCover(beatmapSet, coverType)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fill,
OnLoadComplete = d => d.FadeInFromZero(400, Easing.Out),
})
);
}
}
}
}

View File

@ -56,13 +56,49 @@ namespace osu.Game.Database
// ReSharper disable once NotAccessedField.Local (we should keep a reference to this so it is not finalised) // ReSharper disable once NotAccessedField.Local (we should keep a reference to this so it is not finalised)
private ArchiveImportIPCChannel ipc; private ArchiveImportIPCChannel ipc;
private readonly List<Action> cachedEvents = new List<Action>();
/// <summary>
/// Allows delaying of outwards events until an operation is confirmed (at a database level).
/// </summary>
private bool delayingEvents;
/// <summary>
/// Begin delaying outwards events.
/// </summary>
private void delayEvents() => delayingEvents = true;
/// <summary>
/// Flush delayed events and disable delaying.
/// </summary>
/// <param name="perform">Whether the flushed events should be performed.</param>
private void flushEvents(bool perform)
{
if (perform)
{
foreach (var a in cachedEvents)
a.Invoke();
}
cachedEvents.Clear();
delayingEvents = false;
}
private void handleEvent(Action a)
{
if (delayingEvents)
cachedEvents.Add(a);
else
a.Invoke();
}
protected ArchiveModelManager(Storage storage, IDatabaseContextFactory contextFactory, MutableDatabaseBackedStore<TModel> modelStore, IIpcHost importHost = null) protected ArchiveModelManager(Storage storage, IDatabaseContextFactory contextFactory, MutableDatabaseBackedStore<TModel> modelStore, IIpcHost importHost = null)
{ {
ContextFactory = contextFactory; ContextFactory = contextFactory;
ModelStore = modelStore; ModelStore = modelStore;
ModelStore.ItemAdded += s => ItemAdded?.Invoke(s); ModelStore.ItemAdded += s => handleEvent(() => ItemAdded?.Invoke(s));
ModelStore.ItemRemoved += s => ItemRemoved?.Invoke(s); ModelStore.ItemRemoved += s => handleEvent(() => ItemRemoved?.Invoke(s));
Files = new FileStore(contextFactory, storage); Files = new FileStore(contextFactory, storage);
@ -138,24 +174,56 @@ namespace osu.Game.Database
/// <param name="archive">The archive to be imported.</param> /// <param name="archive">The archive to be imported.</param>
public TModel Import(ArchiveReader archive) public TModel Import(ArchiveReader archive)
{ {
using (ContextFactory.GetForWrite()) // used to share a context for full import. keep in mind this will block all writes. TModel item = null;
delayEvents();
try
{ {
// create a new model (don't yet add to database) using (var write = ContextFactory.GetForWrite()) // used to share a context for full import. keep in mind this will block all writes.
var item = CreateModel(archive); {
try
{
if (!write.IsTransactionLeader) throw new InvalidOperationException($"Ensure there is no parent transaction so errors can correctly be handled by {this}");
var existing = CheckForExisting(item); // create a new model (don't yet add to database)
item = CreateModel(archive);
if (existing != null) return existing; var existing = CheckForExisting(item);
item.Files = createFileInfos(archive, Files); if (existing != null)
{
Logger.Log($"Found existing {typeof(TModel)} for {archive.Name} (ID {existing.ID}). Skipping import.", LoggingTarget.Database);
return existing;
}
Populate(item, archive); item.Files = createFileInfos(archive, Files);
// import to store Populate(item, archive);
ModelStore.Add(item);
return item; // import to store
ModelStore.Add(item);
}
catch (Exception e)
{
write.Errors.Add(e);
throw;
}
}
Logger.Log($"Import of {archive.Name} successfully completed!", LoggingTarget.Database);
} }
catch (Exception e)
{
Logger.Error(e, $"Import of {archive.Name} failed and has been rolled back.", LoggingTarget.Database);
item = null;
}
finally
{
// we only want to flush events after we've confirmed the write context didn't have any errors.
flushEvents(item != null);
}
return item;
} }
/// <summary> /// <summary>
@ -178,12 +246,8 @@ namespace osu.Game.Database
/// <param name="item">The item to delete.</param> /// <param name="item">The item to delete.</param>
public void Delete(TModel item) public void Delete(TModel item)
{ {
using (var usage = ContextFactory.GetForWrite()) using (ContextFactory.GetForWrite())
{ {
var context = usage.Context;
context.ChangeTracker.AutoDetectChangesEnabled = false;
// re-fetch the model on the import context. // re-fetch the model on the import context.
var foundModel = queryModel().Include(s => s.Files).ThenInclude(f => f.FileInfo).First(s => s.ID == item.ID); var foundModel = queryModel().Include(s => s.Files).ThenInclude(f => f.FileInfo).First(s => s.ID == item.ID);
@ -191,8 +255,6 @@ namespace osu.Game.Database
if (ModelStore.Delete(foundModel)) if (ModelStore.Delete(foundModel))
Files.Dereference(foundModel.Files.Select(f => f.FileInfo).ToArray()); Files.Dereference(foundModel.Files.Select(f => f.FileInfo).ToArray());
context.ChangeTracker.AutoDetectChangesEnabled = true;
} }
} }

View File

@ -1,7 +1,9 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Linq;
using System.Threading; using System.Threading;
using Microsoft.EntityFrameworkCore.Storage;
using osu.Framework.Platform; using osu.Framework.Platform;
namespace osu.Game.Database namespace osu.Game.Database
@ -17,8 +19,12 @@ namespace osu.Game.Database
private readonly object writeLock = new object(); private readonly object writeLock = new object();
private bool currentWriteDidWrite; private bool currentWriteDidWrite;
private bool currentWriteDidError;
private int currentWriteUsages; private int currentWriteUsages;
private IDbContextTransaction currentWriteTransaction;
public DatabaseContextFactory(GameHost host) public DatabaseContextFactory(GameHost host)
{ {
this.host = host; this.host = host;
@ -35,14 +41,25 @@ namespace osu.Game.Database
/// Request a context for write usage. Can be consumed in a nested fashion (and will return the same underlying context). /// Request a context for write usage. Can be consumed in a nested fashion (and will return the same underlying context).
/// This method may block if a write is already active on a different thread. /// This method may block if a write is already active on a different thread.
/// </summary> /// </summary>
/// <param name="withTransaction">Whether to start a transaction for this write.</param>
/// <returns>A usage containing a usable context.</returns> /// <returns>A usage containing a usable context.</returns>
public DatabaseWriteUsage GetForWrite() public DatabaseWriteUsage GetForWrite(bool withTransaction = true)
{ {
Monitor.Enter(writeLock); Monitor.Enter(writeLock);
if (currentWriteTransaction == null && withTransaction)
{
// this mitigates the fact that changes on tracked entities will not be rolled back with the transaction by ensuring write operations are always executed in isolated contexts.
// if this results in sub-optimal efficiency, we may need to look into removing Database-level transactions in favour of running SaveChanges where we currently commit the transaction.
if (threadContexts.IsValueCreated)
recycleThreadContexts();
currentWriteTransaction = threadContexts.Value.Database.BeginTransaction();
}
Interlocked.Increment(ref currentWriteUsages); Interlocked.Increment(ref currentWriteUsages);
return new DatabaseWriteUsage(threadContexts.Value, usageCompleted); return new DatabaseWriteUsage(threadContexts.Value, usageCompleted) { IsTransactionLeader = currentWriteTransaction != null && currentWriteUsages == 1 };
} }
private void usageCompleted(DatabaseWriteUsage usage) private void usageCompleted(DatabaseWriteUsage usage)
@ -52,18 +69,27 @@ namespace osu.Game.Database
try try
{ {
currentWriteDidWrite |= usage.PerformedWrite; currentWriteDidWrite |= usage.PerformedWrite;
currentWriteDidError |= usage.Errors.Any();
if (usages > 0) return; if (usages == 0)
if (currentWriteDidWrite)
{ {
// explicitly dispose to ensure any outstanding flushes happen as soon as possible (and underlying resources are purged). if (currentWriteDidError)
usage.Context.Dispose(); currentWriteTransaction?.Rollback();
else
currentWriteTransaction?.Commit();
if (currentWriteDidWrite || currentWriteDidError)
{
// explicitly dispose to ensure any outstanding flushes happen as soon as possible (and underlying resources are purged).
usage.Context.Dispose();
// once all writes are complete, we want to refresh thread-specific contexts to make sure they don't have stale local caches.
recycleThreadContexts();
}
currentWriteTransaction = null;
currentWriteDidWrite = false; currentWriteDidWrite = false;
currentWriteDidError = false;
// once all writes are complete, we want to refresh thread-specific contexts to make sure they don't have stale local caches.
recycleThreadContexts();
} }
} }
finally finally

View File

@ -2,34 +2,50 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using Microsoft.EntityFrameworkCore.Storage; using System.Collections.Generic;
namespace osu.Game.Database namespace osu.Game.Database
{ {
public class DatabaseWriteUsage : IDisposable public class DatabaseWriteUsage : IDisposable
{ {
public readonly OsuDbContext Context; public readonly OsuDbContext Context;
private readonly IDbContextTransaction transaction;
private readonly Action<DatabaseWriteUsage> usageCompleted; private readonly Action<DatabaseWriteUsage> usageCompleted;
public DatabaseWriteUsage(OsuDbContext context, Action<DatabaseWriteUsage> onCompleted) public DatabaseWriteUsage(OsuDbContext context, Action<DatabaseWriteUsage> onCompleted)
{ {
Context = context; Context = context;
transaction = Context.BeginTransaction();
usageCompleted = onCompleted; usageCompleted = onCompleted;
} }
public bool PerformedWrite { get; private set; } public bool PerformedWrite { get; private set; }
private bool isDisposed; private bool isDisposed;
public List<Exception> Errors = new List<Exception>();
/// <summary>
/// Whether this write usage will commit a transaction on completion.
/// If false, there is a parent usage responsible for transaction commit.
/// </summary>
public bool IsTransactionLeader = false;
protected void Dispose(bool disposing) protected void Dispose(bool disposing)
{ {
if (isDisposed) return; if (isDisposed) return;
isDisposed = true; isDisposed = true;
PerformedWrite |= Context.SaveChanges(transaction) > 0; try
usageCompleted?.Invoke(this); {
PerformedWrite |= Context.SaveChanges() > 0;
}
catch (Exception e)
{
Errors.Add(e);
throw;
}
finally
{
usageCompleted?.Invoke(this);
}
} }
public void Dispose() public void Dispose()

View File

@ -14,7 +14,8 @@ namespace osu.Game.Database
/// Request a context for write usage. Can be consumed in a nested fashion (and will return the same underlying context). /// Request a context for write usage. Can be consumed in a nested fashion (and will return the same underlying context).
/// This method may block if a write is already active on a different thread. /// This method may block if a write is already active on a different thread.
/// </summary> /// </summary>
/// <param name="withTransaction">Whether to start a transaction for this write.</param>
/// <returns>A usage containing a usable context.</returns> /// <returns>A usage containing a usable context.</returns>
DatabaseWriteUsage GetForWrite(); DatabaseWriteUsage GetForWrite(bool withTransaction = true);
} }
} }

View File

@ -50,11 +50,10 @@ namespace osu.Game.Database
/// <param name="item">The item to update.</param> /// <param name="item">The item to update.</param>
public void Update(T item) public void Update(T item)
{ {
ItemRemoved?.Invoke(item);
using (var usage = ContextFactory.GetForWrite()) using (var usage = ContextFactory.GetForWrite())
usage.Context.Update(item); usage.Context.Update(item);
ItemRemoved?.Invoke(item);
ItemAdded?.Invoke(item); ItemAdded?.Invoke(item);
} }

View File

@ -3,7 +3,6 @@
using System; using System;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using osu.Framework.Logging; using osu.Framework.Logging;
@ -83,8 +82,8 @@ namespace osu.Game.Database
base.OnModelCreating(modelBuilder); base.OnModelCreating(modelBuilder);
modelBuilder.Entity<BeatmapInfo>().HasIndex(b => b.OnlineBeatmapID).IsUnique(); modelBuilder.Entity<BeatmapInfo>().HasIndex(b => b.OnlineBeatmapID).IsUnique();
modelBuilder.Entity<BeatmapInfo>().HasIndex(b => b.MD5Hash).IsUnique(); modelBuilder.Entity<BeatmapInfo>().HasIndex(b => b.MD5Hash);
modelBuilder.Entity<BeatmapInfo>().HasIndex(b => b.Hash).IsUnique(); modelBuilder.Entity<BeatmapInfo>().HasIndex(b => b.Hash);
modelBuilder.Entity<BeatmapSetInfo>().HasIndex(b => b.OnlineBeatmapSetID).IsUnique(); modelBuilder.Entity<BeatmapSetInfo>().HasIndex(b => b.OnlineBeatmapSetID).IsUnique();
modelBuilder.Entity<BeatmapSetInfo>().HasIndex(b => b.DeletePending); modelBuilder.Entity<BeatmapSetInfo>().HasIndex(b => b.DeletePending);
@ -104,19 +103,6 @@ namespace osu.Game.Database
modelBuilder.Entity<BeatmapInfo>().HasOne(b => b.BaseDifficulty); modelBuilder.Entity<BeatmapInfo>().HasOne(b => b.BaseDifficulty);
} }
public IDbContextTransaction BeginTransaction()
{
// return Database.BeginTransaction();
return null;
}
public int SaveChanges(IDbContextTransaction transaction = null)
{
var ret = base.SaveChanges();
if (ret > 0) transaction?.Commit();
return ret;
}
private class OsuDbLoggerFactory : ILoggerFactory private class OsuDbLoggerFactory : ILoggerFactory
{ {
#region Disposal #region Disposal

View File

@ -14,6 +14,6 @@ namespace osu.Game.Database
public OsuDbContext Get() => context; public OsuDbContext Get() => context;
public DatabaseWriteUsage GetForWrite() => new DatabaseWriteUsage(context, null); public DatabaseWriteUsage GetForWrite(bool withTransaction = true) => new DatabaseWriteUsage(context, null);
} }
} }

View File

@ -0,0 +1,377 @@
// <auto-generated />
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage;
using osu.Game.Database;
using System;
namespace osu.Game.Migrations
{
[DbContext(typeof(OsuDbContext))]
[Migration("20180529055154_RemoveUniqueHashConstraints")]
partial class RemoveUniqueHashConstraints
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.0.3-rtm-10026");
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<float>("ApproachRate");
b.Property<float>("CircleSize");
b.Property<float>("DrainRate");
b.Property<float>("OverallDifficulty");
b.Property<double>("SliderMultiplier");
b.Property<double>("SliderTickRate");
b.HasKey("ID");
b.ToTable("BeatmapDifficulty");
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<int>("AudioLeadIn");
b.Property<int>("BaseDifficultyID");
b.Property<int>("BeatDivisor");
b.Property<int>("BeatmapSetInfoID");
b.Property<bool>("Countdown");
b.Property<double>("DistanceSpacing");
b.Property<int>("GridSize");
b.Property<string>("Hash");
b.Property<bool>("Hidden");
b.Property<bool>("LetterboxInBreaks");
b.Property<string>("MD5Hash");
b.Property<int?>("MetadataID");
b.Property<int?>("OnlineBeatmapID");
b.Property<string>("Path");
b.Property<int>("RulesetID");
b.Property<bool>("SpecialStyle");
b.Property<float>("StackLeniency");
b.Property<double>("StarDifficulty");
b.Property<string>("StoredBookmarks");
b.Property<double>("TimelineZoom");
b.Property<string>("Version");
b.Property<bool>("WidescreenStoryboard");
b.HasKey("ID");
b.HasIndex("BaseDifficultyID");
b.HasIndex("BeatmapSetInfoID");
b.HasIndex("Hash");
b.HasIndex("MD5Hash");
b.HasIndex("MetadataID");
b.HasIndex("OnlineBeatmapID")
.IsUnique();
b.HasIndex("RulesetID");
b.ToTable("BeatmapInfo");
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<string>("Artist");
b.Property<string>("ArtistUnicode");
b.Property<string>("AudioFile");
b.Property<string>("AuthorString")
.HasColumnName("Author");
b.Property<string>("BackgroundFile");
b.Property<int>("PreviewTime");
b.Property<string>("Source");
b.Property<string>("Tags");
b.Property<string>("Title");
b.Property<string>("TitleUnicode");
b.HasKey("ID");
b.ToTable("BeatmapMetadata");
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<int>("BeatmapSetInfoID");
b.Property<int>("FileInfoID");
b.Property<string>("Filename")
.IsRequired();
b.HasKey("ID");
b.HasIndex("BeatmapSetInfoID");
b.HasIndex("FileInfoID");
b.ToTable("BeatmapSetFileInfo");
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<bool>("DeletePending");
b.Property<string>("Hash");
b.Property<int?>("MetadataID");
b.Property<int?>("OnlineBeatmapSetID");
b.Property<bool>("Protected");
b.HasKey("ID");
b.HasIndex("DeletePending");
b.HasIndex("Hash")
.IsUnique();
b.HasIndex("MetadataID");
b.HasIndex("OnlineBeatmapSetID")
.IsUnique();
b.ToTable("BeatmapSetInfo");
});
modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<int>("IntKey")
.HasColumnName("Key");
b.Property<int?>("RulesetID");
b.Property<string>("StringValue")
.HasColumnName("Value");
b.Property<int?>("Variant");
b.HasKey("ID");
b.HasIndex("RulesetID", "Variant");
b.ToTable("Settings");
});
modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<int>("IntAction")
.HasColumnName("Action");
b.Property<string>("KeysString")
.HasColumnName("Keys");
b.Property<int?>("RulesetID");
b.Property<int?>("Variant");
b.HasKey("ID");
b.HasIndex("IntAction");
b.HasIndex("RulesetID", "Variant");
b.ToTable("KeyBinding");
});
modelBuilder.Entity("osu.Game.IO.FileInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<string>("Hash");
b.Property<int>("ReferenceCount");
b.HasKey("ID");
b.HasIndex("Hash")
.IsUnique();
b.HasIndex("ReferenceCount");
b.ToTable("FileInfo");
});
modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b =>
{
b.Property<int?>("ID")
.ValueGeneratedOnAdd();
b.Property<bool>("Available");
b.Property<string>("InstantiationInfo");
b.Property<string>("Name");
b.Property<string>("ShortName");
b.HasKey("ID");
b.HasIndex("Available");
b.HasIndex("ShortName")
.IsUnique();
b.ToTable("RulesetInfo");
});
modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<int>("FileInfoID");
b.Property<string>("Filename")
.IsRequired();
b.Property<int>("SkinInfoID");
b.HasKey("ID");
b.HasIndex("FileInfoID");
b.HasIndex("SkinInfoID");
b.ToTable("SkinFileInfo");
});
modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<string>("Creator");
b.Property<bool>("DeletePending");
b.Property<string>("Name");
b.HasKey("ID");
b.ToTable("SkinInfo");
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
{
b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty")
.WithMany()
.HasForeignKey("BaseDifficultyID")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet")
.WithMany("Beatmaps")
.HasForeignKey("BeatmapSetInfoID")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata")
.WithMany("Beatmaps")
.HasForeignKey("MetadataID");
b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset")
.WithMany()
.HasForeignKey("RulesetID")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
{
b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo")
.WithMany("Files")
.HasForeignKey("BeatmapSetInfoID")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
.WithMany()
.HasForeignKey("FileInfoID")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
{
b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata")
.WithMany("BeatmapSets")
.HasForeignKey("MetadataID");
});
modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b =>
{
b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
.WithMany()
.HasForeignKey("FileInfoID")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("osu.Game.Skinning.SkinInfo")
.WithMany("Files")
.HasForeignKey("SkinInfoID")
.OnDelete(DeleteBehavior.Cascade);
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,53 @@
using Microsoft.EntityFrameworkCore.Migrations;
using System;
using System.Collections.Generic;
namespace osu.Game.Migrations
{
public partial class RemoveUniqueHashConstraints : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_BeatmapInfo_Hash",
table: "BeatmapInfo");
migrationBuilder.DropIndex(
name: "IX_BeatmapInfo_MD5Hash",
table: "BeatmapInfo");
migrationBuilder.CreateIndex(
name: "IX_BeatmapInfo_Hash",
table: "BeatmapInfo",
column: "Hash");
migrationBuilder.CreateIndex(
name: "IX_BeatmapInfo_MD5Hash",
table: "BeatmapInfo",
column: "MD5Hash");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_BeatmapInfo_Hash",
table: "BeatmapInfo");
migrationBuilder.DropIndex(
name: "IX_BeatmapInfo_MD5Hash",
table: "BeatmapInfo");
migrationBuilder.CreateIndex(
name: "IX_BeatmapInfo_Hash",
table: "BeatmapInfo",
column: "Hash",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_BeatmapInfo_MD5Hash",
table: "BeatmapInfo",
column: "MD5Hash",
unique: true);
}
}
}

View File

@ -1,7 +1,11 @@
// <auto-generated /> // <auto-generated />
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage;
using osu.Game.Database; using osu.Game.Database;
using System;
namespace osu.Game.Migrations namespace osu.Game.Migrations
{ {
@ -12,7 +16,7 @@ namespace osu.Game.Migrations
{ {
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder modelBuilder
.HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); .HasAnnotation("ProductVersion", "2.0.3-rtm-10026");
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b =>
{ {
@ -27,9 +31,9 @@ namespace osu.Game.Migrations
b.Property<float>("OverallDifficulty"); b.Property<float>("OverallDifficulty");
b.Property<float>("SliderMultiplier"); b.Property<double>("SliderMultiplier");
b.Property<float>("SliderTickRate"); b.Property<double>("SliderTickRate");
b.HasKey("ID"); b.HasKey("ID");
@ -91,11 +95,9 @@ namespace osu.Game.Migrations
b.HasIndex("BeatmapSetInfoID"); b.HasIndex("BeatmapSetInfoID");
b.HasIndex("Hash") b.HasIndex("Hash");
.IsUnique();
b.HasIndex("MD5Hash") b.HasIndex("MD5Hash");
.IsUnique();
b.HasIndex("MetadataID"); b.HasIndex("MetadataID");

View File

@ -30,6 +30,9 @@ namespace osu.Game.Online.API.Requests
[JsonProperty(@"video")] [JsonProperty(@"video")]
private bool hasVideo { get; set; } private bool hasVideo { get; set; }
[JsonProperty(@"storyboard")]
private bool hasStoryboard { get; set; }
[JsonProperty(@"status")] [JsonProperty(@"status")]
private BeatmapSetOnlineStatus status { get; set; } private BeatmapSetOnlineStatus status { get; set; }
@ -65,6 +68,7 @@ namespace osu.Game.Online.API.Requests
BPM = bpm, BPM = bpm,
Status = status, Status = status,
HasVideo = hasVideo, HasVideo = hasVideo,
HasStoryboard = hasStoryboard,
Submitted = submitted, Submitted = submitted,
Ranked = ranked, Ranked = ranked,
LastUpdated = lastUpdated, LastUpdated = lastUpdated,

View File

@ -1,6 +1,7 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using osu.Framework.Configuration; using osu.Framework.Configuration;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Users; using osu.Game.Users;
@ -16,6 +17,6 @@ namespace osu.Game.Online.Multiplayer
public Bindable<GameType> Type = new Bindable<GameType>(); public Bindable<GameType> Type = new Bindable<GameType>();
public Bindable<BeatmapInfo> Beatmap = new Bindable<BeatmapInfo>(); public Bindable<BeatmapInfo> Beatmap = new Bindable<BeatmapInfo>();
public Bindable<int?> MaxParticipants = new Bindable<int?>(); public Bindable<int?> MaxParticipants = new Bindable<int?>();
public Bindable<User[]> Participants = new Bindable<User[]>(); public Bindable<IEnumerable<User>> Participants = new Bindable<IEnumerable<User>>();
} }
} }

View File

@ -201,7 +201,7 @@ namespace osu.Game
{ {
try try
{ {
using (var db = contextFactory.GetForWrite()) using (var db = contextFactory.GetForWrite(false))
db.Context.Migrate(); db.Context.Migrate();
} }
catch (MigrationFailedException e) catch (MigrationFailedException e)
@ -213,7 +213,7 @@ namespace osu.Game
contextFactory.ResetDatabase(); contextFactory.ResetDatabase();
Logger.Log("Database purged successfully.", LoggingTarget.Database, LogLevel.Important); Logger.Log("Database purged successfully.", LoggingTarget.Database, LogLevel.Important);
using (var db = contextFactory.GetForWrite()) using (var db = contextFactory.GetForWrite(false))
db.Context.Migrate(); db.Context.Migrate();
} }
} }

View File

@ -49,8 +49,8 @@ namespace osu.Game.Overlays.BeatmapSet
fields.Children = new Drawable[] fields.Children = new Drawable[]
{ {
new Field("made by", BeatmapSet.Metadata.Author.Username, @"Exo2.0-RegularItalic"), new Field("mapped by", BeatmapSet.Metadata.Author.Username, @"Exo2.0-RegularItalic"),
new Field("submitted on", online.Submitted.ToString(@"MMM d, yyyy"), @"Exo2.0-Bold") new Field("submitted on", online.Submitted.ToString(@"MMMM d, yyyy"), @"Exo2.0-Bold")
{ {
Margin = new MarginPadding { Top = 5 }, Margin = new MarginPadding { Top = 5 },
}, },
@ -58,11 +58,11 @@ namespace osu.Game.Overlays.BeatmapSet
if (online.Ranked.HasValue) if (online.Ranked.HasValue)
{ {
fields.Add(new Field("ranked on ", online.Ranked.Value.ToString(@"MMM d, yyyy"), @"Exo2.0-Bold")); fields.Add(new Field("ranked on", online.Ranked.Value.ToString(@"MMMM d, yyyy"), @"Exo2.0-Bold"));
} }
else if (online.LastUpdated.HasValue) else if (online.LastUpdated.HasValue)
{ {
fields.Add(new Field("last updated on ", online.LastUpdated.Value.ToString(@"MMM d, yyyy"), @"Exo2.0-Bold")); fields.Add(new Field("last updated on", online.LastUpdated.Value.ToString(@"MMMM d, yyyy"), @"Exo2.0-Bold"));
} }
} }

View File

@ -26,7 +26,7 @@ namespace osu.Game.Overlays.BeatmapSet
private const float buttons_spacing = 5; private const float buttons_spacing = 5;
private readonly Box tabsBg; private readonly Box tabsBg;
private readonly Container coverContainer; private readonly UpdateableBeatmapSetCover cover;
private readonly OsuSpriteText title, artist; private readonly OsuSpriteText title, artist;
private readonly Container noVideoButtons; private readonly Container noVideoButtons;
private readonly FillFlowContainer videoButtons; private readonly FillFlowContainer videoButtons;
@ -36,7 +36,6 @@ namespace osu.Game.Overlays.BeatmapSet
public Details Details; public Details Details;
private BeatmapManager beatmaps; private BeatmapManager beatmaps;
private DelayedLoadWrapper cover;
public readonly BeatmapPicker Picker; public readonly BeatmapPicker Picker;
@ -62,8 +61,8 @@ namespace osu.Game.Overlays.BeatmapSet
title.Text = BeatmapSet?.Metadata.Title ?? string.Empty; title.Text = BeatmapSet?.Metadata.Title ?? string.Empty;
artist.Text = BeatmapSet?.Metadata.Artist ?? string.Empty; artist.Text = BeatmapSet?.Metadata.Artist ?? string.Empty;
onlineStatusPill.Status = BeatmapSet?.OnlineInfo.Status ?? BeatmapSetOnlineStatus.None; onlineStatusPill.Status = BeatmapSet?.OnlineInfo.Status ?? BeatmapSetOnlineStatus.None;
cover.BeatmapSet = BeatmapSet;
cover?.FadeOut(400, Easing.Out);
if (BeatmapSet != null) if (BeatmapSet != null)
{ {
downloadButtonsContainer.FadeIn(transition_duration); downloadButtonsContainer.FadeIn(transition_duration);
@ -71,19 +70,6 @@ namespace osu.Game.Overlays.BeatmapSet
noVideoButtons.FadeTo(BeatmapSet.OnlineInfo.HasVideo ? 0 : 1, transition_duration); noVideoButtons.FadeTo(BeatmapSet.OnlineInfo.HasVideo ? 0 : 1, transition_duration);
videoButtons.FadeTo(BeatmapSet.OnlineInfo.HasVideo ? 1 : 0, transition_duration); videoButtons.FadeTo(BeatmapSet.OnlineInfo.HasVideo ? 1 : 0, transition_duration);
coverContainer.Add(cover = new DelayedLoadWrapper(
new BeatmapSetCover(BeatmapSet)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fill,
OnLoadComplete = d => d.FadeInFromZero(400, Easing.Out),
}, 300)
{
RelativeSizeAxes = Axes.Both,
});
} }
else else
{ {
@ -130,12 +116,7 @@ namespace osu.Game.Overlays.BeatmapSet
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Children = new Drawable[] Children = new Drawable[]
{ {
new Box cover = new UpdateableBeatmapSetCover
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
},
coverContainer = new Container
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
}, },

View File

@ -149,7 +149,7 @@ namespace osu.Game.Overlays.Direct
{ {
new OsuSpriteText new OsuSpriteText
{ {
Text = $"from {SetInfo.Metadata.Source}", Text = $"{SetInfo.Metadata.Source}",
TextSize = 14, TextSize = 14,
Shadow = false, Shadow = false,
Colour = colours.Gray5, Colour = colours.Gray5,
@ -195,18 +195,18 @@ namespace osu.Game.Overlays.Direct
new Statistic(FontAwesome.fa_heart, SetInfo.OnlineInfo?.FavouriteCount ?? 0), new Statistic(FontAwesome.fa_heart, SetInfo.OnlineInfo?.FavouriteCount ?? 0),
}, },
}, },
playButton = new PlayButton(SetInfo)
{
Margin = new MarginPadding { Top = 5, Left = 10 },
Size = new Vector2(30),
Alpha = 0,
},
statusContainer = new FillFlowContainer statusContainer = new FillFlowContainer
{ {
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Margin = new MarginPadding { Top = 5, Left = 5 }, Margin = new MarginPadding { Top = 5, Left = 5 },
Spacing = new Vector2(5), Spacing = new Vector2(5),
}, },
playButton = new PlayButton(SetInfo)
{
Margin = new MarginPadding { Top = 5, Left = 10 },
Size = new Vector2(30),
Alpha = 0,
},
}); });
if (SetInfo.OnlineInfo?.HasVideo ?? false) if (SetInfo.OnlineInfo?.HasVideo ?? false)
@ -214,24 +214,31 @@ namespace osu.Game.Overlays.Direct
statusContainer.Add(new IconPill(FontAwesome.fa_film)); statusContainer.Add(new IconPill(FontAwesome.fa_film));
} }
if (SetInfo.OnlineInfo?.HasStoryboard ?? false)
{
statusContainer.Add(new IconPill(FontAwesome.fa_image));
}
statusContainer.Add(new BeatmapSetOnlineStatusPill(12, new MarginPadding { Horizontal = 10, Vertical = 5 }) statusContainer.Add(new BeatmapSetOnlineStatusPill(12, new MarginPadding { Horizontal = 10, Vertical = 5 })
{ {
Status = SetInfo.OnlineInfo?.Status ?? BeatmapSetOnlineStatus.None, Status = SetInfo.OnlineInfo?.Status ?? BeatmapSetOnlineStatus.None,
}); });
PreviewPlaying.ValueChanged += _ => updateStatusContainer();
} }
protected override bool OnHover(InputState state) protected override bool OnHover(InputState state)
{ {
statusContainer.FadeOut(120, Easing.InOutQuint); updateStatusContainer();
return base.OnHover(state); return base.OnHover(state);
} }
protected override void OnHoverLost(InputState state) protected override void OnHoverLost(InputState state)
{ {
base.OnHoverLost(state); base.OnHoverLost(state);
updateStatusContainer();
statusContainer.FadeIn(120, Easing.InOutQuint);
} }
private void updateStatusContainer() => statusContainer.FadeTo(IsHovered || PreviewPlaying ? 0 : 1, 120, Easing.InOutQuint);
} }
} }

View File

@ -27,8 +27,6 @@ namespace osu.Game.Overlays.Direct
{ {
public readonly BeatmapSetInfo SetInfo; public readonly BeatmapSetInfo SetInfo;
protected Box BlackBackground;
private const double hover_transition_time = 400; private const double hover_transition_time = 400;
private Container content; private Container content;
@ -81,12 +79,6 @@ namespace osu.Game.Overlays.Direct
EdgeEffect = edgeEffectNormal, EdgeEffect = edgeEffectNormal,
Children = new[] Children = new[]
{ {
// temporary blackness until the actual background loads.
BlackBackground = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
},
CreateBackground(), CreateBackground(),
progressBar = new ProgressBar progressBar = new ProgressBar
{ {
@ -215,21 +207,10 @@ namespace osu.Game.Overlays.Direct
return icons; return icons;
} }
protected Drawable CreateBackground() => new DelayedLoadWrapper( protected Drawable CreateBackground() => new UpdateableBeatmapSetCover
new BeatmapSetCover(SetInfo)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fill,
OnLoadComplete = d =>
{
d.FadeInFromZero(400, Easing.Out);
BlackBackground.Delay(400).FadeOut();
},
}, 300)
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
BeatmapSet = SetInfo,
}; };
public class Statistic : FillFlowContainer public class Statistic : FillFlowContainer

View File

@ -126,7 +126,7 @@ namespace osu.Game.Overlays.Direct
return; return;
} }
icon.Icon = playing ? FontAwesome.fa_pause : FontAwesome.fa_play; icon.Icon = playing ? FontAwesome.fa_stop : FontAwesome.fa_play;
icon.FadeColour(playing || IsHovered ? hoverColour : Color4.White, 120, Easing.InOutQuint); icon.FadeColour(playing || IsHovered ? hoverColour : Color4.White, 120, Easing.InOutQuint);
if (playing) if (playing)

View File

@ -31,7 +31,7 @@ namespace osu.Game.Overlays.Mods
protected Color4 LowMultiplierColour, HighMultiplierColour; protected Color4 LowMultiplierColour, HighMultiplierColour;
protected readonly TriangleButton DeselectAllButton; protected readonly TriangleButton DeselectAllButton;
protected readonly OsuSpriteText MultiplierLabel; protected readonly OsuSpriteText MultiplierLabel, UnrankedLabel;
private readonly FillFlowContainer footerContainer; private readonly FillFlowContainer footerContainer;
protected override bool BlockPassThroughKeyboard => false; protected override bool BlockPassThroughKeyboard => false;
@ -58,6 +58,7 @@ namespace osu.Game.Overlays.Mods
LowMultiplierColour = colours.Red; LowMultiplierColour = colours.Red;
HighMultiplierColour = colours.Green; HighMultiplierColour = colours.Green;
UnrankedLabel.Colour = colours.Blue;
if (osu != null) if (osu != null)
Ruleset.BindTo(osu.Ruleset); Ruleset.BindTo(osu.Ruleset);
@ -99,15 +100,14 @@ namespace osu.Game.Overlays.Mods
} }
MultiplierLabel.Text = $"{multiplier:N2}x"; MultiplierLabel.Text = $"{multiplier:N2}x";
if (!ranked)
MultiplierLabel.Text += " (Unranked)";
if (multiplier > 1.0) if (multiplier > 1.0)
MultiplierLabel.FadeColour(HighMultiplierColour, 200); MultiplierLabel.FadeColour(HighMultiplierColour, 200);
else if (multiplier < 1.0) else if (multiplier < 1.0)
MultiplierLabel.FadeColour(LowMultiplierColour, 200); MultiplierLabel.FadeColour(LowMultiplierColour, 200);
else else
MultiplierLabel.FadeColour(Color4.White, 200); MultiplierLabel.FadeColour(Color4.White, 200);
UnrankedLabel.FadeTo(ranked ? 0 : 1, 200);
} }
protected override void PopOut() protected override void PopOut()
@ -352,23 +352,33 @@ namespace osu.Game.Overlays.Mods
}, },
new OsuSpriteText new OsuSpriteText
{ {
Text = @"Score Multiplier: ", Text = @"Score Multiplier:",
TextSize = 30, TextSize = 30,
Shadow = true,
Margin = new MarginPadding Margin = new MarginPadding
{ {
Top = 5 Top = 5,
Right = 10
} }
}, },
MultiplierLabel = new OsuSpriteText MultiplierLabel = new OsuSpriteText
{ {
Font = @"Exo2.0-Bold", Font = @"Exo2.0-Bold",
TextSize = 30, TextSize = 30,
Shadow = true,
Margin = new MarginPadding Margin = new MarginPadding
{ {
Top = 5 Top = 5
} }
},
UnrankedLabel = new OsuSpriteText
{
Font = @"Exo2.0-Bold",
Text = @"(Unranked)",
TextSize = 30,
Margin = new MarginPadding
{
Top = 5,
Left = 10
}
} }
} }
} }

View File

@ -24,19 +24,13 @@ namespace osu.Game.Overlays.Profile.Sections.Historical
this.playCount = playCount; this.playCount = playCount;
} }
protected override Drawable CreateLeftVisual() => new DelayedLoadWrapper(new BeatmapSetCover(beatmap.BeatmapSet, BeatmapSetCoverType.List) protected override Drawable CreateLeftVisual() => new UpdateableBeatmapSetCover
{ {
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
FillMode = FillMode.Fit,
RelativeSizeAxes = Axes.Both,
OnLoadComplete = d => d.FadeInFromZero(500, Easing.OutQuint)
})
{
Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft, Anchor = Anchor.CentreLeft,
RelativeSizeAxes = Axes.None, Origin = Anchor.CentreLeft,
Size = new Vector2(80, 50), Size = new Vector2(80, 50),
BeatmapSet = beatmap.BeatmapSet,
CoverType = BeatmapSetCoverType.List,
}; };
[BackgroundDependencyLoader(true)] [BackgroundDependencyLoader(true)]

View File

@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Mods
public override string ShortenedName => "AT"; public override string ShortenedName => "AT";
public override FontAwesome Icon => FontAwesome.fa_osu_mod_auto; public override FontAwesome Icon => FontAwesome.fa_osu_mod_auto;
public override string Description => "Watch a perfect automated play through the song."; public override string Description => "Watch a perfect automated play through the song.";
public override double ScoreMultiplier => 0; public override double ScoreMultiplier => 1;
public bool AllowFail => false; public bool AllowFail => false;
public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail) }; public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail) };
} }

View File

@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Mods
public override string Name => "Relax"; public override string Name => "Relax";
public override string ShortenedName => "RX"; public override string ShortenedName => "RX";
public override FontAwesome Icon => FontAwesome.fa_osu_mod_relax; public override FontAwesome Icon => FontAwesome.fa_osu_mod_relax;
public override double ScoreMultiplier => 0; public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModNoFail), typeof(ModSuddenDeath) }; public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModNoFail), typeof(ModSuddenDeath) };
} }
} }

View File

@ -254,9 +254,6 @@ namespace osu.Game.Screens.Menu
backButton.ContractStyle = 0; backButton.ContractStyle = 0;
settingsButton.ContractStyle = 0; settingsButton.ContractStyle = 0;
if (state == MenuState.TopLevel)
buttonArea.FinishTransforms(true);
updateLogoState(lastState); updateLogoState(lastState);
using (buttonArea.BeginDelayedSequence(lastState == MenuState.Initial ? 150 : 0, true)) using (buttonArea.BeginDelayedSequence(lastState == MenuState.Initial ? 150 : 0, true))
@ -325,16 +322,15 @@ namespace osu.Game.Screens.Menu
{ {
if (logo == null) return; if (logo == null) return;
logoDelayedAction?.Cancel();
switch (state) switch (state)
{ {
case MenuState.Exit: case MenuState.Exit:
case MenuState.Initial: case MenuState.Initial:
logoTracking = false; logoDelayedAction?.Cancel();
logoDelayedAction = Scheduler.AddDelayed(() => logoDelayedAction = Scheduler.AddDelayed(() =>
{ {
logoTracking = false;
hideOverlaysOnEnter.Value = true; hideOverlaysOnEnter.Value = true;
allowOpeningOverlays.Value = false; allowOpeningOverlays.Value = false;
@ -343,34 +339,40 @@ namespace osu.Game.Screens.Menu
logo.MoveTo(new Vector2(0.5f), 800, Easing.OutExpo); logo.MoveTo(new Vector2(0.5f), 800, Easing.OutExpo);
logo.ScaleTo(1, 800, Easing.OutExpo); logo.ScaleTo(1, 800, Easing.OutExpo);
}, 150); }, buttonArea.Alpha * 150);
break; break;
case MenuState.TopLevel: case MenuState.TopLevel:
case MenuState.Play: case MenuState.Play:
logo.ClearTransforms(targetMember: nameof(Position));
logo.RelativePositionAxes = Axes.None;
switch (lastState) switch (lastState)
{ {
case MenuState.TopLevel: // coming from toplevel to play case MenuState.TopLevel: // coming from toplevel to play
break;
case MenuState.Initial: case MenuState.Initial:
logoTracking = false; logo.ClearTransforms(targetMember: nameof(Position));
logo.ScaleTo(0.5f, 200, Easing.In); logo.RelativePositionAxes = Axes.None;
bool impact = logo.Scale.X > 0.6f;
if (lastState == MenuState.Initial)
logo.ScaleTo(0.5f, 200, Easing.In);
logo.MoveTo(logoTrackingPosition, lastState == MenuState.EnteringMode ? 0 : 200, Easing.In); logo.MoveTo(logoTrackingPosition, lastState == MenuState.EnteringMode ? 0 : 200, Easing.In);
logoDelayedAction?.Cancel();
logoDelayedAction = Scheduler.AddDelayed(() => logoDelayedAction = Scheduler.AddDelayed(() =>
{ {
logoTracking = true; logoTracking = true;
logo.Impact(); if (impact)
logo.Impact();
hideOverlaysOnEnter.Value = false; hideOverlaysOnEnter.Value = false;
allowOpeningOverlays.Value = true; allowOpeningOverlays.Value = true;
}, 200); }, 200);
break; break;
default: default:
logo.ClearTransforms(targetMember: nameof(Position));
logo.RelativePositionAxes = Axes.None;
logoTracking = true; logoTracking = true;
logo.ScaleTo(0.5f, 200, Easing.OutQuint); logo.ScaleTo(0.5f, 200, Easing.OutQuint);
break; break;

View File

@ -0,0 +1,79 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Graphics.Sprites;
namespace osu.Game.Screens.Multi.Components
{
public class BeatmapTitle : FillFlowContainer<OsuSpriteText>
{
private readonly OsuSpriteText beatmapTitle, beatmapDash, beatmapArtist;
private LocalisationEngine localisation;
public float TextSize
{
set { beatmapTitle.TextSize = beatmapDash.TextSize = beatmapArtist.TextSize = value; }
}
private BeatmapInfo beatmap;
public BeatmapInfo Beatmap
{
set
{
if (value == beatmap) return;
beatmap = value;
if (IsLoaded)
updateText();
}
}
public BeatmapTitle()
{
AutoSizeAxes = Axes.Both;
Direction = FillDirection.Horizontal;
Children = new[]
{
beatmapTitle = new OsuSpriteText { Font = @"Exo2.0-BoldItalic", },
beatmapDash = new OsuSpriteText { Font = @"Exo2.0-BoldItalic", },
beatmapArtist = new OsuSpriteText { Font = @"Exo2.0-RegularItalic", },
};
}
[BackgroundDependencyLoader]
private void load(LocalisationEngine localisation)
{
this.localisation = localisation;
}
protected override void LoadComplete()
{
base.LoadComplete();
updateText();
}
private void updateText()
{
if (beatmap == null)
{
beatmapTitle.Current = beatmapArtist.Current = null;
beatmapTitle.Text = "Changing map";
beatmapDash.Text = beatmapArtist.Text = string.Empty;
}
else
{
beatmapTitle.Current = localisation.GetUnicodePreference(beatmap.Metadata.TitleUnicode, beatmap.Metadata.Title);
beatmapDash.Text = @" - ";
beatmapArtist.Current = localisation.GetUnicodePreference(beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist);
}
}
}
}

View File

@ -0,0 +1,70 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.Multiplayer;
using OpenTK;
namespace osu.Game.Screens.Multi.Components
{
public class BeatmapTypeInfo : FillFlowContainer
{
private readonly ModeTypeInfo modeTypeInfo;
private readonly BeatmapTitle beatmapTitle;
private readonly OsuSpriteText beatmapAuthor;
public BeatmapInfo Beatmap
{
set
{
modeTypeInfo.Beatmap = beatmapTitle.Beatmap = value;
beatmapAuthor.Text = value == null ? string.Empty : $"mapped by {value.Metadata.Author}";
}
}
public GameType Type
{
set { modeTypeInfo.Type = value; }
}
public BeatmapTypeInfo()
{
AutoSizeAxes = Axes.Both;
Direction = FillDirection.Horizontal;
LayoutDuration = 100;
Spacing = new Vector2(5f, 0f);
Children = new Drawable[]
{
modeTypeInfo = new ModeTypeInfo(),
new Container
{
AutoSizeAxes = Axes.X,
Height = 30,
Margin = new MarginPadding { Left = 5 },
Children = new Drawable[]
{
beatmapTitle = new BeatmapTitle(),
beatmapAuthor = new OsuSpriteText
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
TextSize = 14,
},
},
},
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
beatmapAuthor.Colour = colours.Gray9;
}
}
}

View File

@ -11,7 +11,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Framework.Localisation;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables; using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics; using osu.Game.Graphics;
@ -42,7 +41,7 @@ namespace osu.Game.Screens.Multi.Components
private readonly Bindable<RoomStatus> statusBind = new Bindable<RoomStatus>(); private readonly Bindable<RoomStatus> statusBind = new Bindable<RoomStatus>();
private readonly Bindable<GameType> typeBind = new Bindable<GameType>(); private readonly Bindable<GameType> typeBind = new Bindable<GameType>();
private readonly Bindable<BeatmapInfo> beatmapBind = new Bindable<BeatmapInfo>(); private readonly Bindable<BeatmapInfo> beatmapBind = new Bindable<BeatmapInfo>();
private readonly Bindable<User[]> participantsBind = new Bindable<User[]>(); private readonly Bindable<IEnumerable<User>> participantsBind = new Bindable<IEnumerable<User>>();
public readonly Room Room; public readonly Room Room;
@ -108,12 +107,13 @@ namespace osu.Game.Screens.Multi.Components
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours, LocalisationEngine localisation) private void load(OsuColour colours)
{ {
Box sideStrip; Box sideStrip;
Container coverContainer; UpdateableBeatmapSetCover cover;
OsuSpriteText name, status, beatmapTitle, beatmapDash, beatmapArtist; OsuSpriteText name, status;
ParticipantInfo participantInfo; ParticipantInfo participantInfo;
BeatmapTitle beatmapTitle;
ModeTypeInfo modeTypeInfo; ModeTypeInfo modeTypeInfo;
Children = new Drawable[] Children = new Drawable[]
@ -146,24 +146,12 @@ namespace osu.Game.Screens.Multi.Components
RelativeSizeAxes = Axes.Y, RelativeSizeAxes = Axes.Y,
Width = side_strip_width, Width = side_strip_width,
}, },
new Container cover = new UpdateableBeatmapSetCover
{ {
Width = cover_width, Width = cover_width,
RelativeSizeAxes = Axes.Y, RelativeSizeAxes = Axes.Y,
Masking = true, Masking = true,
Margin = new MarginPadding { Left = side_strip_width }, Margin = new MarginPadding { Left = side_strip_width },
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
},
coverContainer = new Container
{
RelativeSizeAxes = Axes.Both,
},
},
}, },
new Container new Container
{ {
@ -205,30 +193,10 @@ namespace osu.Game.Screens.Multi.Components
TextSize = 14, TextSize = 14,
Font = @"Exo2.0-Bold", Font = @"Exo2.0-Bold",
}, },
new FillFlowContainer<OsuSpriteText> beatmapTitle = new BeatmapTitle
{ {
RelativeSizeAxes = Axes.X, TextSize = 14,
AutoSizeAxes = Axes.Y, Colour = colours.Gray9
Colour = colours.Gray9,
Direction = FillDirection.Horizontal,
Children = new[]
{
beatmapTitle = new OsuSpriteText
{
TextSize = 14,
Font = @"Exo2.0-BoldItalic",
},
beatmapDash = new OsuSpriteText
{
TextSize = 14,
Font = @"Exo2.0-BoldItalic",
},
beatmapArtist = new OsuSpriteText
{
TextSize = 14,
Font = @"Exo2.0-RegularItalic",
},
},
}, },
}, },
}, },
@ -259,34 +227,9 @@ namespace osu.Game.Screens.Multi.Components
beatmapBind.ValueChanged += b => beatmapBind.ValueChanged += b =>
{ {
cover.BeatmapSet = b?.BeatmapSet;
beatmapTitle.Beatmap = b;
modeTypeInfo.Beatmap = b; modeTypeInfo.Beatmap = b;
if (b != null)
{
coverContainer.FadeIn(transition_duration);
LoadComponentAsync(new BeatmapSetCover(b.BeatmapSet)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
FillMode = FillMode.Fill,
OnLoadComplete = d => d.FadeInFromZero(400, Easing.Out),
}, coverContainer.Add);
beatmapTitle.Current = localisation.GetUnicodePreference(b.Metadata.TitleUnicode, b.Metadata.Title);
beatmapDash.Text = @" - ";
beatmapArtist.Current = localisation.GetUnicodePreference(b.Metadata.ArtistUnicode, b.Metadata.Artist);
}
else
{
coverContainer.FadeOut(transition_duration);
beatmapTitle.Current = null;
beatmapArtist.Current = null;
beatmapTitle.Text = "Changing map";
beatmapDash.Text = beatmapArtist.Text = string.Empty;
}
}; };
nameBind.BindTo(Room.Name); nameBind.BindTo(Room.Name);

View File

@ -64,6 +64,7 @@ namespace osu.Game.Screens.Multi.Components
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal, Direction = FillDirection.Horizontal,
Spacing = new Vector2(5f, 0f), Spacing = new Vector2(5f, 0f),
LayoutDuration = 100,
Children = new[] Children = new[]
{ {
rulesetContainer = new Container rulesetContainer = new Container

View File

@ -0,0 +1,79 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics.Sprites;
namespace osu.Game.Screens.Multi.Components
{
public class ParticipantCount : FillFlowContainer
{
private const float text_size = 30;
private const float transition_duration = 100;
private readonly OsuSpriteText count, slash, maxText;
public int Count
{
set => count.Text = value.ToString();
}
private int? max;
public int? Max
{
get => max;
set
{
if (value == max) return;
max = value;
updateMax();
}
}
public ParticipantCount()
{
AutoSizeAxes = Axes.Both;
Direction = FillDirection.Horizontal;
LayoutDuration = transition_duration;
Children = new[]
{
count = new OsuSpriteText
{
TextSize = text_size,
Font = @"Exo2.0-Bold"
},
slash = new OsuSpriteText
{
Text = @"/",
TextSize = text_size,
Font = @"Exo2.0-Light"
},
maxText = new OsuSpriteText
{
TextSize = text_size,
Font = @"Exo2.0-Light"
},
};
updateMax();
}
private void updateMax()
{
if (Max == null)
{
slash.FadeOut(transition_duration);
maxText.FadeOut(transition_duration);
}
else
{
slash.FadeIn(transition_duration);
maxText.Text = Max.ToString();
maxText.FadeIn(transition_duration);
}
}
}
}

View File

@ -1,6 +1,7 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Configuration; using osu.Framework.Configuration;
@ -10,7 +11,6 @@ using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Localisation;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables; using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics; using osu.Game.Graphics;
@ -34,13 +34,15 @@ namespace osu.Game.Screens.Multi.Components
private readonly Bindable<GameType> typeBind = new Bindable<GameType>(); private readonly Bindable<GameType> typeBind = new Bindable<GameType>();
private readonly Bindable<BeatmapInfo> beatmapBind = new Bindable<BeatmapInfo>(); private readonly Bindable<BeatmapInfo> beatmapBind = new Bindable<BeatmapInfo>();
private readonly Bindable<int?> maxParticipantsBind = new Bindable<int?>(); private readonly Bindable<int?> maxParticipantsBind = new Bindable<int?>();
private readonly Bindable<User[]> participantsBind = new Bindable<User[]>(); private readonly Bindable<IEnumerable<User>> participantsBind = new Bindable<IEnumerable<User>>();
private OsuColour colours; private OsuColour colours;
private Box statusStrip; private Box statusStrip;
private Container coverContainer; private UpdateableBeatmapSetCover cover;
private FillFlowContainer topFlow, participantsFlow, participantNumbersFlow, infoPanelFlow; private ParticipantCount participantCount;
private FillFlowContainer topFlow, participantsFlow;
private OsuSpriteText name, status; private OsuSpriteText name, status;
private BeatmapTypeInfo beatmapTypeInfo;
private ScrollContainer participantsScroll; private ScrollContainer participantsScroll;
private ParticipantInfo participantInfo; private ParticipantInfo participantInfo;
@ -77,13 +79,10 @@ namespace osu.Game.Screens.Multi.Components
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours, LocalisationEngine localisation) private void load(OsuColour colours)
{ {
this.colours = colours; this.colours = colours;
ModeTypeInfo modeTypeInfo;
OsuSpriteText participants, participantsSlash, maxParticipants, beatmapTitle, beatmapDash, beatmapArtist, beatmapAuthor;
Children = new Drawable[] Children = new Drawable[]
{ {
new Box new Box
@ -105,21 +104,9 @@ namespace osu.Game.Screens.Multi.Components
Masking = true, Masking = true,
Children = new Drawable[] Children = new Drawable[]
{ {
new Container cover = new UpdateableBeatmapSetCover
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
},
coverContainer = new Container
{
RelativeSizeAxes = Axes.Both,
},
},
}, },
new Box new Box
{ {
@ -132,32 +119,10 @@ namespace osu.Game.Screens.Multi.Components
Padding = new MarginPadding(20), Padding = new MarginPadding(20),
Children = new Drawable[] Children = new Drawable[]
{ {
participantNumbersFlow = new FillFlowContainer participantCount = new ParticipantCount
{ {
Anchor = Anchor.TopRight, Anchor = Anchor.TopRight,
Origin = Anchor.TopRight, Origin = Anchor.TopRight,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
LayoutDuration = transition_duration,
Children = new[]
{
participants = new OsuSpriteText
{
TextSize = 30,
Font = @"Exo2.0-Bold"
},
participantsSlash = new OsuSpriteText
{
Text = @"/",
TextSize = 30,
Font = @"Exo2.0-Light"
},
maxParticipants = new OsuSpriteText
{
TextSize = 30,
Font = @"Exo2.0-Light"
},
},
}, },
name = new OsuSpriteText name = new OsuSpriteText
{ {
@ -200,54 +165,7 @@ namespace osu.Game.Screens.Multi.Components
TextSize = 14, TextSize = 14,
Font = @"Exo2.0-Bold", Font = @"Exo2.0-Bold",
}, },
infoPanelFlow = new FillFlowContainer beatmapTypeInfo = new BeatmapTypeInfo(),
{
AutoSizeAxes = Axes.X,
Height = 30,
Direction = FillDirection.Horizontal,
LayoutDuration = transition_duration,
Spacing = new Vector2(5f, 0f),
Children = new Drawable[]
{
modeTypeInfo = new ModeTypeInfo(),
new Container
{
AutoSizeAxes = Axes.X,
RelativeSizeAxes = Axes.Y,
Margin = new MarginPadding { Left = 5 },
Children = new[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Children = new[]
{
beatmapTitle = new OsuSpriteText
{
Font = @"Exo2.0-BoldItalic",
},
beatmapDash = new OsuSpriteText
{
Font = @"Exo2.0-BoldItalic",
},
beatmapArtist = new OsuSpriteText
{
Font = @"Exo2.0-RegularItalic",
},
},
},
beatmapAuthor = new OsuSpriteText
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
TextSize = 14,
Colour = colours.Gray9,
},
},
},
},
},
}, },
}, },
}, },
@ -285,61 +203,19 @@ namespace osu.Game.Screens.Multi.Components
nameBind.ValueChanged += n => name.Text = n; nameBind.ValueChanged += n => name.Text = n;
hostBind.ValueChanged += h => participantInfo.Host = h; hostBind.ValueChanged += h => participantInfo.Host = h;
typeBind.ValueChanged += t => modeTypeInfo.Type = t; typeBind.ValueChanged += t => beatmapTypeInfo.Type = t;
maxParticipantsBind.ValueChanged += m => participantCount.Max = m;
statusBind.ValueChanged += displayStatus; statusBind.ValueChanged += displayStatus;
beatmapBind.ValueChanged += b => beatmapBind.ValueChanged += b =>
{ {
modeTypeInfo.Beatmap = b; cover.BeatmapSet = b?.BeatmapSet;
beatmapTypeInfo.Beatmap = b;
if (b != null)
{
coverContainer.FadeIn(transition_duration);
LoadComponentAsync(new BeatmapSetCover(b.BeatmapSet)
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
FillMode = FillMode.Fill,
OnLoadComplete = d => d.FadeInFromZero(400, Easing.Out),
}, coverContainer.Add);
beatmapTitle.Current = localisation.GetUnicodePreference(b.Metadata.TitleUnicode, b.Metadata.Title);
beatmapDash.Text = @" - ";
beatmapArtist.Current = localisation.GetUnicodePreference(b.Metadata.ArtistUnicode, b.Metadata.Artist);
beatmapAuthor.Text = $"mapped by {b.Metadata.Author}";
}
else
{
coverContainer.FadeOut(transition_duration);
beatmapTitle.Current = null;
beatmapArtist.Current = null;
beatmapTitle.Text = "Changing map";
beatmapDash.Text = beatmapArtist.Text = beatmapAuthor.Text = string.Empty;
}
};
maxParticipantsBind.ValueChanged += m =>
{
if (m == null)
{
participantsSlash.FadeOut(transition_duration);
maxParticipants.FadeOut(transition_duration);
}
else
{
participantsSlash.FadeIn(transition_duration);
maxParticipants.FadeIn(transition_duration);
maxParticipants.Text = m.ToString();
}
}; };
participantsBind.ValueChanged += p => participantsBind.ValueChanged += p =>
{ {
participants.Text = p.Length.ToString(); participantCount.Count = p.Count();
participantInfo.Participants = p; participantInfo.Participants = p;
participantsFlow.ChildrenEnumerable = p.Select(u => new UserTile(u)); participantsFlow.ChildrenEnumerable = p.Select(u => new UserTile(u));
}; };
@ -367,10 +243,10 @@ namespace osu.Game.Screens.Multi.Components
{ {
if (Room == null) if (Room == null)
{ {
coverContainer.FadeOut(transition_duration); cover.BeatmapSet = null;
participantsFlow.FadeOut(transition_duration); participantsFlow.FadeOut(transition_duration);
participantNumbersFlow.FadeOut(transition_duration); participantCount.FadeOut(transition_duration);
infoPanelFlow.FadeOut(transition_duration); beatmapTypeInfo.FadeOut(transition_duration);
name.FadeOut(transition_duration); name.FadeOut(transition_duration);
participantInfo.FadeOut(transition_duration); participantInfo.FadeOut(transition_duration);
@ -379,8 +255,8 @@ namespace osu.Game.Screens.Multi.Components
else else
{ {
participantsFlow.FadeIn(transition_duration); participantsFlow.FadeIn(transition_duration);
participantNumbersFlow.FadeIn(transition_duration); participantCount.FadeIn(transition_duration);
infoPanelFlow.FadeIn(transition_duration); beatmapTypeInfo.FadeIn(transition_duration);
name.FadeIn(transition_duration); name.FadeIn(transition_duration);
participantInfo.FadeIn(transition_duration); participantInfo.FadeIn(transition_duration);

View File

@ -20,7 +20,7 @@ namespace osu.Game.Screens.Multi
{ {
public const float HEIGHT = 121; public const float HEIGHT = 121;
private readonly OsuSpriteText screenTitle; private readonly OsuSpriteText screenType;
private readonly HeaderBreadcrumbControl breadcrumbs; private readonly HeaderBreadcrumbControl breadcrumbs;
public Header(Screen initialScreen) public Header(Screen initialScreen)
@ -67,7 +67,7 @@ namespace osu.Game.Screens.Multi
Text = "multiplayer ", Text = "multiplayer ",
TextSize = 25, TextSize = 25,
}, },
screenTitle = new OsuSpriteText screenType = new OsuSpriteText
{ {
TextSize = 25, TextSize = 25,
Font = @"Exo2.0-Light", Font = @"Exo2.0-Light",
@ -86,14 +86,14 @@ namespace osu.Game.Screens.Multi
}, },
}; };
breadcrumbs.Current.ValueChanged += s => screenTitle.Text = ((MultiplayerScreen)s).Title; breadcrumbs.Current.ValueChanged += s => screenType.Text = ((MultiplayerScreen)s).Type.ToLower();
breadcrumbs.Current.TriggerChange(); breadcrumbs.Current.TriggerChange();
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours)
{ {
screenTitle.Colour = colours.Yellow; screenType.Colour = colours.Yellow;
breadcrumbs.StripColour = colours.Green; breadcrumbs.StripColour = colours.Green;
} }

View File

@ -25,7 +25,7 @@ namespace osu.Game.Screens.Multi.Screens.Lounge
protected readonly FillFlowContainer<DrawableRoom> RoomsContainer; protected readonly FillFlowContainer<DrawableRoom> RoomsContainer;
protected readonly RoomInspector Inspector; protected readonly RoomInspector Inspector;
public override string Title => "lounge"; public override string Title => "Lounge";
protected override Container<Drawable> TransitionContent => content; protected override Container<Drawable> TransitionContent => content;

View File

@ -15,6 +15,11 @@ namespace osu.Game.Screens.Multi.Screens
protected virtual Container<Drawable> TransitionContent => Content; protected virtual Container<Drawable> TransitionContent => Content;
/// <summary>
/// The type to display in the title of the <see cref="Header"/>.
/// </summary>
public virtual string Type => Title;
protected override void OnEntering(Screen last) protected override void OnEntering(Screen last)
{ {
base.OnEntering(last); base.OnEntering(last);

View File

@ -215,7 +215,10 @@ namespace osu.Game.Screens
private void applyArrivingDefaults(bool isResuming) private void applyArrivingDefaults(bool isResuming)
{ {
logo.AppendAnimatingAction(() => LogoArriving(logo, isResuming), true); logo.AppendAnimatingAction(() =>
{
if (IsCurrentScreen) LogoArriving(logo, isResuming);
}, true);
if (backgroundParallaxContainer != null) if (backgroundParallaxContainer != null)
backgroundParallaxContainer.ParallaxAmount = ParallaxContainer.DEFAULT_PARALLAX_AMOUNT * BackgroundParallaxAmount; backgroundParallaxContainer.ParallaxAmount = ParallaxContainer.DEFAULT_PARALLAX_AMOUNT * BackgroundParallaxAmount;

View File

@ -209,8 +209,11 @@ namespace osu.Game.Screens.Play
{ {
base.Dispose(isDisposing); base.Dispose(isDisposing);
// if the player never got pushed, we should explicitly dispose it. if (isDisposing)
loadTask?.ContinueWith(_ => player.Dispose()); {
// if the player never got pushed, we should explicitly dispose it.
loadTask?.ContinueWith(_ => player.Dispose());
}
} }
private class BeatmapMetadataDisplay : Container private class BeatmapMetadataDisplay : Container

View File

@ -50,11 +50,11 @@ namespace osu.Game.Screens.Play.PlayerSettings
content.ResizeHeightTo(0, transition_duration, Easing.OutQuint); content.ResizeHeightTo(0, transition_duration, Easing.OutQuint);
} }
button.FadeColour(expanded ? buttonActiveColour : Color4.White, 200, Easing.OutQuint); updateExpanded();
} }
} }
private Color4 buttonActiveColour; private Color4 expandedColour;
protected PlayerSettingsGroup() protected PlayerSettingsGroup()
{ {
@ -130,9 +130,13 @@ namespace osu.Game.Screens.Play.PlayerSettings
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours)
{ {
button.Colour = buttonActiveColour = colours.Yellow; expandedColour = colours.Yellow;
updateExpanded();
} }
private void updateExpanded() => button.FadeColour(expanded ? expandedColour : Color4.White, 200, Easing.InOutQuint);
protected override Container<Drawable> Content => content; protected override Container<Drawable> Content => content;
protected override bool OnHover(InputState state) => true; protected override bool OnHover(InputState state) => true;

View File

@ -92,6 +92,6 @@ namespace osu.Game.Screens.Play
} }
} }
private string formatTime(TimeSpan timeSpan) => $"{(timeSpan < TimeSpan.Zero ? "-" : "")}{timeSpan.Duration().TotalMinutes:N0}:{timeSpan.Duration().Seconds:D2}"; private string formatTime(TimeSpan timeSpan) => $"{(timeSpan < TimeSpan.Zero ? "-" : "")}{Math.Floor(timeSpan.Duration().TotalMinutes)}:{timeSpan.Duration().Seconds:D2}";
} }
} }

View File

@ -57,6 +57,7 @@ namespace osu.Game.Screens.Ranking
{ {
base.OnEntering(last); base.OnEntering(last);
(Background as BackgroundScreenBeatmap)?.BlurTo(background_blur, 2500, Easing.OutQuint); (Background as BackgroundScreenBeatmap)?.BlurTo(background_blur, 2500, Easing.OutQuint);
Background.ScaleTo(1.1f, transition_time, Easing.OutQuint);
allCircles.ForEach(c => allCircles.ForEach(c =>
{ {
@ -102,6 +103,8 @@ namespace osu.Game.Screens.Ranking
c.ScaleTo(0, transition_time, Easing.OutSine); c.ScaleTo(0, transition_time, Easing.OutSine);
}); });
Background.ScaleTo(1f, transition_time / 4, Easing.OutQuint);
Content.FadeOut(transition_time / 4); Content.FadeOut(transition_time / 4);
return base.OnExiting(next); return base.OnExiting(next);
@ -160,7 +163,6 @@ namespace osu.Game.Screens.Ranking
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
ParallaxAmount = 0.01f, ParallaxAmount = 0.01f,
Scale = new Vector2(1 / circle_outer_scale / overscan),
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Children = new Drawable[] Children = new Drawable[]

View File

@ -51,7 +51,7 @@ namespace osu.Game.Screens.Select.Carousel
if (songSelect != null) if (songSelect != null)
{ {
startRequested = songSelect.FinaliseSelection; startRequested = b => songSelect.FinaliseSelection(b);
editRequested = songSelect.Edit; editRequested = songSelect.Edit;
} }

View File

@ -7,7 +7,7 @@ namespace osu.Game.Screens.Select
{ {
protected override bool ShowFooter => false; protected override bool ShowFooter => false;
protected override bool OnSelectionFinalised() protected override bool OnStart()
{ {
Exit(); Exit();
return true; return true;

View File

@ -5,7 +5,7 @@ namespace osu.Game.Screens.Select
{ {
public class MatchSongSelect : SongSelect public class MatchSongSelect : SongSelect
{ {
protected override bool OnSelectionFinalised() protected override bool OnStart()
{ {
Schedule(() => Schedule(() =>
{ {

View File

@ -141,7 +141,7 @@ namespace osu.Game.Screens.Select
return false; return false;
} }
protected override bool OnSelectionFinalised() protected override bool OnStart()
{ {
if (player != null) return false; if (player != null) return false;

View File

@ -2,7 +2,6 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using System.Threading;
using OpenTK; using OpenTK;
using OpenTK.Input; using OpenTK.Input;
using osu.Framework.Allocation; using osu.Framework.Allocation;
@ -63,8 +62,6 @@ namespace osu.Game.Screens.Select
private SampleChannel sampleChangeDifficulty; private SampleChannel sampleChangeDifficulty;
private SampleChannel sampleChangeBeatmap; private SampleChannel sampleChangeBeatmap;
private CancellationTokenSource initialAddSetsTask;
private GameBeatmap beatmap; private GameBeatmap beatmap;
private DependencyContainer dependencies; private DependencyContainer dependencies;
@ -212,9 +209,7 @@ namespace osu.Game.Screens.Select
sampleChangeDifficulty = audio.Sample.Get(@"SongSelect/select-difficulty"); sampleChangeDifficulty = audio.Sample.Get(@"SongSelect/select-difficulty");
sampleChangeBeatmap = audio.Sample.Get(@"SongSelect/select-expand"); sampleChangeBeatmap = audio.Sample.Get(@"SongSelect/select-expand");
initialAddSetsTask = new CancellationTokenSource(); Carousel.BeatmapSets = this.beatmaps.GetAllUsableBeatmapSetsEnumerable();
Carousel.BeatmapSets = this.beatmaps.GetAllUsableBeatmapSets();
Beatmap.DisabledChanged += disabled => Carousel.AllowSelection = !disabled; Beatmap.DisabledChanged += disabled => Carousel.AllowSelection = !disabled;
Beatmap.ValueChanged += workingBeatmapChanged; Beatmap.ValueChanged += workingBeatmapChanged;
@ -232,7 +227,8 @@ namespace osu.Game.Screens.Select
/// Call to make a selection and perform the default action for this SongSelect. /// Call to make a selection and perform the default action for this SongSelect.
/// </summary> /// </summary>
/// <param name="beatmap">An optional beatmap to override the current carousel selection.</param> /// <param name="beatmap">An optional beatmap to override the current carousel selection.</param>
public void FinaliseSelection(BeatmapInfo beatmap = null) /// <param name="performStartAction">Whether to trigger <see cref="OnStart"/>.</param>
public void FinaliseSelection(BeatmapInfo beatmap = null, bool performStartAction = true)
{ {
// if we have a pending filter operation, we want to run it now. // if we have a pending filter operation, we want to run it now.
// it could change selection (ie. if the ruleset has been changed). // it could change selection (ie. if the ruleset has been changed).
@ -248,14 +244,15 @@ namespace osu.Game.Screens.Select
selectionChangedDebounce = null; selectionChangedDebounce = null;
} }
OnSelectionFinalised(); if (performStartAction)
OnStart();
} }
/// <summary> /// <summary>
/// Called when a selection is made. /// Called when a selection is made.
/// </summary> /// </summary>
/// <returns>If a resultant action occurred that takes the user away from SongSelect.</returns> /// <returns>If a resultant action occurred that takes the user away from SongSelect.</returns>
protected abstract bool OnSelectionFinalised(); protected abstract bool OnStart();
private ScheduledDelegate selectionChangedDebounce; private ScheduledDelegate selectionChangedDebounce;
@ -400,7 +397,7 @@ namespace osu.Game.Screens.Select
protected override bool OnExiting(Screen next) protected override bool OnExiting(Screen next)
{ {
FinaliseSelection(); FinaliseSelection(performStartAction: false);
beatmapInfoWedge.State = Visibility.Hidden; beatmapInfoWedge.State = Visibility.Hidden;
@ -422,8 +419,6 @@ namespace osu.Game.Screens.Select
beatmaps.BeatmapHidden -= onBeatmapHidden; beatmaps.BeatmapHidden -= onBeatmapHidden;
beatmaps.BeatmapRestored -= onBeatmapRestored; beatmaps.BeatmapRestored -= onBeatmapRestored;
} }
initialAddSetsTask?.Cancel();
} }
/// <summary> /// <summary>

View File

@ -17,7 +17,7 @@
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="Humanizer" Version="2.2.0" /> <PackageReference Include="Humanizer" Version="2.2.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.0.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.0.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.0.3" />
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
<PackageReference Include="SharpCompress" Version="0.18.1" /> <PackageReference Include="SharpCompress" Version="0.18.1" />
<PackageReference Include="NUnit" Version="3.10.1" /> <PackageReference Include="NUnit" Version="3.10.1" />