diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs
index a495d7048d..844db4a80f 100644
--- a/osu.Desktop/OsuGameDesktop.cs
+++ b/osu.Desktop/OsuGameDesktop.cs
@@ -12,6 +12,7 @@ using osu.Framework.Platform;
using osu.Game;
using OpenTK.Input;
using Microsoft.Win32;
+using osu.Framework.Platform.Windows;
namespace osu.Desktop
{
@@ -40,7 +41,7 @@ namespace osu.Desktop
///
/// A method of accessing an osu-stable install in a controlled fashion.
///
- private class StableStorage : DesktopStorage
+ private class StableStorage : WindowsStorage
{
protected override string LocateBasePath()
{
diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj
index 3d64cab84e..af027da2fc 100644
--- a/osu.Desktop/osu.Desktop.csproj
+++ b/osu.Desktop/osu.Desktop.csproj
@@ -1,4 +1,4 @@
-
+
net471;netcoreapp2.0
@@ -20,6 +20,8 @@
osu.Desktop.Program
+
+
@@ -30,10 +32,14 @@
-
+
-
+
+
+
+
+
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseCatcherArea.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseCatcherArea.cs
index f239290ed4..5119260c53 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestCaseCatcherArea.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestCaseCatcherArea.cs
@@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Catch.Tests
Child = catcherArea = new TestCatcherArea(new BeatmapDifficulty { CircleSize = size })
{
Anchor = Anchor.CentreLeft,
- Origin = Anchor.BottomLeft
+ Origin = Anchor.TopLeft
},
};
}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseFruitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseFruitObjects.cs
index 275752523d..e77dd76353 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestCaseFruitObjects.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestCaseFruitObjects.cs
@@ -25,6 +25,7 @@ namespace osu.Game.Rulesets.Catch.Tests
typeof(DrawableCatchHitObject),
typeof(DrawableFruit),
typeof(DrawableDroplet),
+ typeof(BananaShower),
typeof(Pulp),
};
@@ -53,12 +54,19 @@ namespace osu.Game.Rulesets.Catch.Tests
private DrawableFruit createDrawable(int index)
{
- var fruit = new Fruit
- {
- StartTime = 1000000000000,
- IndexInBeatmap = index,
- Scale = 1.5f,
- };
+ Fruit fruit = index == 5
+ ? new BananaShower.Banana
+ {
+ StartTime = 1000000000000,
+ IndexInBeatmap = index,
+ Scale = 1.5f,
+ }
+ : new Fruit
+ {
+ StartTime = 1000000000000,
+ IndexInBeatmap = index,
+ Scale = 1.5f,
+ };
return new DrawableFruit(fruit)
{
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs
index 41792b10a4..4603148114 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs
@@ -18,12 +18,19 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
private Circle border;
+ private const float drawable_radius = (float)CatchHitObject.OBJECT_RADIUS * radius_adjust;
+
+ ///
+ /// Because we're adding a border around the fruit, we need to scale down some.
+ ///
+ private const float radius_adjust = 1.1f;
+
public DrawableFruit(Fruit h)
: base(h)
{
Origin = Anchor.Centre;
- Size = new Vector2((float)CatchHitObject.OBJECT_RADIUS);
+ Size = new Vector2(drawable_radius);
Masking = false;
Rotation = (float)(RNG.NextDouble() - 0.5f) * 40;
@@ -44,14 +51,14 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
Hollow = !HitObject.HyperDash,
Type = EdgeEffectType.Glow,
- Radius = 4,
+ Radius = 4 * radius_adjust,
Colour = HitObject.HyperDash ? Color4.Red : AccentColour.Darken(1).Opacity(0.6f)
},
- Size = new Vector2(Height * 1.5f),
+ Size = new Vector2(Height),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
BorderColour = Color4.White,
- BorderThickness = 4f,
+ BorderThickness = 3f * radius_adjust,
Children = new Framework.Graphics.Drawable[]
{
new Box
@@ -82,8 +89,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
private Framework.Graphics.Drawable createPulp(FruitVisualRepresentation representation)
{
- const float large_pulp_3 = 13f;
- const float distance_from_centre_3 = 0.23f;
+ const float large_pulp_3 = 8f * radius_adjust;
+ const float distance_from_centre_3 = 0.15f;
const float large_pulp_4 = large_pulp_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
{
- Anchor = Anchor.TopCentre,
- Origin = Anchor.BottomCentre,
AccentColour = AccentColour,
Size = new Vector2(small_pulp),
- Y = 0.05f,
+ Y = -0.34f,
},
new Pulp
{
@@ -146,11 +151,9 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
new Pulp
{
- Anchor = Anchor.TopCentre,
- Origin = Anchor.BottomCentre,
AccentColour = AccentColour,
Size = new Vector2(small_pulp),
- Y = 0.1f,
+ Y = -0.3f,
},
new Pulp
{
@@ -186,11 +189,9 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
new Pulp
{
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopCentre,
AccentColour = AccentColour,
Size = new Vector2(small_pulp),
- Y = -0.1f,
+ Y = -0.33f,
},
new Pulp
{
@@ -220,10 +221,9 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
new Pulp
{
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopCentre,
AccentColour = AccentColour,
Size = new Vector2(small_pulp),
+ Y = -0.25f,
},
new Pulp
{
@@ -253,16 +253,15 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
new Pulp
{
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopCentre,
AccentColour = AccentColour,
Size = new Vector2(small_pulp),
- Y = -0.15f
+ Y = -0.3f
},
new Pulp
{
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,
},
}
};
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs
index d17a72a165..250dc8c7f1 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs
@@ -29,14 +29,24 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable.Pieces
set
{
accentColour = value;
-
- EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Glow,
- Radius = 8,
- Colour = accentColour.Darken(0.2f).Opacity(0.75f)
- };
+ if (IsLoaded) updateAccentColour();
}
}
+
+ 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();
+ }
}
}
diff --git a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs
index 070dc19a6f..52763e09af 100644
--- a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs
@@ -32,6 +32,8 @@ namespace osu.Game.Rulesets.Catch.UI
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 GetVisualRepresentation(CatchHitObject h)
{
switch (h)
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
index 181536a91e..d8c7b5130d 100644
--- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
@@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Catch.UI
{
public class CatcherArea : Container
{
- public const float CATCHER_SIZE = 172;
+ public const float CATCHER_SIZE = 84;
protected readonly Catcher MovableCatcher;
@@ -99,8 +99,6 @@ namespace osu.Game.Rulesets.Catch.UI
public class Catcher : Container, IKeyBindingHandler
{
- private Texture texture;
-
private Container caughtFruit;
public Container ExplodingFruitTarget;
@@ -121,10 +119,8 @@ namespace osu.Game.Rulesets.Catch.UI
}
[BackgroundDependencyLoader]
- private void load(TextureStore textures)
+ private void load()
{
- texture = textures.Get(@"Play/Catch/fruit-catcher-idle");
-
Children = new Drawable[]
{
caughtFruit = new Container
@@ -196,13 +192,7 @@ namespace osu.Game.Rulesets.Catch.UI
Scheduler.AddDelayed(beginTrail, HyperDashing ? 25 : 50);
}
- private Sprite createCatcherSprite() => new Sprite
- {
- Size = new Vector2(CATCHER_SIZE),
- FillMode = FillMode.Fill,
- Texture = texture,
- OriginPosition = new Vector2(-3, 10) // temporary until the sprite is aligned correctly.
- };
+ private Sprite createCatcherSprite() => new CatcherSprite();
///
/// Add a caught fruit to the catcher's stack.
@@ -411,6 +401,23 @@ namespace osu.Game.Rulesets.Catch.UI
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");
+ }
+ }
}
}
}
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs
index 7f3985b26d..256811b4c1 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs
@@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public override string Name => "Dual Stages";
public override string ShortenedName => "DS";
public override string Description => @"Double the stages, double the fun!";
- public override double ScoreMultiplier => 0;
+ public override double ScoreMultiplier => 1;
private bool isForCurrentRuleset;
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs
index 5af898287a..2f951461c3 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs
@@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public override string ShortenedName => "RD";
public override FontAwesome Icon => FontAwesome.fa_osu_dice;
public override string Description => @"Shuffle around the keys!";
- public override double ScoreMultiplier => 0;
+ public override double ScoreMultiplier => 1;
public void ApplyToRulesetContainer(RulesetContainer rulesetContainer)
{
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs
index 3573c133c2..b2ddd65e38 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs
@@ -13,8 +13,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override string ShortenedName => "AP";
public override FontAwesome Icon => FontAwesome.fa_osu_mod_autopilot;
public override string Description => @"Automatic cursor movement - just follow the rhythm.";
- public override double ScoreMultiplier => 0;
- public override bool Ranked => false;
+ public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail), typeof(ModAutoplay) };
}
}
diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
index f60caf2397..586217a05f 100644
--- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
+++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
@@ -12,6 +12,7 @@ using osu.Framework.Platform;
using osu.Game.IPC;
using osu.Framework.Allocation;
using osu.Game.Beatmaps;
+using SharpCompress.Archives.Zip;
namespace osu.Game.Tests.Beatmaps.IO
{
@@ -77,8 +78,69 @@ namespace osu.Game.Tests.Beatmaps.IO
var manager = osu.Dependencies.Get();
- Assert.IsTrue(manager.GetAllUsableBeatmapSets().Count == 1);
- Assert.IsTrue(manager.QueryBeatmapSets(_ => true).ToList().Count == 1);
+ Assert.AreEqual(1, manager.GetAllUsableBeatmapSets().Count);
+ 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();
+
+ 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
{
@@ -100,18 +162,17 @@ namespace osu.Game.Tests.Beatmaps.IO
var imported = loadOszIntoOsu(osu);
- //var change = manager.QueryBeatmapSets(_ => true).First();
imported.Hash += "-changed";
manager.Update(imported);
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.Beatmaps.First().ID < importedSecondTime.Beatmaps.First().ID);
- Assert.IsTrue(manager.GetAllUsableBeatmapSets().Count == 1);
- Assert.IsTrue(manager.QueryBeatmapSets(_ => true).ToList().Count == 1);
+ // only one beatmap will exist as the online set ID matched, causing purging of the first import.
+ Assert.AreEqual(1, manager.GetAllUsableBeatmapSets().Count);
+ Assert.AreEqual(1, manager.QueryBeatmapSets(_ => true).ToList().Count);
}
finally
{
@@ -162,8 +223,7 @@ namespace osu.Game.Tests.Beatmaps.IO
var osu = loadOsu(host);
- var temp = prepareTempCopy(osz_path);
- Assert.IsTrue(File.Exists(temp));
+ var temp = createTemporaryBeatmap();
var importer = new ArchiveImportIPCChannel(client);
if (!importer.ImportAsync(temp).Wait(10000))
@@ -188,8 +248,7 @@ namespace osu.Game.Tests.Beatmaps.IO
try
{
var osu = loadOsu(host);
- var temp = prepareTempCopy(osz_path);
- Assert.IsTrue(File.Exists(temp), "Temporary file copy never substantiated");
+ var temp = createTemporaryBeatmap();
using (File.OpenRead(temp))
osu.Dependencies.Get().Import(temp);
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));
+ return temp;
+ }
+
+ private BeatmapSetInfo loadOszIntoOsu(OsuGameBase osu, string path = null)
+ {
+ var temp = path ?? createTemporaryBeatmap();
var manager = osu.Dependencies.Get();
@@ -219,7 +283,7 @@ namespace osu.Game.Tests.Beatmaps.IO
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)
@@ -228,16 +292,10 @@ namespace osu.Game.Tests.Beatmaps.IO
manager.Delete(imported);
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);
}
- private string prepareTempCopy(string path)
- {
- var temp = Path.GetTempFileName();
- return new FileInfo(path).CopyTo(temp, true).FullName;
- }
-
private OsuGameBase loadOsu(GameHost host)
{
var osu = new OsuGameBase();
diff --git a/osu.Game.Tests/Visual/TestCaseMods.cs b/osu.Game.Tests/Visual/TestCaseMods.cs
index d3d21509fd..3255478bea 100644
--- a/osu.Game.Tests/Visual/TestCaseMods.cs
+++ b/osu.Game.Tests/Visual/TestCaseMods.cs
@@ -39,8 +39,6 @@ namespace osu.Game.Tests.Visual
typeof(SpecialSection),
};
- private const string unranked_suffix = " (Unranked)";
-
private RulesetStore rulesets;
private ModDisplay modDisplay;
private TestModSelectOverlay modSelect;
@@ -121,7 +119,7 @@ namespace osu.Game.Tests.Visual
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)
@@ -198,13 +196,16 @@ namespace osu.Game.Tests.Visual
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);
- 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);
- 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));
@@ -240,6 +241,7 @@ namespace osu.Game.Tests.Visual
}
public new OsuSpriteText MultiplierLabel => base.MultiplierLabel;
+ public new OsuSpriteText UnrankedLabel => base.UnrankedLabel;
public new TriangleButton DeselectAllButton => base.DeselectAllButton;
public new Color4 LowMultiplierColour => base.LowMultiplierColour;
diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs
index 36fde8a319..efc0279aa0 100644
--- a/osu.Game/Beatmaps/BeatmapManager.cs
+++ b/osu.Game/Beatmaps/BeatmapManager.cs
@@ -81,7 +81,7 @@ namespace osu.Game.Beatmaps
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
foreach (BeatmapInfo b in model.Beatmaps)
@@ -107,6 +107,7 @@ namespace osu.Game.Beatmaps
{
Delete(existingOnlineId);
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 s.
///
/// A list of available .
- public List GetAllUsableBeatmapSets() => beatmaps.ConsumableItems.Where(s => !s.DeletePending && !s.Protected).ToList();
+ public List GetAllUsableBeatmapSets() => GetAllUsableBeatmapSetsEnumerable().ToList();
+
+ ///
+ /// Returns a list of all usable s.
+ ///
+ /// A list of available .
+ public IQueryable GetAllUsableBeatmapSetsEnumerable() => beatmaps.ConsumableItems.Where(s => !s.DeletePending && !s.Protected);
///
/// Perform a lookup query on available s.
@@ -303,7 +310,7 @@ namespace osu.Game.Beatmaps
{
// let's make sure there are actually .osu files to import.
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;
using (var stream = new StreamReader(reader.GetStream(mapName)))
@@ -321,7 +328,7 @@ namespace osu.Game.Beatmaps
///
/// Create all required s for the provided archive.
///
- private List createBeatmapDifficulties(ArchiveReader reader)
+ private List createBeatmapDifficulties(BeatmapSetInfo model, ArchiveReader reader)
{
var beatmapInfos = new List();
@@ -341,6 +348,14 @@ namespace osu.Game.Beatmaps
beatmap.BeatmapInfo.Hash = ms.ComputeSHA2Hash();
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);
beatmap.BeatmapInfo.Ruleset = ruleset;
diff --git a/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs b/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs
index a70caf0207..8f985306e3 100644
--- a/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs
+++ b/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs
@@ -36,6 +36,11 @@ namespace osu.Game.Beatmaps
///
public bool HasVideo { get; set; }
+ ///
+ /// Whether or not this beatmap set has a storyboard.
+ ///
+ public bool HasStoryboard { get; set; }
+
///
/// The different sizes of cover art for this beatmap set.
///
diff --git a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs
new file mode 100644
index 0000000000..1976db907c
--- /dev/null
+++ b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs
@@ -0,0 +1,79 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// 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),
+ })
+ );
+ }
+ }
+ }
+}
diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs
index e04559d547..7741fe9ab3 100644
--- a/osu.Game/Database/ArchiveModelManager.cs
+++ b/osu.Game/Database/ArchiveModelManager.cs
@@ -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)
private ArchiveImportIPCChannel ipc;
+ private readonly List cachedEvents = new List();
+
+ ///
+ /// Allows delaying of outwards events until an operation is confirmed (at a database level).
+ ///
+ private bool delayingEvents;
+
+ ///
+ /// Begin delaying outwards events.
+ ///
+ private void delayEvents() => delayingEvents = true;
+
+ ///
+ /// Flush delayed events and disable delaying.
+ ///
+ /// Whether the flushed events should be performed.
+ 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 modelStore, IIpcHost importHost = null)
{
ContextFactory = contextFactory;
ModelStore = modelStore;
- ModelStore.ItemAdded += s => ItemAdded?.Invoke(s);
- ModelStore.ItemRemoved += s => ItemRemoved?.Invoke(s);
+ ModelStore.ItemAdded += s => handleEvent(() => ItemAdded?.Invoke(s));
+ ModelStore.ItemRemoved += s => handleEvent(() => ItemRemoved?.Invoke(s));
Files = new FileStore(contextFactory, storage);
@@ -138,24 +174,56 @@ namespace osu.Game.Database
/// The archive to be imported.
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)
- var item = CreateModel(archive);
+ using (var write = ContextFactory.GetForWrite()) // used to share a context for full import. keep in mind this will block all writes.
+ {
+ 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
- ModelStore.Add(item);
+ Populate(item, archive);
- 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;
}
///
@@ -178,12 +246,8 @@ namespace osu.Game.Database
/// The item to delete.
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.
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))
Files.Dereference(foundModel.Files.Select(f => f.FileInfo).ToArray());
-
- context.ChangeTracker.AutoDetectChangesEnabled = true;
}
}
diff --git a/osu.Game/Database/DatabaseContextFactory.cs b/osu.Game/Database/DatabaseContextFactory.cs
index 71960303b5..a1d371f431 100644
--- a/osu.Game/Database/DatabaseContextFactory.cs
+++ b/osu.Game/Database/DatabaseContextFactory.cs
@@ -1,7 +1,9 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using System.Linq;
using System.Threading;
+using Microsoft.EntityFrameworkCore.Storage;
using osu.Framework.Platform;
namespace osu.Game.Database
@@ -17,8 +19,12 @@ namespace osu.Game.Database
private readonly object writeLock = new object();
private bool currentWriteDidWrite;
+ private bool currentWriteDidError;
+
private int currentWriteUsages;
+ private IDbContextTransaction currentWriteTransaction;
+
public DatabaseContextFactory(GameHost 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).
/// This method may block if a write is already active on a different thread.
///
+ /// Whether to start a transaction for this write.
/// A usage containing a usable context.
- public DatabaseWriteUsage GetForWrite()
+ public DatabaseWriteUsage GetForWrite(bool withTransaction = true)
{
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);
- return new DatabaseWriteUsage(threadContexts.Value, usageCompleted);
+ return new DatabaseWriteUsage(threadContexts.Value, usageCompleted) { IsTransactionLeader = currentWriteTransaction != null && currentWriteUsages == 1 };
}
private void usageCompleted(DatabaseWriteUsage usage)
@@ -52,18 +69,27 @@ namespace osu.Game.Database
try
{
currentWriteDidWrite |= usage.PerformedWrite;
+ currentWriteDidError |= usage.Errors.Any();
- if (usages > 0) return;
-
- if (currentWriteDidWrite)
+ if (usages == 0)
{
- // explicitly dispose to ensure any outstanding flushes happen as soon as possible (and underlying resources are purged).
- usage.Context.Dispose();
+ if (currentWriteDidError)
+ 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;
-
- // once all writes are complete, we want to refresh thread-specific contexts to make sure they don't have stale local caches.
- recycleThreadContexts();
+ currentWriteDidError = false;
}
}
finally
diff --git a/osu.Game/Database/DatabaseWriteUsage.cs b/osu.Game/Database/DatabaseWriteUsage.cs
index 7858c1a0d1..64ab24e824 100644
--- a/osu.Game/Database/DatabaseWriteUsage.cs
+++ b/osu.Game/Database/DatabaseWriteUsage.cs
@@ -2,34 +2,50 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
-using Microsoft.EntityFrameworkCore.Storage;
+using System.Collections.Generic;
namespace osu.Game.Database
{
public class DatabaseWriteUsage : IDisposable
{
public readonly OsuDbContext Context;
- private readonly IDbContextTransaction transaction;
private readonly Action usageCompleted;
public DatabaseWriteUsage(OsuDbContext context, Action onCompleted)
{
Context = context;
- transaction = Context.BeginTransaction();
usageCompleted = onCompleted;
}
public bool PerformedWrite { get; private set; }
private bool isDisposed;
+ public List Errors = new List();
+
+ ///
+ /// Whether this write usage will commit a transaction on completion.
+ /// If false, there is a parent usage responsible for transaction commit.
+ ///
+ public bool IsTransactionLeader = false;
protected void Dispose(bool disposing)
{
if (isDisposed) return;
isDisposed = true;
- PerformedWrite |= Context.SaveChanges(transaction) > 0;
- usageCompleted?.Invoke(this);
+ try
+ {
+ PerformedWrite |= Context.SaveChanges() > 0;
+ }
+ catch (Exception e)
+ {
+ Errors.Add(e);
+ throw;
+ }
+ finally
+ {
+ usageCompleted?.Invoke(this);
+ }
}
public void Dispose()
diff --git a/osu.Game/Database/IDatabaseContextFactory.cs b/osu.Game/Database/IDatabaseContextFactory.cs
index 372e1770e4..d38d15b252 100644
--- a/osu.Game/Database/IDatabaseContextFactory.cs
+++ b/osu.Game/Database/IDatabaseContextFactory.cs
@@ -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).
/// This method may block if a write is already active on a different thread.
///
+ /// Whether to start a transaction for this write.
/// A usage containing a usable context.
- DatabaseWriteUsage GetForWrite();
+ DatabaseWriteUsage GetForWrite(bool withTransaction = true);
}
}
diff --git a/osu.Game/Database/MutableDatabaseBackedStore.cs b/osu.Game/Database/MutableDatabaseBackedStore.cs
index 8569d81f01..69a1f57cc4 100644
--- a/osu.Game/Database/MutableDatabaseBackedStore.cs
+++ b/osu.Game/Database/MutableDatabaseBackedStore.cs
@@ -50,11 +50,10 @@ namespace osu.Game.Database
/// The item to update.
public void Update(T item)
{
- ItemRemoved?.Invoke(item);
-
using (var usage = ContextFactory.GetForWrite())
usage.Context.Update(item);
+ ItemRemoved?.Invoke(item);
ItemAdded?.Invoke(item);
}
diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs
index 1979ce3648..7758b3eb25 100644
--- a/osu.Game/Database/OsuDbContext.cs
+++ b/osu.Game/Database/OsuDbContext.cs
@@ -3,7 +3,6 @@
using System;
using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.Extensions.Logging;
using osu.Framework.Logging;
@@ -83,8 +82,8 @@ namespace osu.Game.Database
base.OnModelCreating(modelBuilder);
modelBuilder.Entity().HasIndex(b => b.OnlineBeatmapID).IsUnique();
- modelBuilder.Entity().HasIndex(b => b.MD5Hash).IsUnique();
- modelBuilder.Entity().HasIndex(b => b.Hash).IsUnique();
+ modelBuilder.Entity().HasIndex(b => b.MD5Hash);
+ modelBuilder.Entity().HasIndex(b => b.Hash);
modelBuilder.Entity().HasIndex(b => b.OnlineBeatmapSetID).IsUnique();
modelBuilder.Entity().HasIndex(b => b.DeletePending);
@@ -104,19 +103,6 @@ namespace osu.Game.Database
modelBuilder.Entity().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
{
#region Disposal
diff --git a/osu.Game/Database/SingletonContextFactory.cs b/osu.Game/Database/SingletonContextFactory.cs
index 74951e8433..ce3fbf6881 100644
--- a/osu.Game/Database/SingletonContextFactory.cs
+++ b/osu.Game/Database/SingletonContextFactory.cs
@@ -14,6 +14,6 @@ namespace osu.Game.Database
public OsuDbContext Get() => context;
- public DatabaseWriteUsage GetForWrite() => new DatabaseWriteUsage(context, null);
+ public DatabaseWriteUsage GetForWrite(bool withTransaction = true) => new DatabaseWriteUsage(context, null);
}
}
diff --git a/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.Designer.cs b/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.Designer.cs
new file mode 100644
index 0000000000..f28408bfb3
--- /dev/null
+++ b/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.Designer.cs
@@ -0,0 +1,377 @@
+//
+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("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("ApproachRate");
+
+ b.Property("CircleSize");
+
+ b.Property("DrainRate");
+
+ b.Property("OverallDifficulty");
+
+ b.Property("SliderMultiplier");
+
+ b.Property("SliderTickRate");
+
+ b.HasKey("ID");
+
+ b.ToTable("BeatmapDifficulty");
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("AudioLeadIn");
+
+ b.Property("BaseDifficultyID");
+
+ b.Property("BeatDivisor");
+
+ b.Property("BeatmapSetInfoID");
+
+ b.Property("Countdown");
+
+ b.Property("DistanceSpacing");
+
+ b.Property("GridSize");
+
+ b.Property("Hash");
+
+ b.Property("Hidden");
+
+ b.Property("LetterboxInBreaks");
+
+ b.Property("MD5Hash");
+
+ b.Property("MetadataID");
+
+ b.Property("OnlineBeatmapID");
+
+ b.Property("Path");
+
+ b.Property("RulesetID");
+
+ b.Property("SpecialStyle");
+
+ b.Property("StackLeniency");
+
+ b.Property("StarDifficulty");
+
+ b.Property("StoredBookmarks");
+
+ b.Property("TimelineZoom");
+
+ b.Property("Version");
+
+ b.Property("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("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Artist");
+
+ b.Property("ArtistUnicode");
+
+ b.Property("AudioFile");
+
+ b.Property("AuthorString")
+ .HasColumnName("Author");
+
+ b.Property("BackgroundFile");
+
+ b.Property("PreviewTime");
+
+ b.Property("Source");
+
+ b.Property("Tags");
+
+ b.Property("Title");
+
+ b.Property("TitleUnicode");
+
+ b.HasKey("ID");
+
+ b.ToTable("BeatmapMetadata");
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("BeatmapSetInfoID");
+
+ b.Property("FileInfoID");
+
+ b.Property("Filename")
+ .IsRequired();
+
+ b.HasKey("ID");
+
+ b.HasIndex("BeatmapSetInfoID");
+
+ b.HasIndex("FileInfoID");
+
+ b.ToTable("BeatmapSetFileInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("DeletePending");
+
+ b.Property("Hash");
+
+ b.Property("MetadataID");
+
+ b.Property("OnlineBeatmapSetID");
+
+ b.Property("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("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("IntKey")
+ .HasColumnName("Key");
+
+ b.Property("RulesetID");
+
+ b.Property("StringValue")
+ .HasColumnName("Value");
+
+ b.Property("Variant");
+
+ b.HasKey("ID");
+
+ b.HasIndex("RulesetID", "Variant");
+
+ b.ToTable("Settings");
+ });
+
+ modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("IntAction")
+ .HasColumnName("Action");
+
+ b.Property("KeysString")
+ .HasColumnName("Keys");
+
+ b.Property("RulesetID");
+
+ b.Property("Variant");
+
+ b.HasKey("ID");
+
+ b.HasIndex("IntAction");
+
+ b.HasIndex("RulesetID", "Variant");
+
+ b.ToTable("KeyBinding");
+ });
+
+ modelBuilder.Entity("osu.Game.IO.FileInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Hash");
+
+ b.Property("ReferenceCount");
+
+ b.HasKey("ID");
+
+ b.HasIndex("Hash")
+ .IsUnique();
+
+ b.HasIndex("ReferenceCount");
+
+ b.ToTable("FileInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Available");
+
+ b.Property("InstantiationInfo");
+
+ b.Property("Name");
+
+ b.Property("ShortName");
+
+ b.HasKey("ID");
+
+ b.HasIndex("Available");
+
+ b.HasIndex("ShortName")
+ .IsUnique();
+
+ b.ToTable("RulesetInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("FileInfoID");
+
+ b.Property("Filename")
+ .IsRequired();
+
+ b.Property("SkinInfoID");
+
+ b.HasKey("ID");
+
+ b.HasIndex("FileInfoID");
+
+ b.HasIndex("SkinInfoID");
+
+ b.ToTable("SkinFileInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Creator");
+
+ b.Property("DeletePending");
+
+ b.Property("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
+ }
+ }
+}
diff --git a/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.cs b/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.cs
new file mode 100644
index 0000000000..fe8c983a3f
--- /dev/null
+++ b/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.cs
@@ -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);
+ }
+ }
+}
diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs
index 2abbe7785f..d750d50b5b 100644
--- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs
+++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs
@@ -1,7 +1,11 @@
//
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
{
@@ -12,7 +16,7 @@ namespace osu.Game.Migrations
{
#pragma warning disable 612, 618
modelBuilder
- .HasAnnotation("ProductVersion", "2.0.0-rtm-26452");
+ .HasAnnotation("ProductVersion", "2.0.3-rtm-10026");
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b =>
{
@@ -27,9 +31,9 @@ namespace osu.Game.Migrations
b.Property("OverallDifficulty");
- b.Property("SliderMultiplier");
+ b.Property("SliderMultiplier");
- b.Property("SliderTickRate");
+ b.Property("SliderTickRate");
b.HasKey("ID");
@@ -91,11 +95,9 @@ namespace osu.Game.Migrations
b.HasIndex("BeatmapSetInfoID");
- b.HasIndex("Hash")
- .IsUnique();
+ b.HasIndex("Hash");
- b.HasIndex("MD5Hash")
- .IsUnique();
+ b.HasIndex("MD5Hash");
b.HasIndex("MetadataID");
diff --git a/osu.Game/Online/API/Requests/APIResponseBeatmapSet.cs b/osu.Game/Online/API/Requests/APIResponseBeatmapSet.cs
index 2661303652..44c1216959 100644
--- a/osu.Game/Online/API/Requests/APIResponseBeatmapSet.cs
+++ b/osu.Game/Online/API/Requests/APIResponseBeatmapSet.cs
@@ -30,6 +30,9 @@ namespace osu.Game.Online.API.Requests
[JsonProperty(@"video")]
private bool hasVideo { get; set; }
+ [JsonProperty(@"storyboard")]
+ private bool hasStoryboard { get; set; }
+
[JsonProperty(@"status")]
private BeatmapSetOnlineStatus status { get; set; }
@@ -65,6 +68,7 @@ namespace osu.Game.Online.API.Requests
BPM = bpm,
Status = status,
HasVideo = hasVideo,
+ HasStoryboard = hasStoryboard,
Submitted = submitted,
Ranked = ranked,
LastUpdated = lastUpdated,
diff --git a/osu.Game/Online/Multiplayer/Room.cs b/osu.Game/Online/Multiplayer/Room.cs
index ae3fb5ec6e..b076afbcdb 100644
--- a/osu.Game/Online/Multiplayer/Room.cs
+++ b/osu.Game/Online/Multiplayer/Room.cs
@@ -1,6 +1,7 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using System.Collections.Generic;
using osu.Framework.Configuration;
using osu.Game.Beatmaps;
using osu.Game.Users;
@@ -16,6 +17,6 @@ namespace osu.Game.Online.Multiplayer
public Bindable Type = new Bindable();
public Bindable Beatmap = new Bindable();
public Bindable MaxParticipants = new Bindable();
- public Bindable Participants = new Bindable();
+ public Bindable> Participants = new Bindable>();
}
}
diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs
index a3a081d6d1..b9d32a6322 100644
--- a/osu.Game/OsuGameBase.cs
+++ b/osu.Game/OsuGameBase.cs
@@ -208,7 +208,7 @@ namespace osu.Game
{
try
{
- using (var db = contextFactory.GetForWrite())
+ using (var db = contextFactory.GetForWrite(false))
db.Context.Migrate();
}
catch (MigrationFailedException e)
@@ -220,7 +220,7 @@ namespace osu.Game
contextFactory.ResetDatabase();
Logger.Log("Database purged successfully.", LoggingTarget.Database, LogLevel.Important);
- using (var db = contextFactory.GetForWrite())
+ using (var db = contextFactory.GetForWrite(false))
db.Context.Migrate();
}
}
diff --git a/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs b/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs
index 66e3148065..398518ef2b 100644
--- a/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs
+++ b/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs
@@ -49,8 +49,8 @@ namespace osu.Game.Overlays.BeatmapSet
fields.Children = new Drawable[]
{
- new Field("made by", BeatmapSet.Metadata.Author.Username, @"Exo2.0-RegularItalic"),
- new Field("submitted on", online.Submitted.ToString(@"MMM d, yyyy"), @"Exo2.0-Bold")
+ new Field("mapped by", BeatmapSet.Metadata.Author.Username, @"Exo2.0-RegularItalic"),
+ new Field("submitted on", online.Submitted.ToString(@"MMMM d, yyyy"), @"Exo2.0-Bold")
{
Margin = new MarginPadding { Top = 5 },
},
@@ -58,11 +58,11 @@ namespace osu.Game.Overlays.BeatmapSet
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)
{
- 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"));
}
}
diff --git a/osu.Game/Overlays/BeatmapSet/Header.cs b/osu.Game/Overlays/BeatmapSet/Header.cs
index 8833a89479..89c141ef17 100644
--- a/osu.Game/Overlays/BeatmapSet/Header.cs
+++ b/osu.Game/Overlays/BeatmapSet/Header.cs
@@ -26,7 +26,7 @@ namespace osu.Game.Overlays.BeatmapSet
private const float buttons_spacing = 5;
private readonly Box tabsBg;
- private readonly Container coverContainer;
+ private readonly UpdateableBeatmapSetCover cover;
private readonly OsuSpriteText title, artist;
private readonly Container noVideoButtons;
private readonly FillFlowContainer videoButtons;
@@ -36,7 +36,6 @@ namespace osu.Game.Overlays.BeatmapSet
public Details Details;
private BeatmapManager beatmaps;
- private DelayedLoadWrapper cover;
public readonly BeatmapPicker Picker;
@@ -62,8 +61,8 @@ namespace osu.Game.Overlays.BeatmapSet
title.Text = BeatmapSet?.Metadata.Title ?? string.Empty;
artist.Text = BeatmapSet?.Metadata.Artist ?? string.Empty;
onlineStatusPill.Status = BeatmapSet?.OnlineInfo.Status ?? BeatmapSetOnlineStatus.None;
+ cover.BeatmapSet = BeatmapSet;
- cover?.FadeOut(400, Easing.Out);
if (BeatmapSet != null)
{
downloadButtonsContainer.FadeIn(transition_duration);
@@ -71,19 +70,6 @@ namespace osu.Game.Overlays.BeatmapSet
noVideoButtons.FadeTo(BeatmapSet.OnlineInfo.HasVideo ? 0 : 1, 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
{
@@ -130,12 +116,7 @@ namespace osu.Game.Overlays.BeatmapSet
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = Color4.Black,
- },
- coverContainer = new Container
+ cover = new UpdateableBeatmapSetCover
{
RelativeSizeAxes = Axes.Both,
},
diff --git a/osu.Game/Overlays/Direct/DirectGridPanel.cs b/osu.Game/Overlays/Direct/DirectGridPanel.cs
index 9715615d14..ed4630a8e7 100644
--- a/osu.Game/Overlays/Direct/DirectGridPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectGridPanel.cs
@@ -149,7 +149,7 @@ namespace osu.Game.Overlays.Direct
{
new OsuSpriteText
{
- Text = $"from {SetInfo.Metadata.Source}",
+ Text = $"{SetInfo.Metadata.Source}",
TextSize = 14,
Shadow = false,
Colour = colours.Gray5,
@@ -195,18 +195,18 @@ namespace osu.Game.Overlays.Direct
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
{
AutoSizeAxes = Axes.Both,
Margin = new MarginPadding { Top = 5, Left = 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)
@@ -214,24 +214,31 @@ namespace osu.Game.Overlays.Direct
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 })
{
Status = SetInfo.OnlineInfo?.Status ?? BeatmapSetOnlineStatus.None,
});
+
+ PreviewPlaying.ValueChanged += _ => updateStatusContainer();
}
protected override bool OnHover(InputState state)
{
- statusContainer.FadeOut(120, Easing.InOutQuint);
-
+ updateStatusContainer();
return base.OnHover(state);
}
protected override void OnHoverLost(InputState state)
{
base.OnHoverLost(state);
-
- statusContainer.FadeIn(120, Easing.InOutQuint);
+ updateStatusContainer();
}
+
+ private void updateStatusContainer() => statusContainer.FadeTo(IsHovered || PreviewPlaying ? 0 : 1, 120, Easing.InOutQuint);
}
}
diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs
index cc0123dabc..df784252ce 100644
--- a/osu.Game/Overlays/Direct/DirectPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectPanel.cs
@@ -27,8 +27,6 @@ namespace osu.Game.Overlays.Direct
{
public readonly BeatmapSetInfo SetInfo;
- protected Box BlackBackground;
-
private const double hover_transition_time = 400;
private Container content;
@@ -81,12 +79,6 @@ namespace osu.Game.Overlays.Direct
EdgeEffect = edgeEffectNormal,
Children = new[]
{
- // temporary blackness until the actual background loads.
- BlackBackground = new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = Color4.Black,
- },
CreateBackground(),
progressBar = new ProgressBar
{
@@ -215,21 +207,10 @@ namespace osu.Game.Overlays.Direct
return icons;
}
- protected Drawable CreateBackground() => new DelayedLoadWrapper(
- 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)
+ protected Drawable CreateBackground() => new UpdateableBeatmapSetCover
{
RelativeSizeAxes = Axes.Both,
+ BeatmapSet = SetInfo,
};
public class Statistic : FillFlowContainer
diff --git a/osu.Game/Overlays/Direct/PlayButton.cs b/osu.Game/Overlays/Direct/PlayButton.cs
index 4913b11ae1..131083c6ff 100644
--- a/osu.Game/Overlays/Direct/PlayButton.cs
+++ b/osu.Game/Overlays/Direct/PlayButton.cs
@@ -126,7 +126,7 @@ namespace osu.Game.Overlays.Direct
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);
if (playing)
diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs
index 632c00d1fd..f1624721da 100644
--- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs
+++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs
@@ -31,7 +31,7 @@ namespace osu.Game.Overlays.Mods
protected Color4 LowMultiplierColour, HighMultiplierColour;
protected readonly TriangleButton DeselectAllButton;
- protected readonly OsuSpriteText MultiplierLabel;
+ protected readonly OsuSpriteText MultiplierLabel, UnrankedLabel;
private readonly FillFlowContainer footerContainer;
protected override bool BlockPassThroughKeyboard => false;
@@ -58,6 +58,7 @@ namespace osu.Game.Overlays.Mods
LowMultiplierColour = colours.Red;
HighMultiplierColour = colours.Green;
+ UnrankedLabel.Colour = colours.Blue;
if (osu != null)
Ruleset.BindTo(osu.Ruleset);
@@ -99,15 +100,14 @@ namespace osu.Game.Overlays.Mods
}
MultiplierLabel.Text = $"{multiplier:N2}x";
- if (!ranked)
- MultiplierLabel.Text += " (Unranked)";
-
if (multiplier > 1.0)
MultiplierLabel.FadeColour(HighMultiplierColour, 200);
else if (multiplier < 1.0)
MultiplierLabel.FadeColour(LowMultiplierColour, 200);
else
MultiplierLabel.FadeColour(Color4.White, 200);
+
+ UnrankedLabel.FadeTo(ranked ? 0 : 1, 200);
}
protected override void PopOut()
@@ -352,23 +352,33 @@ namespace osu.Game.Overlays.Mods
},
new OsuSpriteText
{
- Text = @"Score Multiplier: ",
+ Text = @"Score Multiplier:",
TextSize = 30,
- Shadow = true,
Margin = new MarginPadding
{
- Top = 5
+ Top = 5,
+ Right = 10
}
},
MultiplierLabel = new OsuSpriteText
{
Font = @"Exo2.0-Bold",
TextSize = 30,
- Shadow = true,
Margin = new MarginPadding
{
Top = 5
}
+ },
+ UnrankedLabel = new OsuSpriteText
+ {
+ Font = @"Exo2.0-Bold",
+ Text = @"(Unranked)",
+ TextSize = 30,
+ Margin = new MarginPadding
+ {
+ Top = 5,
+ Left = 10
+ }
}
}
}
diff --git a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedRow.cs b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedRow.cs
index be8e09105b..0a2b2fe121 100644
--- a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedRow.cs
+++ b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedRow.cs
@@ -24,19 +24,13 @@ namespace osu.Game.Overlays.Profile.Sections.Historical
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,
- RelativeSizeAxes = Axes.None,
+ Origin = Anchor.CentreLeft,
Size = new Vector2(80, 50),
+ BeatmapSet = beatmap.BeatmapSet,
+ CoverType = BeatmapSetCoverType.List,
};
[BackgroundDependencyLoader(true)]
diff --git a/osu.Game/Rulesets/Mods/ModAutoplay.cs b/osu.Game/Rulesets/Mods/ModAutoplay.cs
index 75f37b993d..7058d1bed6 100644
--- a/osu.Game/Rulesets/Mods/ModAutoplay.cs
+++ b/osu.Game/Rulesets/Mods/ModAutoplay.cs
@@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Mods
public override string ShortenedName => "AT";
public override FontAwesome Icon => FontAwesome.fa_osu_mod_auto;
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 override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail) };
}
diff --git a/osu.Game/Rulesets/Mods/ModRelax.cs b/osu.Game/Rulesets/Mods/ModRelax.cs
index e4137254e6..0bd9becd78 100644
--- a/osu.Game/Rulesets/Mods/ModRelax.cs
+++ b/osu.Game/Rulesets/Mods/ModRelax.cs
@@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Mods
public override string Name => "Relax";
public override string ShortenedName => "RX";
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) };
}
}
diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs
index bbbe918279..a0f2fc45cd 100644
--- a/osu.Game/Screens/Menu/ButtonSystem.cs
+++ b/osu.Game/Screens/Menu/ButtonSystem.cs
@@ -244,9 +244,6 @@ namespace osu.Game.Screens.Menu
backButton.ContractStyle = 0;
settingsButton.ContractStyle = 0;
- if (state == MenuState.TopLevel)
- buttonArea.FinishTransforms(true);
-
updateLogoState(lastState);
using (buttonArea.BeginDelayedSequence(lastState == MenuState.Initial ? 150 : 0, true))
@@ -315,45 +312,51 @@ namespace osu.Game.Screens.Menu
{
if (logo == null) return;
- logoDelayedAction?.Cancel();
-
switch (state)
{
case MenuState.Exit:
case MenuState.Initial:
- logoTracking = false;
-
+ logoDelayedAction?.Cancel();
logoDelayedAction = Scheduler.AddDelayed(() =>
{
+ logoTracking = false;
+
logo.ClearTransforms(targetMember: nameof(Position));
logo.RelativePositionAxes = Axes.Both;
logo.MoveTo(new Vector2(0.5f), 800, Easing.OutExpo);
logo.ScaleTo(1, 800, Easing.OutExpo);
- }, 150);
+ }, buttonArea.Alpha * 150);
break;
case MenuState.TopLevel:
case MenuState.Play:
- logo.ClearTransforms(targetMember: nameof(Position));
- logo.RelativePositionAxes = Axes.None;
-
switch (lastState)
{
case MenuState.TopLevel: // coming from toplevel to play
+ break;
case MenuState.Initial:
- logoTracking = false;
- logo.ScaleTo(0.5f, 200, Easing.In);
+ logo.ClearTransforms(targetMember: nameof(Position));
+ 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);
+ logoDelayedAction?.Cancel();
logoDelayedAction = Scheduler.AddDelayed(() =>
{
logoTracking = true;
- logo.Impact();
+ if (impact)
+ logo.Impact();
}, 200);
break;
default:
+ logo.ClearTransforms(targetMember: nameof(Position));
+ logo.RelativePositionAxes = Axes.None;
logoTracking = true;
logo.ScaleTo(0.5f, 200, Easing.OutQuint);
break;
diff --git a/osu.Game/Screens/Multi/Components/BeatmapTitle.cs b/osu.Game/Screens/Multi/Components/BeatmapTitle.cs
new file mode 100644
index 0000000000..42863754c5
--- /dev/null
+++ b/osu.Game/Screens/Multi/Components/BeatmapTitle.cs
@@ -0,0 +1,79 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// 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
+ {
+ 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);
+ }
+ }
+ }
+}
diff --git a/osu.Game/Screens/Multi/Components/BeatmapTypeInfo.cs b/osu.Game/Screens/Multi/Components/BeatmapTypeInfo.cs
new file mode 100644
index 0000000000..78ffe01ef0
--- /dev/null
+++ b/osu.Game/Screens/Multi/Components/BeatmapTypeInfo.cs
@@ -0,0 +1,70 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// 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;
+ }
+ }
+}
diff --git a/osu.Game/Screens/Multi/Components/DrawableRoom.cs b/osu.Game/Screens/Multi/Components/DrawableRoom.cs
index 1851f4618e..54bd0ae7cc 100644
--- a/osu.Game/Screens/Multi/Components/DrawableRoom.cs
+++ b/osu.Game/Screens/Multi/Components/DrawableRoom.cs
@@ -11,7 +11,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input;
-using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics;
@@ -42,7 +41,7 @@ namespace osu.Game.Screens.Multi.Components
private readonly Bindable statusBind = new Bindable();
private readonly Bindable typeBind = new Bindable();
private readonly Bindable beatmapBind = new Bindable();
- private readonly Bindable participantsBind = new Bindable();
+ private readonly Bindable> participantsBind = new Bindable>();
public readonly Room Room;
@@ -108,12 +107,13 @@ namespace osu.Game.Screens.Multi.Components
}
[BackgroundDependencyLoader]
- private void load(OsuColour colours, LocalisationEngine localisation)
+ private void load(OsuColour colours)
{
Box sideStrip;
- Container coverContainer;
- OsuSpriteText name, status, beatmapTitle, beatmapDash, beatmapArtist;
+ UpdateableBeatmapSetCover cover;
+ OsuSpriteText name, status;
ParticipantInfo participantInfo;
+ BeatmapTitle beatmapTitle;
ModeTypeInfo modeTypeInfo;
Children = new Drawable[]
@@ -146,24 +146,12 @@ namespace osu.Game.Screens.Multi.Components
RelativeSizeAxes = Axes.Y,
Width = side_strip_width,
},
- new Container
+ cover = new UpdateableBeatmapSetCover
{
Width = cover_width,
RelativeSizeAxes = Axes.Y,
Masking = true,
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
{
@@ -205,30 +193,10 @@ namespace osu.Game.Screens.Multi.Components
TextSize = 14,
Font = @"Exo2.0-Bold",
},
- new FillFlowContainer
+ beatmapTitle = new BeatmapTitle
{
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- 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",
- },
- },
+ TextSize = 14,
+ Colour = colours.Gray9
},
},
},
@@ -259,34 +227,9 @@ namespace osu.Game.Screens.Multi.Components
beatmapBind.ValueChanged += b =>
{
+ cover.BeatmapSet = b?.BeatmapSet;
+ beatmapTitle.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);
diff --git a/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs b/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs
index e3aba685a7..e2d0268090 100644
--- a/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs
+++ b/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs
@@ -64,6 +64,7 @@ namespace osu.Game.Screens.Multi.Components
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(5f, 0f),
+ LayoutDuration = 100,
Children = new[]
{
rulesetContainer = new Container
diff --git a/osu.Game/Screens/Multi/Components/ParticipantCount.cs b/osu.Game/Screens/Multi/Components/ParticipantCount.cs
new file mode 100644
index 0000000000..e7183cbd92
--- /dev/null
+++ b/osu.Game/Screens/Multi/Components/ParticipantCount.cs
@@ -0,0 +1,79 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// 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);
+ }
+ }
+ }
+}
diff --git a/osu.Game/Screens/Multi/Components/RoomInspector.cs b/osu.Game/Screens/Multi/Components/RoomInspector.cs
index 3bd054b042..22bca1efc7 100644
--- a/osu.Game/Screens/Multi/Components/RoomInspector.cs
+++ b/osu.Game/Screens/Multi/Components/RoomInspector.cs
@@ -1,6 +1,7 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
@@ -10,7 +11,6 @@ using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes;
-using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics;
@@ -34,13 +34,15 @@ namespace osu.Game.Screens.Multi.Components
private readonly Bindable typeBind = new Bindable();
private readonly Bindable beatmapBind = new Bindable();
private readonly Bindable maxParticipantsBind = new Bindable();
- private readonly Bindable participantsBind = new Bindable();
+ private readonly Bindable> participantsBind = new Bindable>();
private OsuColour colours;
private Box statusStrip;
- private Container coverContainer;
- private FillFlowContainer topFlow, participantsFlow, participantNumbersFlow, infoPanelFlow;
+ private UpdateableBeatmapSetCover cover;
+ private ParticipantCount participantCount;
+ private FillFlowContainer topFlow, participantsFlow;
private OsuSpriteText name, status;
+ private BeatmapTypeInfo beatmapTypeInfo;
private ScrollContainer participantsScroll;
private ParticipantInfo participantInfo;
@@ -77,13 +79,10 @@ namespace osu.Game.Screens.Multi.Components
}
[BackgroundDependencyLoader]
- private void load(OsuColour colours, LocalisationEngine localisation)
+ private void load(OsuColour colours)
{
this.colours = colours;
- ModeTypeInfo modeTypeInfo;
- OsuSpriteText participants, participantsSlash, maxParticipants, beatmapTitle, beatmapDash, beatmapArtist, beatmapAuthor;
-
Children = new Drawable[]
{
new Box
@@ -105,21 +104,9 @@ namespace osu.Game.Screens.Multi.Components
Masking = true,
Children = new Drawable[]
{
- new Container
+ cover = new UpdateableBeatmapSetCover
{
RelativeSizeAxes = Axes.Both,
- Children = new Drawable[]
- {
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = Color4.Black,
- },
- coverContainer = new Container
- {
- RelativeSizeAxes = Axes.Both,
- },
- },
},
new Box
{
@@ -132,32 +119,10 @@ namespace osu.Game.Screens.Multi.Components
Padding = new MarginPadding(20),
Children = new Drawable[]
{
- participantNumbersFlow = new FillFlowContainer
+ participantCount = new ParticipantCount
{
Anchor = 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
{
@@ -200,54 +165,7 @@ namespace osu.Game.Screens.Multi.Components
TextSize = 14,
Font = @"Exo2.0-Bold",
},
- infoPanelFlow = new FillFlowContainer
- {
- 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,
- },
- },
- },
- },
- },
+ beatmapTypeInfo = new BeatmapTypeInfo(),
},
},
},
@@ -285,61 +203,19 @@ namespace osu.Game.Screens.Multi.Components
nameBind.ValueChanged += n => name.Text = n;
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;
beatmapBind.ValueChanged += b =>
{
- modeTypeInfo.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();
- }
+ cover.BeatmapSet = b?.BeatmapSet;
+ beatmapTypeInfo.Beatmap = b;
};
participantsBind.ValueChanged += p =>
{
- participants.Text = p.Length.ToString();
+ participantCount.Count = p.Count();
participantInfo.Participants = p;
participantsFlow.ChildrenEnumerable = p.Select(u => new UserTile(u));
};
@@ -367,10 +243,10 @@ namespace osu.Game.Screens.Multi.Components
{
if (Room == null)
{
- coverContainer.FadeOut(transition_duration);
+ cover.BeatmapSet = null;
participantsFlow.FadeOut(transition_duration);
- participantNumbersFlow.FadeOut(transition_duration);
- infoPanelFlow.FadeOut(transition_duration);
+ participantCount.FadeOut(transition_duration);
+ beatmapTypeInfo.FadeOut(transition_duration);
name.FadeOut(transition_duration);
participantInfo.FadeOut(transition_duration);
@@ -379,8 +255,8 @@ namespace osu.Game.Screens.Multi.Components
else
{
participantsFlow.FadeIn(transition_duration);
- participantNumbersFlow.FadeIn(transition_duration);
- infoPanelFlow.FadeIn(transition_duration);
+ participantCount.FadeIn(transition_duration);
+ beatmapTypeInfo.FadeIn(transition_duration);
name.FadeIn(transition_duration);
participantInfo.FadeIn(transition_duration);
diff --git a/osu.Game/Screens/Multi/Header.cs b/osu.Game/Screens/Multi/Header.cs
index de71b20007..46610a36d8 100644
--- a/osu.Game/Screens/Multi/Header.cs
+++ b/osu.Game/Screens/Multi/Header.cs
@@ -20,7 +20,7 @@ namespace osu.Game.Screens.Multi
{
public const float HEIGHT = 121;
- private readonly OsuSpriteText screenTitle;
+ private readonly OsuSpriteText screenType;
private readonly HeaderBreadcrumbControl breadcrumbs;
public Header(Screen initialScreen)
@@ -67,7 +67,7 @@ namespace osu.Game.Screens.Multi
Text = "multiplayer ",
TextSize = 25,
},
- screenTitle = new OsuSpriteText
+ screenType = new OsuSpriteText
{
TextSize = 25,
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();
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
- screenTitle.Colour = colours.Yellow;
+ screenType.Colour = colours.Yellow;
breadcrumbs.StripColour = colours.Green;
}
diff --git a/osu.Game/Screens/Multi/Screens/Lounge/Lounge.cs b/osu.Game/Screens/Multi/Screens/Lounge/Lounge.cs
index 60ffe2c0b9..016babcaa5 100644
--- a/osu.Game/Screens/Multi/Screens/Lounge/Lounge.cs
+++ b/osu.Game/Screens/Multi/Screens/Lounge/Lounge.cs
@@ -25,7 +25,7 @@ namespace osu.Game.Screens.Multi.Screens.Lounge
protected readonly FillFlowContainer RoomsContainer;
protected readonly RoomInspector Inspector;
- public override string Title => "lounge";
+ public override string Title => "Lounge";
protected override Container TransitionContent => content;
diff --git a/osu.Game/Screens/Multi/Screens/MultiplayerScreen.cs b/osu.Game/Screens/Multi/Screens/MultiplayerScreen.cs
index 191fe66037..fa9b40684c 100644
--- a/osu.Game/Screens/Multi/Screens/MultiplayerScreen.cs
+++ b/osu.Game/Screens/Multi/Screens/MultiplayerScreen.cs
@@ -15,6 +15,11 @@ namespace osu.Game.Screens.Multi.Screens
protected virtual Container TransitionContent => Content;
+ ///
+ /// The type to display in the title of the .
+ ///
+ public virtual string Type => Title;
+
protected override void OnEntering(Screen last)
{
base.OnEntering(last);
diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs
index 0bff75f53b..8a36645145 100644
--- a/osu.Game/Screens/OsuScreen.cs
+++ b/osu.Game/Screens/OsuScreen.cs
@@ -248,7 +248,10 @@ namespace osu.Game.Screens
private void applyArrivingDefaults(bool isResuming)
{
- logo.AppendAnimatingAction(() => LogoArriving(logo, isResuming), true);
+ logo.AppendAnimatingAction(() =>
+ {
+ if (IsCurrentScreen) LogoArriving(logo, isResuming);
+ }, true);
if (backgroundParallaxContainer != null)
backgroundParallaxContainer.ParallaxAmount = ParallaxContainer.DEFAULT_PARALLAX_AMOUNT * BackgroundParallaxAmount;
diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs
index 9c8961498a..2d5bc889c3 100644
--- a/osu.Game/Screens/Play/PlayerLoader.cs
+++ b/osu.Game/Screens/Play/PlayerLoader.cs
@@ -209,8 +209,11 @@ namespace osu.Game.Screens.Play
{
base.Dispose(isDisposing);
- // if the player never got pushed, we should explicitly dispose it.
- loadTask?.ContinueWith(_ => player.Dispose());
+ if (isDisposing)
+ {
+ // if the player never got pushed, we should explicitly dispose it.
+ loadTask?.ContinueWith(_ => player.Dispose());
+ }
}
private class BeatmapMetadataDisplay : Container
diff --git a/osu.Game/Screens/Play/SongProgressInfo.cs b/osu.Game/Screens/Play/SongProgressInfo.cs
index b79c212ade..3156a646db 100644
--- a/osu.Game/Screens/Play/SongProgressInfo.cs
+++ b/osu.Game/Screens/Play/SongProgressInfo.cs
@@ -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}";
}
}
diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs
index f39952dc31..c5996327b9 100644
--- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs
+++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs
@@ -51,7 +51,7 @@ namespace osu.Game.Screens.Select.Carousel
if (songSelect != null)
{
- startRequested = songSelect.FinaliseSelection;
+ startRequested = b => songSelect.FinaliseSelection(b);
editRequested = songSelect.Edit;
}
diff --git a/osu.Game/Screens/Select/EditSongSelect.cs b/osu.Game/Screens/Select/EditSongSelect.cs
index bca009e2c1..e1d71fdd05 100644
--- a/osu.Game/Screens/Select/EditSongSelect.cs
+++ b/osu.Game/Screens/Select/EditSongSelect.cs
@@ -7,7 +7,7 @@ namespace osu.Game.Screens.Select
{
protected override bool ShowFooter => false;
- protected override bool OnSelectionFinalised()
+ protected override bool OnStart()
{
Exit();
return true;
diff --git a/osu.Game/Screens/Select/MatchSongSelect.cs b/osu.Game/Screens/Select/MatchSongSelect.cs
index 3ffac591f3..a0c96d0cee 100644
--- a/osu.Game/Screens/Select/MatchSongSelect.cs
+++ b/osu.Game/Screens/Select/MatchSongSelect.cs
@@ -5,7 +5,7 @@ namespace osu.Game.Screens.Select
{
public class MatchSongSelect : SongSelect
{
- protected override bool OnSelectionFinalised()
+ protected override bool OnStart()
{
Schedule(() =>
{
diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs
index 7992930c45..8ce40fcfa0 100644
--- a/osu.Game/Screens/Select/PlaySongSelect.cs
+++ b/osu.Game/Screens/Select/PlaySongSelect.cs
@@ -137,7 +137,7 @@ namespace osu.Game.Screens.Select
return false;
}
- protected override bool OnSelectionFinalised()
+ protected override bool OnStart()
{
if (player != null) return false;
diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs
index 4ffa9e2a90..e1271aebc4 100644
--- a/osu.Game/Screens/Select/SongSelect.cs
+++ b/osu.Game/Screens/Select/SongSelect.cs
@@ -2,7 +2,6 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
-using System.Threading;
using OpenTK;
using OpenTK.Input;
using osu.Framework.Allocation;
@@ -63,8 +62,6 @@ namespace osu.Game.Screens.Select
private SampleChannel sampleChangeDifficulty;
private SampleChannel sampleChangeBeatmap;
- private CancellationTokenSource initialAddSetsTask;
-
private DependencyContainer dependencies;
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(parent);
@@ -207,9 +204,7 @@ namespace osu.Game.Screens.Select
sampleChangeDifficulty = audio.Sample.Get(@"SongSelect/select-difficulty");
sampleChangeBeatmap = audio.Sample.Get(@"SongSelect/select-expand");
- initialAddSetsTask = new CancellationTokenSource();
-
- Carousel.BeatmapSets = this.beatmaps.GetAllUsableBeatmapSets();
+ Carousel.BeatmapSets = this.beatmaps.GetAllUsableBeatmapSetsEnumerable();
Beatmap.DisabledChanged += disabled => Carousel.AllowSelection = !disabled;
Beatmap.TriggerChange();
@@ -227,7 +222,8 @@ namespace osu.Game.Screens.Select
/// Call to make a selection and perform the default action for this SongSelect.
///
/// An optional beatmap to override the current carousel selection.
- public void FinaliseSelection(BeatmapInfo beatmap = null)
+ /// Whether to trigger .
+ public void FinaliseSelection(BeatmapInfo beatmap = null, bool performStartAction = true)
{
// if we have a pending filter operation, we want to run it now.
// it could change selection (ie. if the ruleset has been changed).
@@ -243,14 +239,15 @@ namespace osu.Game.Screens.Select
selectionChangedDebounce = null;
}
- OnSelectionFinalised();
+ if (performStartAction)
+ OnStart();
}
///
/// Called when a selection is made.
///
/// If a resultant action occurred that takes the user away from SongSelect.
- protected abstract bool OnSelectionFinalised();
+ protected abstract bool OnStart();
private ScheduledDelegate selectionChangedDebounce;
@@ -395,7 +392,7 @@ namespace osu.Game.Screens.Select
protected override bool OnExiting(Screen next)
{
- FinaliseSelection();
+ FinaliseSelection(performStartAction: false);
beatmapInfoWedge.State = Visibility.Hidden;
@@ -417,8 +414,6 @@ namespace osu.Game.Screens.Select
beatmaps.BeatmapHidden -= onBeatmapHidden;
beatmaps.BeatmapRestored -= onBeatmapRestored;
}
-
- initialAddSetsTask?.Cancel();
}
///
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 1a75f1979a..afb656a260 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -17,7 +17,7 @@
-
+