diff --git a/Directory.Build.props b/Directory.Build.props
index 9ec442aafa..2e1873a9ed 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -18,7 +18,7 @@
-
+
$(MSBuildThisFileDirectory)CodeAnalysis\osu.ruleset
@@ -28,9 +28,17 @@
$(NoWarn);CS1591
-
- $(NoWarn);NU1701
+
+ $(NoWarn);NU1701;CA9998
false
@@ -40,7 +48,7 @@
https://github.com/ppy/osu
Automated release.
ppy Pty Ltd
- Copyright (c) 2020 ppy Pty Ltd
+ Copyright (c) 2021 ppy Pty Ltd
osu game
-
\ No newline at end of file
+
diff --git a/LICENCE b/LICENCE
index 2435c23545..b5962ad3b2 100644
--- a/LICENCE
+++ b/LICENCE
@@ -1,4 +1,4 @@
-Copyright (c) 2020 ppy Pty Ltd .
+Copyright (c) 2021 ppy Pty Ltd .
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/osu.Android.props b/osu.Android.props
index 611f0d05f4..492c88c7e4 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -52,6 +52,6 @@
-
+
diff --git a/osu.Desktop/osu.nuspec b/osu.Desktop/osu.nuspec
index 2fc6009183..fa182f8e70 100644
--- a/osu.Desktop/osu.nuspec
+++ b/osu.Desktop/osu.nuspec
@@ -11,7 +11,7 @@
false
A free-to-win rhythm game. Rhythm is just a *click* away!
testing
- Copyright (c) 2020 ppy Pty Ltd
+ Copyright (c) 2021 ppy Pty Ltd
en-AU
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs
index acdd0a420c..438d17dbc5 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs
@@ -59,8 +59,8 @@ namespace osu.Game.Rulesets.Catch.Mods
{
base.ApplySettings(difficulty);
- difficulty.CircleSize = CircleSize.Value;
- difficulty.ApproachRate = ApproachRate.Value;
+ ApplySetting(CircleSize, cs => difficulty.CircleSize = cs);
+ ApplySetting(ApproachRate, ar => difficulty.ApproachRate = ar);
}
}
}
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs
index 49c1fe8540..db8546c71b 100644
--- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs
@@ -1,13 +1,17 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Framework.Utils;
+using osu.Game.Beatmaps;
using osu.Game.Graphics.Containers;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Mods;
+using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
namespace osu.Game.Rulesets.Osu.Tests.Mods
@@ -18,8 +22,23 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
public void TestNoAdjustment() => CreateModTest(new ModTestData
{
Mod = new OsuModDifficultyAdjust(),
+ Beatmap = new Beatmap
+ {
+ BeatmapInfo = new BeatmapInfo
+ {
+ BaseDifficulty = new BeatmapDifficulty
+ {
+ CircleSize = 8
+ }
+ },
+ HitObjects = new List
+ {
+ new HitCircle { StartTime = 1000 },
+ new HitCircle { StartTime = 2000 }
+ }
+ },
Autoplay = true,
- PassCondition = checkSomeHit
+ PassCondition = () => checkSomeHit() && checkObjectsScale(0.29f)
});
[Test]
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs
index ff995e38ce..a638234dbd 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs
@@ -59,8 +59,8 @@ namespace osu.Game.Rulesets.Osu.Mods
{
base.ApplySettings(difficulty);
- difficulty.CircleSize = CircleSize.Value;
- difficulty.ApproachRate = ApproachRate.Value;
+ ApplySetting(CircleSize, cs => difficulty.CircleSize = cs);
+ ApplySetting(ApproachRate, ar => difficulty.ApproachRate = ar);
}
}
}
diff --git a/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs b/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs
index 90a487c0ac..b27c257795 100644
--- a/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs
+++ b/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs
@@ -246,5 +246,32 @@ namespace osu.Game.Tests.NonVisual
Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(0));
Assert.That(cpi.AllControlPoints.Count, Is.EqualTo(0));
}
+
+ [Test]
+ public void TestCreateCopyIsDeepClone()
+ {
+ var cpi = new ControlPointInfo();
+
+ cpi.Add(1000, new TimingControlPoint { BeatLength = 500 });
+
+ var cpiCopy = cpi.CreateCopy();
+
+ cpiCopy.Add(2000, new TimingControlPoint { BeatLength = 500 });
+
+ Assert.That(cpi.Groups.Count, Is.EqualTo(1));
+ Assert.That(cpiCopy.Groups.Count, Is.EqualTo(2));
+
+ Assert.That(cpi.TimingPoints.Count, Is.EqualTo(1));
+ Assert.That(cpiCopy.TimingPoints.Count, Is.EqualTo(2));
+
+ Assert.That(cpi.TimingPoints[0], Is.Not.SameAs(cpiCopy.TimingPoints[0]));
+ Assert.That(cpi.TimingPoints[0].BeatLengthBindable, Is.Not.SameAs(cpiCopy.TimingPoints[0].BeatLengthBindable));
+
+ Assert.That(cpi.TimingPoints[0].BeatLength, Is.EqualTo(cpiCopy.TimingPoints[0].BeatLength));
+
+ cpi.TimingPoints[0].BeatLength = 800;
+
+ Assert.That(cpi.TimingPoints[0].BeatLength, Is.Not.EqualTo(cpiCopy.TimingPoints[0].BeatLength));
+ }
}
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneCreateMultiplayerMatchButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneCreateMultiplayerMatchButton.cs
new file mode 100644
index 0000000000..2f0398c6ef
--- /dev/null
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneCreateMultiplayerMatchButton.cs
@@ -0,0 +1,50 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using NUnit.Framework;
+using osu.Framework.Graphics;
+using osu.Game.Screens.OnlinePlay.Multiplayer;
+
+namespace osu.Game.Tests.Visual.Multiplayer
+{
+ public class TestSceneCreateMultiplayerMatchButton : MultiplayerTestScene
+ {
+ private CreateMultiplayerMatchButton button;
+
+ public override void SetUpSteps()
+ {
+ base.SetUpSteps();
+ AddStep("create button", () => Child = button = new CreateMultiplayerMatchButton
+ {
+ Width = 200,
+ Height = 100,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre
+ });
+ }
+
+ [Test]
+ public void TestButtonEnableStateChanges()
+ {
+ IDisposable joiningRoomOperation = null;
+
+ assertButtonEnableState(true);
+
+ AddStep("begin joining room", () => joiningRoomOperation = OngoingOperationTracker.BeginOperation());
+ assertButtonEnableState(false);
+
+ AddStep("end joining room", () => joiningRoomOperation.Dispose());
+ assertButtonEnableState(true);
+
+ AddStep("disconnect client", () => Client.Disconnect());
+ assertButtonEnableState(false);
+
+ AddStep("re-connect client", () => Client.Connect());
+ assertButtonEnableState(true);
+ }
+
+ private void assertButtonEnableState(bool enabled)
+ => AddAssert($"button {(enabled ? "enabled" : "disabled")}", () => button.Enabled.Value == enabled);
+ }
+}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs
index 8869718fd1..2344ebea0e 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs
@@ -3,10 +3,12 @@
using System.Linq;
using NUnit.Framework;
+using osu.Framework.Allocation;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets.Osu;
+using osu.Game.Screens.OnlinePlay;
using osu.Game.Screens.OnlinePlay.Multiplayer;
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
using osu.Game.Tests.Beatmaps;
@@ -18,6 +20,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
private MultiplayerMatchSubScreen screen;
+ [Cached]
+ private OngoingOperationTracker ongoingOperationTracker = new OngoingOperationTracker();
+
public TestSceneMultiplayerMatchSubScreen()
: base(false)
{
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs
index 03ba73d35b..878776bf51 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
@@ -30,6 +31,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
private BeatmapManager beatmaps;
private RulesetStore rulesets;
+ private IDisposable readyClickOperation;
+
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
{
@@ -56,6 +59,19 @@ namespace osu.Game.Tests.Visual.Multiplayer
Beatmap = { Value = Beatmap.Value.BeatmapInfo },
Ruleset = { Value = Beatmap.Value.BeatmapInfo.Ruleset }
}
+ },
+ OnReadyClick = async () =>
+ {
+ readyClickOperation = OngoingOperationTracker.BeginOperation();
+
+ if (Client.IsHost && Client.LocalUser?.State == MultiplayerUserState.Ready)
+ {
+ await Client.StartMatch();
+ return;
+ }
+
+ await Client.ToggleReady();
+ readyClickOperation.Dispose();
}
};
});
@@ -108,8 +124,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
addClickButtonStep();
AddAssert("user is ready", () => Client.Room?.Users[0].State == MultiplayerUserState.Ready);
- addClickButtonStep();
- AddAssert("match started", () => Client.Room?.Users[0].State == MultiplayerUserState.WaitingForLoad);
+ verifyGameplayStartFlow();
}
[Test]
@@ -124,8 +139,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
addClickButtonStep();
AddStep("make user host", () => Client.TransferHost(Client.Room?.Users[0].UserID ?? 0));
- addClickButtonStep();
- AddAssert("match started", () => Client.Room?.Users[0].State == MultiplayerUserState.WaitingForLoad);
+ verifyGameplayStartFlow();
}
[Test]
@@ -179,5 +193,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
InputManager.MoveMouseTo(button);
InputManager.Click(MouseButton.Left);
});
+
+ private void verifyGameplayStartFlow()
+ {
+ addClickButtonStep();
+ AddAssert("user waiting for load", () => Client.Room?.Users[0].State == MultiplayerUserState.WaitingForLoad);
+ AddAssert("ready button disabled", () => !button.ChildrenOfType().Single().Enabled.Value);
+
+ AddStep("transitioned to gameplay", () => readyClickOperation.Dispose());
+ AddAssert("ready button enabled", () => button.ChildrenOfType().Single().Enabled.Value);
+ }
}
}
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs
index 3d3517ada4..40b2f66d74 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs
@@ -11,6 +11,7 @@ using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Screens.Select.Details;
using osuTK.Graphics;
@@ -141,16 +142,12 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("select changed Difficulty Adjust mod", () =>
{
var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance();
- var difficultyAdjustMod = ruleset.GetAllMods().OfType().Single();
+ var difficultyAdjustMod = ruleset.GetAllMods().OfType().Single();
var originalDifficulty = advancedStats.Beatmap.BaseDifficulty;
- var adjustedDifficulty = new BeatmapDifficulty
- {
- CircleSize = originalDifficulty.CircleSize,
- DrainRate = originalDifficulty.DrainRate - 0.5f,
- OverallDifficulty = originalDifficulty.OverallDifficulty,
- ApproachRate = originalDifficulty.ApproachRate + 2.2f,
- };
- difficultyAdjustMod.ReadFromDifficulty(adjustedDifficulty);
+
+ difficultyAdjustMod.ReadFromDifficulty(originalDifficulty);
+ difficultyAdjustMod.DrainRate.Value = originalDifficulty.DrainRate - 0.5f;
+ difficultyAdjustMod.ApproachRate.Value = originalDifficulty.ApproachRate + 2.2f;
SelectedMods.Value = new[] { difficultyAdjustMod };
});
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs
index 6f083f4ab6..0d0acbb8f4 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs
@@ -131,6 +131,18 @@ namespace osu.Game.Tests.Visual.UserInterface
AddAssert("ensure mods not selected", () => modDisplay.Current.Value.Count == 0);
}
+ [Test]
+ public void TestExternallySetCustomizedMod()
+ {
+ AddStep("set customized mod externally", () => SelectedMods.Value = new[] { new OsuModDoubleTime { SpeedChange = { Value = 1.01 } } });
+
+ AddAssert("ensure button is selected and customized accordingly", () =>
+ {
+ var button = modSelect.GetModButton(SelectedMods.Value.Single());
+ return ((OsuModDoubleTime)button.SelectedMod).SpeedChange.Value == 1.01;
+ });
+ }
+
private void testSingleMod(Mod mod)
{
selectNext(mod);
diff --git a/osu.Game.Tournament/JsonPointConverter.cs b/osu.Game.Tournament/JsonPointConverter.cs
new file mode 100644
index 0000000000..9c82f8ac06
--- /dev/null
+++ b/osu.Game.Tournament/JsonPointConverter.cs
@@ -0,0 +1,65 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Diagnostics;
+using System.Drawing;
+using Newtonsoft.Json;
+
+namespace osu.Game.Tournament
+{
+ ///
+ /// We made a change from using SixLabors.ImageSharp.Point to System.Drawing.Point at some stage.
+ /// This handles converting to a standardised format on json serialize/deserialize operations.
+ ///
+ internal class JsonPointConverter : JsonConverter
+ {
+ public override void WriteJson(JsonWriter writer, Point value, JsonSerializer serializer)
+ {
+ // use the format of LaborSharp's Point since it is nicer.
+ serializer.Serialize(writer, new { value.X, value.Y });
+ }
+
+ public override Point ReadJson(JsonReader reader, Type objectType, Point existingValue, bool hasExistingValue, JsonSerializer serializer)
+ {
+ if (reader.TokenType != JsonToken.StartObject)
+ {
+ // if there's no object present then this is using string representation (System.Drawing.Point serializes to "x,y")
+ string str = (string)reader.Value;
+
+ Debug.Assert(str != null);
+
+ return new PointConverter().ConvertFromString(str) as Point? ?? new Point();
+ }
+
+ var point = new Point();
+
+ while (reader.Read())
+ {
+ if (reader.TokenType == JsonToken.EndObject) break;
+
+ if (reader.TokenType == JsonToken.PropertyName)
+ {
+ var name = reader.Value?.ToString();
+ int? val = reader.ReadAsInt32();
+
+ if (val == null)
+ continue;
+
+ switch (name)
+ {
+ case "X":
+ point.X = val.Value;
+ break;
+
+ case "Y":
+ point.Y = val.Value;
+ break;
+ }
+ }
+ }
+
+ return point;
+ }
+ }
+}
diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs
index dbda6aa023..bc36f27e5b 100644
--- a/osu.Game.Tournament/TournamentGameBase.cs
+++ b/osu.Game.Tournament/TournamentGameBase.cs
@@ -8,12 +8,12 @@ using Newtonsoft.Json;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Textures;
using osu.Framework.Input;
-using osu.Framework.Platform;
using osu.Framework.IO.Stores;
+using osu.Framework.Platform;
using osu.Game.Beatmaps;
using osu.Game.Online.API.Requests;
-using osu.Game.Tournament.IPC;
using osu.Game.Tournament.IO;
+using osu.Game.Tournament.IPC;
using osu.Game.Tournament.Models;
using osu.Game.Users;
using osuTK.Input;
@@ -60,7 +60,7 @@ namespace osu.Game.Tournament
{
using (Stream stream = storage.GetStream(bracket_filename, FileAccess.Read, FileMode.Open))
using (var sr = new StreamReader(stream))
- ladder = JsonConvert.DeserializeObject(sr.ReadToEnd());
+ ladder = JsonConvert.DeserializeObject(sr.ReadToEnd(), new JsonPointConverter());
}
ladder ??= new LadderInfo();
@@ -251,6 +251,7 @@ namespace osu.Game.Tournament
Formatting = Formatting.Indented,
NullValueHandling = NullValueHandling.Ignore,
DefaultValueHandling = DefaultValueHandling.Ignore,
+ Converters = new JsonConverter[] { new JsonPointConverter() }
}));
}
}
diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs
index 5435e86dfd..be2006e67a 100644
--- a/osu.Game/Beatmaps/Beatmap.cs
+++ b/osu.Game/Beatmaps/Beatmap.cs
@@ -50,7 +50,15 @@ namespace osu.Game.Beatmaps
IBeatmap IBeatmap.Clone() => Clone();
- public Beatmap Clone() => (Beatmap)MemberwiseClone();
+ public Beatmap Clone()
+ {
+ var clone = (Beatmap)MemberwiseClone();
+
+ clone.ControlPointInfo = ControlPointInfo.CreateCopy();
+ // todo: deep clone other elements as required.
+
+ return clone;
+ }
}
public class Beatmap : Beatmap
diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs
index c6649f6af1..e8dc623ddb 100644
--- a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs
@@ -28,5 +28,21 @@ namespace osu.Game.Beatmaps.ControlPoints
/// An existing control point to compare with.
/// Whether this is redundant when placed alongside .
public abstract bool IsRedundant(ControlPoint existing);
+
+ ///
+ /// Create an unbound copy of this control point.
+ ///
+ public ControlPoint CreateCopy()
+ {
+ var copy = (ControlPoint)Activator.CreateInstance(GetType());
+
+ copy.CopyFrom(this);
+
+ return copy;
+ }
+
+ public virtual void CopyFrom(ControlPoint other)
+ {
+ }
}
}
diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
index b843aad950..e8a91e4001 100644
--- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
+++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
@@ -297,5 +297,15 @@ namespace osu.Game.Beatmaps.ControlPoints
break;
}
}
+
+ public ControlPointInfo CreateCopy()
+ {
+ var controlPointInfo = new ControlPointInfo();
+
+ foreach (var point in AllControlPoints)
+ controlPointInfo.Add(point.Time, point.CreateCopy());
+
+ return controlPointInfo;
+ }
}
}
diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs
index 283bf76572..0bc5605051 100644
--- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs
@@ -39,5 +39,12 @@ namespace osu.Game.Beatmaps.ControlPoints
public override bool IsRedundant(ControlPoint existing)
=> existing is DifficultyControlPoint existingDifficulty
&& SpeedMultiplier == existingDifficulty.SpeedMultiplier;
+
+ public override void CopyFrom(ControlPoint other)
+ {
+ SpeedMultiplier = ((DifficultyControlPoint)other).SpeedMultiplier;
+
+ base.CopyFrom(other);
+ }
}
}
diff --git a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs
index ea28fca170..79bc88e773 100644
--- a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs
@@ -50,5 +50,13 @@ namespace osu.Game.Beatmaps.ControlPoints
&& existing is EffectControlPoint existingEffect
&& KiaiMode == existingEffect.KiaiMode
&& OmitFirstBarLine == existingEffect.OmitFirstBarLine;
+
+ public override void CopyFrom(ControlPoint other)
+ {
+ KiaiMode = ((EffectControlPoint)other).KiaiMode;
+ OmitFirstBarLine = ((EffectControlPoint)other).OmitFirstBarLine;
+
+ base.CopyFrom(other);
+ }
}
}
diff --git a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs
index fd0b496335..4aa6a3d6e9 100644
--- a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs
@@ -72,5 +72,13 @@ namespace osu.Game.Beatmaps.ControlPoints
=> existing is SampleControlPoint existingSample
&& SampleBank == existingSample.SampleBank
&& SampleVolume == existingSample.SampleVolume;
+
+ public override void CopyFrom(ControlPoint other)
+ {
+ SampleVolume = ((SampleControlPoint)other).SampleVolume;
+ SampleBank = ((SampleControlPoint)other).SampleBank;
+
+ base.CopyFrom(other);
+ }
}
}
diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs
index d9378bca4a..580642f593 100644
--- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs
@@ -69,5 +69,13 @@ namespace osu.Game.Beatmaps.ControlPoints
// Timing points are never redundant as they can change the time signature.
public override bool IsRedundant(ControlPoint existing) => false;
+
+ public override void CopyFrom(ControlPoint other)
+ {
+ TimeSignature = ((TimingControlPoint)other).TimeSignature;
+ BeatLength = ((TimingControlPoint)other).BeatLength;
+
+ base.CopyFrom(other);
+ }
}
}
diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
index c9d139bdd0..069a25b83d 100644
--- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
@@ -164,13 +164,24 @@ namespace osu.Game.Beatmaps.Formats
/// Legacy BPM multiplier that introduces floating-point errors for rulesets that depend on it.
/// DO NOT USE THIS UNLESS 100% SURE.
///
- public readonly float BpmMultiplier;
+ public float BpmMultiplier { get; private set; }
public LegacyDifficultyControlPoint(double beatLength)
+ : this()
+ {
+ BpmMultiplier = beatLength < 0 ? Math.Clamp((float)-beatLength, 10, 10000) / 100f : 1;
+ }
+
+ public LegacyDifficultyControlPoint()
{
SpeedMultiplierBindable.Precision = double.Epsilon;
+ }
- BpmMultiplier = beatLength < 0 ? Math.Clamp((float)-beatLength, 10, 10000) / 100f : 1;
+ public override void CopyFrom(ControlPoint other)
+ {
+ base.CopyFrom(other);
+
+ BpmMultiplier = ((LegacyDifficultyControlPoint)other).BpmMultiplier;
}
}
@@ -192,6 +203,13 @@ namespace osu.Game.Beatmaps.Formats
=> base.IsRedundant(existing)
&& existing is LegacySampleControlPoint existingSample
&& CustomSampleBank == existingSample.CustomSampleBank;
+
+ public override void CopyFrom(ControlPoint other)
+ {
+ base.CopyFrom(other);
+
+ CustomSampleBank = ((LegacySampleControlPoint)other).CustomSampleBank;
+ }
}
}
}
diff --git a/osu.Game/Configuration/SessionStatics.cs b/osu.Game/Configuration/SessionStatics.cs
index 03bc434aac..fd401119ff 100644
--- a/osu.Game/Configuration/SessionStatics.cs
+++ b/osu.Game/Configuration/SessionStatics.cs
@@ -1,7 +1,9 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API.Requests.Responses;
+using osu.Game.Overlays;
namespace osu.Game.Configuration
{
@@ -14,6 +16,7 @@ namespace osu.Game.Configuration
{
Set(Static.LoginOverlayDisplayed, false);
Set(Static.MutedAudioNotificationShownOnce, false);
+ Set(Static.LastHoverSoundPlaybackTime, (double?)null);
Set(Static.SeasonalBackgrounds, null);
}
}
@@ -28,5 +31,11 @@ namespace osu.Game.Configuration
/// Value under this lookup can be null if there are no backgrounds available (or API is not reachable).
///
SeasonalBackgrounds,
+
+ ///
+ /// The last playback time in milliseconds of a hover sample (from ).
+ /// Used to debounce hover sounds game-wide to avoid volume saturation, especially in scrolling views with many UI controls like .
+ ///
+ LastHoverSoundPlaybackTime
}
}
diff --git a/osu.Game/Graphics/UserInterface/HoverSounds.cs b/osu.Game/Graphics/UserInterface/HoverSounds.cs
index 40899e7e95..a1d06711db 100644
--- a/osu.Game/Graphics/UserInterface/HoverSounds.cs
+++ b/osu.Game/Graphics/UserInterface/HoverSounds.cs
@@ -5,11 +5,12 @@ using System.ComponentModel;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
+using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events;
-using osu.Framework.Threading;
+using osu.Game.Configuration;
namespace osu.Game.Graphics.UserInterface
{
@@ -22,37 +23,40 @@ namespace osu.Game.Graphics.UserInterface
private SampleChannel sampleHover;
///
- /// Length of debounce for hover sound playback, in milliseconds. Default is 50ms.
+ /// Length of debounce for hover sound playback, in milliseconds.
///
- public double HoverDebounceTime { get; } = 50;
+ public double HoverDebounceTime { get; } = 20;
protected readonly HoverSampleSet SampleSet;
+ private Bindable lastPlaybackTime;
+
public HoverSounds(HoverSampleSet sampleSet = HoverSampleSet.Normal)
{
SampleSet = sampleSet;
RelativeSizeAxes = Axes.Both;
}
- private ScheduledDelegate playDelegate;
+ [BackgroundDependencyLoader]
+ private void load(AudioManager audio, SessionStatics statics)
+ {
+ lastPlaybackTime = statics.GetBindable(Static.LastHoverSoundPlaybackTime);
+
+ sampleHover = audio.Samples.Get($@"UI/generic-hover{SampleSet.GetDescription()}");
+ }
protected override bool OnHover(HoverEvent e)
{
- playDelegate?.Cancel();
+ bool enoughTimePassedSinceLastPlayback = !lastPlaybackTime.Value.HasValue || Time.Current - lastPlaybackTime.Value >= HoverDebounceTime;
- if (HoverDebounceTime <= 0)
+ if (enoughTimePassedSinceLastPlayback)
+ {
sampleHover?.Play();
- else
- playDelegate = Scheduler.AddDelayed(() => sampleHover?.Play(), HoverDebounceTime);
+ lastPlaybackTime.Value = Time.Current;
+ }
return base.OnHover(e);
}
-
- [BackgroundDependencyLoader]
- private void load(AudioManager audio)
- {
- sampleHover = audio.Samples.Get($@"UI/generic-hover{SampleSet.GetDescription()}");
- }
}
public enum HoverSampleSet
diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs
index dc80488d39..34cba09e8c 100644
--- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs
+++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs
@@ -65,6 +65,23 @@ namespace osu.Game.Online.Multiplayer
///
public readonly BindableList CurrentMatchPlayingUserIds = new BindableList();
+ ///
+ /// The corresponding to the local player, if available.
+ ///
+ public MultiplayerRoomUser? LocalUser => Room?.Users.SingleOrDefault(u => u.User?.Id == api.LocalUser.Value.Id);
+
+ ///
+ /// Whether the is the host in .
+ ///
+ public bool IsHost
+ {
+ get
+ {
+ var localUser = LocalUser;
+ return localUser != null && Room?.Host != null && localUser.Equals(Room.Host);
+ }
+ }
+
[Resolved]
private UserLookupCache userLookupCache { get; set; } = null!;
@@ -178,6 +195,32 @@ namespace osu.Game.Online.Multiplayer
});
}
+ ///
+ /// Toggles the 's ready state.
+ ///
+ /// If a toggle of ready state is not valid at this time.
+ public async Task ToggleReady()
+ {
+ var localUser = LocalUser;
+
+ if (localUser == null)
+ return;
+
+ switch (localUser.State)
+ {
+ case MultiplayerUserState.Idle:
+ await ChangeState(MultiplayerUserState.Ready);
+ return;
+
+ case MultiplayerUserState.Ready:
+ await ChangeState(MultiplayerUserState.Idle);
+ return;
+
+ default:
+ throw new InvalidOperationException($"Cannot toggle ready when in {localUser.State}");
+ }
+ }
+
public abstract Task TransferHost(int userId);
public abstract Task ChangeSettings(MultiplayerRoomSettings settings);
diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs
index e574828cd2..ab8efdabcc 100644
--- a/osu.Game/Overlays/Mods/ModButton.cs
+++ b/osu.Game/Overlays/Mods/ModButton.cs
@@ -52,9 +52,10 @@ namespace osu.Game.Overlays.Mods
if (newIndex == selectedIndex) return false;
int direction = newIndex < selectedIndex ? -1 : 1;
+
bool beforeSelected = Selected;
- Mod modBefore = SelectedMod ?? Mods[0];
+ Mod previousSelection = SelectedMod ?? Mods[0];
if (newIndex >= Mods.Length)
newIndex = -1;
@@ -65,40 +66,45 @@ namespace osu.Game.Overlays.Mods
return false;
selectedIndex = newIndex;
- Mod modAfter = SelectedMod ?? Mods[0];
- if (beforeSelected != Selected)
+ Mod newSelection = SelectedMod ?? Mods[0];
+
+ Schedule(() =>
{
- iconsContainer.RotateTo(Selected ? 5f : 0f, 300, Easing.OutElastic);
- iconsContainer.ScaleTo(Selected ? 1.1f : 1f, 300, Easing.OutElastic);
- }
-
- if (modBefore != modAfter)
- {
- const float rotate_angle = 16;
-
- foregroundIcon.RotateTo(rotate_angle * direction, mod_switch_duration, mod_switch_easing);
- backgroundIcon.RotateTo(-rotate_angle * direction, mod_switch_duration, mod_switch_easing);
-
- backgroundIcon.Mod = modAfter;
-
- using (BeginDelayedSequence(mod_switch_duration, true))
+ if (beforeSelected != Selected)
{
- foregroundIcon
- .RotateTo(-rotate_angle * direction)
- .RotateTo(0f, mod_switch_duration, mod_switch_easing);
-
- backgroundIcon
- .RotateTo(rotate_angle * direction)
- .RotateTo(0f, mod_switch_duration, mod_switch_easing);
-
- Schedule(() => displayMod(modAfter));
+ iconsContainer.RotateTo(Selected ? 5f : 0f, 300, Easing.OutElastic);
+ iconsContainer.ScaleTo(Selected ? 1.1f : 1f, 300, Easing.OutElastic);
}
- }
- foregroundIcon.Selected.Value = Selected;
+ if (previousSelection != newSelection)
+ {
+ const float rotate_angle = 16;
+
+ foregroundIcon.RotateTo(rotate_angle * direction, mod_switch_duration, mod_switch_easing);
+ backgroundIcon.RotateTo(-rotate_angle * direction, mod_switch_duration, mod_switch_easing);
+
+ backgroundIcon.Mod = newSelection;
+
+ using (BeginDelayedSequence(mod_switch_duration, true))
+ {
+ foregroundIcon
+ .RotateTo(-rotate_angle * direction)
+ .RotateTo(0f, mod_switch_duration, mod_switch_easing);
+
+ backgroundIcon
+ .RotateTo(rotate_angle * direction)
+ .RotateTo(0f, mod_switch_duration, mod_switch_easing);
+
+ Schedule(() => displayMod(newSelection));
+ }
+ }
+
+ foregroundIcon.Selected.Value = Selected;
+ });
SelectionChanged?.Invoke(SelectedMod);
+
return true;
}
diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs
index 0107f94dcf..573d1e5355 100644
--- a/osu.Game/Overlays/Mods/ModSection.cs
+++ b/osu.Game/Overlays/Mods/ModSection.cs
@@ -127,20 +127,30 @@ namespace osu.Game.Overlays.Mods
}
///
- /// Select one or more mods in this section and deselects all other ones.
+ /// Updates all buttons with the given list of selected mods.
///
- /// The types of s which should be selected.
- public void SelectTypes(IEnumerable modTypes)
+ /// The new list of selected mods to select.
+ public void UpdateSelectedMods(IReadOnlyList newSelectedMods)
{
foreach (var button in buttons)
- {
- int i = Array.FindIndex(button.Mods, m => modTypes.Any(t => t == m.GetType()));
+ updateButtonMods(button, newSelectedMods);
+ }
- if (i >= 0)
- button.SelectAt(i);
- else
- button.Deselect();
+ private void updateButtonMods(ModButton button, IReadOnlyList newSelectedMods)
+ {
+ foreach (var mod in newSelectedMods)
+ {
+ var index = Array.FindIndex(button.Mods, m1 => mod.GetType() == m1.GetType());
+ if (index < 0)
+ continue;
+
+ var buttonMod = button.Mods[index];
+ buttonMod.CopyFrom(mod);
+ button.SelectAt(index);
+ return;
}
+
+ button.Deselect();
}
protected ModSection()
diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs
index 34f5c70adb..0c8245bebe 100644
--- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs
+++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs
@@ -249,7 +249,7 @@ namespace osu.Game.Overlays.Mods
{
Width = 180,
Text = "Deselect All",
- Action = DeselectAll,
+ Action = deselectAll,
Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft,
},
@@ -318,7 +318,7 @@ namespace osu.Game.Overlays.Mods
sampleOff = audio.Samples.Get(@"UI/check-off");
}
- public void DeselectAll()
+ private void deselectAll()
{
foreach (var section in ModSectionsContainer.Children)
section.DeselectAll();
@@ -331,7 +331,7 @@ namespace osu.Game.Overlays.Mods
///
/// The types of s which should be deselected.
/// Set to true to bypass animations and update selections immediately.
- public void DeselectTypes(Type[] modTypes, bool immediate = false)
+ private void deselectTypes(Type[] modTypes, bool immediate = false)
{
if (modTypes.Length == 0) return;
@@ -409,7 +409,7 @@ namespace osu.Game.Overlays.Mods
private void selectedModsChanged(ValueChangedEvent> mods)
{
foreach (var section in ModSectionsContainer.Children)
- section.SelectTypes(mods.NewValue.Select(m => m.GetType()).ToList());
+ section.UpdateSelectedMods(mods.NewValue);
updateMods();
}
@@ -438,7 +438,7 @@ namespace osu.Game.Overlays.Mods
{
if (State.Value == Visibility.Visible) sampleOn?.Play();
- DeselectTypes(selectedMod.IncompatibleMods, true);
+ deselectTypes(selectedMod.IncompatibleMods, true);
if (selectedMod.RequiresConfiguration) ModSettingsContainer.Show();
}
diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs
index 24d184e531..3a8717e678 100644
--- a/osu.Game/Rulesets/Mods/Mod.cs
+++ b/osu.Game/Rulesets/Mods/Mod.cs
@@ -128,20 +128,29 @@ namespace osu.Game.Rulesets.Mods
///
public virtual Mod CreateCopy()
{
- var copy = (Mod)Activator.CreateInstance(GetType());
+ var result = (Mod)Activator.CreateInstance(GetType());
+ result.CopyFrom(this);
+ return result;
+ }
+
+ ///
+ /// Copies mod setting values from into this instance.
+ ///
+ /// The mod to copy properties from.
+ public void CopyFrom(Mod source)
+ {
+ if (source.GetType() != GetType())
+ throw new ArgumentException($"Expected mod of type {GetType()}, got {source.GetType()}.", nameof(source));
- // Copy bindable values across
foreach (var (_, prop) in this.GetSettingsSourceProperties())
{
- var origBindable = (IBindable)prop.GetValue(this);
- var copyBindable = (IBindable)prop.GetValue(copy);
+ var targetBindable = (IBindable)prop.GetValue(this);
+ var sourceBindable = (IBindable)prop.GetValue(source);
// we only care about changes that have been made away from defaults.
- if (!origBindable.IsDefault)
- copy.CopyAdjustedSetting(copyBindable, origBindable);
+ if (!sourceBindable.IsDefault)
+ CopyAdjustedSetting(targetBindable, sourceBindable);
}
-
- return copy;
}
///
diff --git a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs
index 72a4bb297f..a531e885db 100644
--- a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs
+++ b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs
@@ -116,18 +116,30 @@ namespace osu.Game.Rulesets.Mods
internal override void CopyAdjustedSetting(IBindable target, object source)
{
- userChangedSettings[target] = true;
+ // if the value is non-bindable, it's presumably coming from an external source (like the API) - therefore presume it is not default.
+ // if the value is bindable, defer to the source's IsDefault to be able to tell.
+ userChangedSettings[target] = !(source is IBindable bindableSource) || !bindableSource.IsDefault;
base.CopyAdjustedSetting(target, source);
}
+ ///
+ /// Applies a setting from a configuration bindable using , if it has been changed by the user.
+ ///
+ protected void ApplySetting(BindableNumber setting, Action applyFunc)
+ where T : struct, IComparable, IConvertible, IEquatable
+ {
+ if (userChangedSettings.TryGetValue(setting, out bool userChangedSetting) && userChangedSetting)
+ applyFunc.Invoke(setting.Value);
+ }
+
///
/// Apply all custom settings to the provided beatmap.
///
/// The beatmap to have settings applied.
protected virtual void ApplySettings(BeatmapDifficulty difficulty)
{
- difficulty.DrainRate = DrainRate.Value;
- difficulty.OverallDifficulty = OverallDifficulty.Value;
+ ApplySetting(DrainRate, dr => difficulty.DrainRate = dr);
+ ApplySetting(OverallDifficulty, od => difficulty.OverallDifficulty = od);
}
}
}
diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
index da6da0ea97..e5eaf5db88 100644
--- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
+++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
@@ -750,7 +750,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
if (Result.Type != originalType)
{
Logger.Log($"{GetType().ReadableName()} applied an invalid hit result ({originalType}) when {nameof(HitResult.IgnoreMiss)} or {nameof(HitResult.IgnoreHit)} is expected.\n"
- + $"This has been automatically adjusted to {Result.Type}, and support will be removed from 2020-03-28 onwards.", level: LogLevel.Important);
+ + $"This has been automatically adjusted to {Result.Type}, and support will be removed from 2021-03-28 onwards.", level: LogLevel.Important);
}
}
diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs
index 36b421586e..df569b91c1 100644
--- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs
@@ -10,10 +10,7 @@ using osu.Game.Beatmaps.ControlPoints;
namespace osu.Game.Rulesets.Objects.Legacy
{
- internal abstract class ConvertSlider : ConvertHitObject, IHasPathWithRepeats, IHasLegacyLastTickOffset,
-#pragma warning disable 618
- IHasCurve
-#pragma warning restore 618
+ internal abstract class ConvertSlider : ConvertHitObject, IHasPathWithRepeats, IHasLegacyLastTickOffset
{
///
/// Scoring distance with a speed-adjusted beat length of 1 second.
diff --git a/osu.Game/Rulesets/Objects/Types/IHasCurve.cs b/osu.Game/Rulesets/Objects/Types/IHasCurve.cs
deleted file mode 100644
index 26f50ffa31..0000000000
--- a/osu.Game/Rulesets/Objects/Types/IHasCurve.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System;
-using osuTK;
-
-namespace osu.Game.Rulesets.Objects.Types
-{
- [Obsolete("Use IHasPathWithRepeats instead.")] // can be removed 20201126
- public interface IHasCurve : IHasDistance, IHasRepeats
- {
- ///
- /// The curve.
- ///
- SliderPath Path { get; }
- }
-
-#pragma warning disable 618
- [Obsolete("Use IHasPathWithRepeats instead.")] // can be removed 20201126
- public static class HasCurveExtensions
- {
- ///
- /// Computes the position on the curve relative to how much of the has been completed.
- ///
- /// The curve.
- /// [0, 1] where 0 is the start time of the and 1 is the end time of the .
- /// The position on the curve.
- public static Vector2 CurvePositionAt(this IHasCurve obj, double progress)
- => obj.Path.PositionAt(obj.ProgressAt(progress));
-
- ///
- /// Computes the progress along the curve relative to how much of the has been completed.
- ///
- /// The curve.
- /// [0, 1] where 0 is the start time of the and 1 is the end time of the .
- /// [0, 1] where 0 is the beginning of the curve and 1 is the end of the curve.
- public static double ProgressAt(this IHasCurve obj, double progress)
- {
- double p = progress * obj.SpanCount() % 1;
- if (obj.SpanAt(progress) % 2 == 1)
- p = 1 - p;
- return p;
- }
-
- ///
- /// Determines which span of the curve the progress point is on.
- ///
- /// The curve.
- /// [0, 1] where 0 is the beginning of the curve and 1 is the end of the curve.
- /// [0, SpanCount) where 0 is the first run.
- public static int SpanAt(this IHasCurve obj, double progress)
- => (int)(progress * obj.SpanCount());
- }
-#pragma warning restore 618
-}
diff --git a/osu.Game/Rulesets/Objects/Types/IHasDuration.cs b/osu.Game/Rulesets/Objects/Types/IHasDuration.cs
index b558273650..ca734da5ad 100644
--- a/osu.Game/Rulesets/Objects/Types/IHasDuration.cs
+++ b/osu.Game/Rulesets/Objects/Types/IHasDuration.cs
@@ -6,26 +6,16 @@ namespace osu.Game.Rulesets.Objects.Types
///
/// A HitObject that ends at a different time than its start time.
///
-#pragma warning disable 618
- public interface IHasDuration : IHasEndTime
-#pragma warning restore 618
+ public interface IHasDuration
{
- double IHasEndTime.EndTime
- {
- get => EndTime;
- set => Duration = (Duration - EndTime) + value;
- }
-
- double IHasEndTime.Duration => Duration;
-
///
/// The time at which the HitObject ends.
///
- new double EndTime { get; }
+ double EndTime { get; }
///
/// The duration of the HitObject.
///
- new double Duration { get; set; }
+ double Duration { get; set; }
}
}
diff --git a/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs b/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs
deleted file mode 100644
index c3769c5909..0000000000
--- a/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System;
-using Newtonsoft.Json;
-
-namespace osu.Game.Rulesets.Objects.Types
-{
- ///
- /// A HitObject that ends at a different time than its start time.
- ///
- [Obsolete("Use IHasDuration instead.")] // can be removed 20201126
- public interface IHasEndTime
- {
- ///
- /// The time at which the HitObject ends.
- ///
- [JsonIgnore]
- double EndTime { get; set; }
-
- ///
- /// The duration of the HitObject.
- ///
- double Duration { get; }
- }
-}
diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs
index d422bca087..deabea57ef 100644
--- a/osu.Game/Rulesets/RulesetStore.cs
+++ b/osu.Game/Rulesets/RulesetStore.cs
@@ -100,9 +100,7 @@ namespace osu.Game.Rulesets
foreach (var r in instances.Where(r => !(r is ILegacyRuleset)))
{
- // todo: StartsWith can be changed to Equals on 2020-11-08
- // This is to give users enough time to have their database use new abbreviated info).
- if (existingRulesets.FirstOrDefault(ri => ri.InstantiationInfo.StartsWith(r.RulesetInfo.InstantiationInfo, StringComparison.Ordinal)) == null)
+ if (existingRulesets.FirstOrDefault(ri => ri.InstantiationInfo.Equals(r.RulesetInfo.InstantiationInfo, StringComparison.Ordinal)) == null)
context.RulesetInfo.Add(r.RulesetInfo);
}
diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs
index c297a03dbf..81b1195a40 100644
--- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs
+++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs
@@ -16,6 +16,9 @@ namespace osu.Game.Screens.Edit.Compose
{
public class ComposeScreen : EditorScreenWithTimeline
{
+ [Resolved]
+ private IBindable beatmap { get; set; }
+
private HitObjectComposer composer;
public ComposeScreen()
@@ -59,7 +62,7 @@ namespace osu.Game.Screens.Edit.Compose
{
Debug.Assert(ruleset != null);
- var beatmapSkinProvider = new BeatmapSkinProvidingContainer(Beatmap.Value.Skin);
+ var beatmapSkinProvider = new BeatmapSkinProvidingContainer(beatmap.Value.Skin);
// the beatmapSkinProvider is used as the fallback source here to allow the ruleset-specific skin implementation
// full access to all skin sources.
diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs
index 8c34cb2e08..b7ebf0c0a4 100644
--- a/osu.Game/Screens/Edit/Editor.cs
+++ b/osu.Game/Screens/Edit/Editor.cs
@@ -486,6 +486,8 @@ namespace osu.Game.Screens.Edit
ApplyToBackground(b => b.FadeColour(Color4.White, 500));
resetTrack();
+ Beatmap.Value = beatmapManager.GetWorkingBeatmap(Beatmap.Value.BeatmapInfo);
+
return base.OnExiting(next);
}
diff --git a/osu.Game/Screens/Edit/EditorScreen.cs b/osu.Game/Screens/Edit/EditorScreen.cs
index 4d62a7d3cd..7fbb6a8ca0 100644
--- a/osu.Game/Screens/Edit/EditorScreen.cs
+++ b/osu.Game/Screens/Edit/EditorScreen.cs
@@ -2,10 +2,8 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
-using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Game.Beatmaps;
namespace osu.Game.Screens.Edit
{
@@ -14,9 +12,6 @@ namespace osu.Game.Screens.Edit
///
public abstract class EditorScreen : Container
{
- [Resolved]
- protected IBindable Beatmap { get; private set; }
-
[Resolved]
protected EditorBeatmap EditorBeatmap { get; private set; }
diff --git a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs
index b9457f422a..2d623a200c 100644
--- a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs
+++ b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs
@@ -30,16 +30,16 @@ namespace osu.Game.Screens.Edit
{
}
+ private Container mainContent;
+
+ private LoadingSpinner spinner;
+
[BackgroundDependencyLoader(true)]
private void load([CanBeNull] BindableBeatDivisor beatDivisor)
{
if (beatDivisor != null)
this.beatDivisor.BindTo(beatDivisor);
- Container mainContent;
-
- LoadingSpinner spinner;
-
Children = new Drawable[]
{
mainContent = new Container
@@ -99,6 +99,11 @@ namespace osu.Game.Screens.Edit
}
},
};
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
LoadComponentAsync(CreateMainContent(), content =>
{
diff --git a/osu.Game/Screens/Edit/Setup/DifficultySection.cs b/osu.Game/Screens/Edit/Setup/DifficultySection.cs
index 897ddc6955..36fb0191b0 100644
--- a/osu.Game/Screens/Edit/Setup/DifficultySection.cs
+++ b/osu.Game/Screens/Edit/Setup/DifficultySection.cs
@@ -13,9 +13,6 @@ namespace osu.Game.Screens.Edit.Setup
{
internal class DifficultySection : SetupSection
{
- [Resolved]
- private EditorBeatmap editorBeatmap { get; set; }
-
private LabelledSliderBar circleSizeSlider;
private LabelledSliderBar healthDrainSlider;
private LabelledSliderBar approachRateSlider;
@@ -34,7 +31,7 @@ namespace osu.Game.Screens.Edit.Setup
{
Label = "Object Size",
Description = "The size of all hit objects",
- Current = new BindableFloat(Beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize)
+ Current = new BindableFloat(Beatmap.BeatmapInfo.BaseDifficulty.CircleSize)
{
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
MinValue = 0,
@@ -46,7 +43,7 @@ namespace osu.Game.Screens.Edit.Setup
{
Label = "Health Drain",
Description = "The rate of passive health drain throughout playable time",
- Current = new BindableFloat(Beatmap.Value.BeatmapInfo.BaseDifficulty.DrainRate)
+ Current = new BindableFloat(Beatmap.BeatmapInfo.BaseDifficulty.DrainRate)
{
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
MinValue = 0,
@@ -58,7 +55,7 @@ namespace osu.Game.Screens.Edit.Setup
{
Label = "Approach Rate",
Description = "The speed at which objects are presented to the player",
- Current = new BindableFloat(Beatmap.Value.BeatmapInfo.BaseDifficulty.ApproachRate)
+ Current = new BindableFloat(Beatmap.BeatmapInfo.BaseDifficulty.ApproachRate)
{
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
MinValue = 0,
@@ -70,7 +67,7 @@ namespace osu.Game.Screens.Edit.Setup
{
Label = "Overall Difficulty",
Description = "The harshness of hit windows and difficulty of special objects (ie. spinners)",
- Current = new BindableFloat(Beatmap.Value.BeatmapInfo.BaseDifficulty.OverallDifficulty)
+ Current = new BindableFloat(Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty)
{
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
MinValue = 0,
@@ -88,12 +85,12 @@ namespace osu.Game.Screens.Edit.Setup
{
// for now, update these on commit rather than making BeatmapMetadata bindables.
// after switching database engines we can reconsider if switching to bindables is a good direction.
- Beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize = circleSizeSlider.Current.Value;
- Beatmap.Value.BeatmapInfo.BaseDifficulty.DrainRate = healthDrainSlider.Current.Value;
- Beatmap.Value.BeatmapInfo.BaseDifficulty.ApproachRate = approachRateSlider.Current.Value;
- Beatmap.Value.BeatmapInfo.BaseDifficulty.OverallDifficulty = overallDifficultySlider.Current.Value;
+ Beatmap.BeatmapInfo.BaseDifficulty.CircleSize = circleSizeSlider.Current.Value;
+ Beatmap.BeatmapInfo.BaseDifficulty.DrainRate = healthDrainSlider.Current.Value;
+ Beatmap.BeatmapInfo.BaseDifficulty.ApproachRate = approachRateSlider.Current.Value;
+ Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty = overallDifficultySlider.Current.Value;
- editorBeatmap.UpdateAllHitObjects();
+ Beatmap.UpdateAllHitObjects();
}
}
}
diff --git a/osu.Game/Screens/Edit/Setup/MetadataSection.cs b/osu.Game/Screens/Edit/Setup/MetadataSection.cs
index 4ddee2acc6..e812c042fb 100644
--- a/osu.Game/Screens/Edit/Setup/MetadataSection.cs
+++ b/osu.Game/Screens/Edit/Setup/MetadataSection.cs
@@ -29,25 +29,25 @@ namespace osu.Game.Screens.Edit.Setup
artistTextBox = new LabelledTextBox
{
Label = "Artist",
- Current = { Value = Beatmap.Value.Metadata.Artist },
+ Current = { Value = Beatmap.Metadata.Artist },
TabbableContentContainer = this
},
titleTextBox = new LabelledTextBox
{
Label = "Title",
- Current = { Value = Beatmap.Value.Metadata.Title },
+ Current = { Value = Beatmap.Metadata.Title },
TabbableContentContainer = this
},
creatorTextBox = new LabelledTextBox
{
Label = "Creator",
- Current = { Value = Beatmap.Value.Metadata.AuthorString },
+ Current = { Value = Beatmap.Metadata.AuthorString },
TabbableContentContainer = this
},
difficultyTextBox = new LabelledTextBox
{
Label = "Difficulty Name",
- Current = { Value = Beatmap.Value.BeatmapInfo.Version },
+ Current = { Value = Beatmap.BeatmapInfo.Version },
TabbableContentContainer = this
},
};
@@ -62,10 +62,10 @@ namespace osu.Game.Screens.Edit.Setup
// for now, update these on commit rather than making BeatmapMetadata bindables.
// after switching database engines we can reconsider if switching to bindables is a good direction.
- Beatmap.Value.Metadata.Artist = artistTextBox.Current.Value;
- Beatmap.Value.Metadata.Title = titleTextBox.Current.Value;
- Beatmap.Value.Metadata.AuthorString = creatorTextBox.Current.Value;
- Beatmap.Value.BeatmapInfo.Version = difficultyTextBox.Current.Value;
+ Beatmap.Metadata.Artist = artistTextBox.Current.Value;
+ Beatmap.Metadata.Title = titleTextBox.Current.Value;
+ Beatmap.Metadata.AuthorString = creatorTextBox.Current.Value;
+ Beatmap.BeatmapInfo.Version = difficultyTextBox.Current.Value;
}
}
}
diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs
index 0c957b80af..010d7c2797 100644
--- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs
+++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs
@@ -42,6 +42,9 @@ namespace osu.Game.Screens.Edit.Setup
[Resolved]
private BeatmapManager beatmaps { get; set; }
+ [Resolved]
+ private IBindable working { get; set; }
+
[Resolved(canBeNull: true)]
private Editor editor { get; set; }
@@ -70,7 +73,7 @@ namespace osu.Game.Screens.Edit.Setup
audioTrackTextBox = new FileChooserLabelledTextBox
{
Label = "Audio Track",
- Current = { Value = Beatmap.Value.Metadata.AudioFile ?? "Click to select a track" },
+ Current = { Value = working.Value.Metadata.AudioFile ?? "Click to select a track" },
Target = audioTrackFileChooserContainer,
TabbableContentContainer = this
},
@@ -115,11 +118,11 @@ namespace osu.Game.Screens.Edit.Setup
if (!info.Exists)
return false;
- var set = Beatmap.Value.BeatmapSetInfo;
+ var set = working.Value.BeatmapSetInfo;
// remove the previous background for now.
// in the future we probably want to check if this is being used elsewhere (other difficulties?)
- var oldFile = set.Files.FirstOrDefault(f => f.Filename == Beatmap.Value.Metadata.BackgroundFile);
+ var oldFile = set.Files.FirstOrDefault(f => f.Filename == working.Value.Metadata.BackgroundFile);
using (var stream = info.OpenRead())
{
@@ -129,7 +132,7 @@ namespace osu.Game.Screens.Edit.Setup
beatmaps.AddFile(set, stream, info.Name);
}
- Beatmap.Value.Metadata.BackgroundFile = info.Name;
+ working.Value.Metadata.BackgroundFile = info.Name;
updateBackgroundSprite();
return true;
@@ -148,11 +151,11 @@ namespace osu.Game.Screens.Edit.Setup
if (!info.Exists)
return false;
- var set = Beatmap.Value.BeatmapSetInfo;
+ var set = working.Value.BeatmapSetInfo;
// remove the previous audio track for now.
// in the future we probably want to check if this is being used elsewhere (other difficulties?)
- var oldFile = set.Files.FirstOrDefault(f => f.Filename == Beatmap.Value.Metadata.AudioFile);
+ var oldFile = set.Files.FirstOrDefault(f => f.Filename == working.Value.Metadata.AudioFile);
using (var stream = info.OpenRead())
{
@@ -162,7 +165,7 @@ namespace osu.Game.Screens.Edit.Setup
beatmaps.AddFile(set, stream, info.Name);
}
- Beatmap.Value.Metadata.AudioFile = info.Name;
+ working.Value.Metadata.AudioFile = info.Name;
music.ReloadCurrentTrack();
@@ -178,7 +181,7 @@ namespace osu.Game.Screens.Edit.Setup
private void updateBackgroundSprite()
{
- LoadComponentAsync(new BeatmapBackgroundSprite(Beatmap.Value)
+ LoadComponentAsync(new BeatmapBackgroundSprite(working.Value)
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
diff --git a/osu.Game/Screens/Edit/Setup/SetupSection.cs b/osu.Game/Screens/Edit/Setup/SetupSection.cs
index cdf17d355e..88521a8fb0 100644
--- a/osu.Game/Screens/Edit/Setup/SetupSection.cs
+++ b/osu.Game/Screens/Edit/Setup/SetupSection.cs
@@ -2,10 +2,8 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
-using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osuTK;
@@ -19,7 +17,7 @@ namespace osu.Game.Screens.Edit.Setup
protected OsuColour Colours { get; private set; }
[Resolved]
- protected IBindable Beatmap { get; private set; }
+ protected EditorBeatmap Beatmap { get; private set; }
protected override Container Content => flow;
diff --git a/osu.Game/Screens/Edit/Timing/DifficultySection.cs b/osu.Game/Screens/Edit/Timing/DifficultySection.cs
index b55d74e3b4..b87b8961f8 100644
--- a/osu.Game/Screens/Edit/Timing/DifficultySection.cs
+++ b/osu.Game/Screens/Edit/Timing/DifficultySection.cs
@@ -34,7 +34,7 @@ namespace osu.Game.Screens.Edit.Timing
protected override DifficultyControlPoint CreatePoint()
{
- var reference = Beatmap.Value.Beatmap.ControlPointInfo.DifficultyPointAt(SelectedGroup.Value.Time);
+ var reference = Beatmap.ControlPointInfo.DifficultyPointAt(SelectedGroup.Value.Time);
return new DifficultyControlPoint
{
diff --git a/osu.Game/Screens/Edit/Timing/EffectSection.cs b/osu.Game/Screens/Edit/Timing/EffectSection.cs
index 2f143108a9..6d23b52c05 100644
--- a/osu.Game/Screens/Edit/Timing/EffectSection.cs
+++ b/osu.Game/Screens/Edit/Timing/EffectSection.cs
@@ -37,7 +37,7 @@ namespace osu.Game.Screens.Edit.Timing
protected override EffectControlPoint CreatePoint()
{
- var reference = Beatmap.Value.Beatmap.ControlPointInfo.EffectPointAt(SelectedGroup.Value.Time);
+ var reference = Beatmap.ControlPointInfo.EffectPointAt(SelectedGroup.Value.Time);
return new EffectControlPoint
{
diff --git a/osu.Game/Screens/Edit/Timing/GroupSection.cs b/osu.Game/Screens/Edit/Timing/GroupSection.cs
index 2605ea8b75..2e2c380d4a 100644
--- a/osu.Game/Screens/Edit/Timing/GroupSection.cs
+++ b/osu.Game/Screens/Edit/Timing/GroupSection.cs
@@ -6,7 +6,6 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics.UserInterfaceV2;
@@ -24,7 +23,7 @@ namespace osu.Game.Screens.Edit.Timing
protected Bindable SelectedGroup { get; private set; }
[Resolved]
- protected IBindable Beatmap { get; private set; }
+ protected EditorBeatmap Beatmap { get; private set; }
[Resolved]
private EditorClock clock { get; set; }
@@ -107,13 +106,13 @@ namespace osu.Game.Screens.Edit.Timing
var currentGroupItems = SelectedGroup.Value.ControlPoints.ToArray();
- Beatmap.Value.Beatmap.ControlPointInfo.RemoveGroup(SelectedGroup.Value);
+ Beatmap.ControlPointInfo.RemoveGroup(SelectedGroup.Value);
foreach (var cp in currentGroupItems)
- Beatmap.Value.Beatmap.ControlPointInfo.Add(time, cp);
+ Beatmap.ControlPointInfo.Add(time, cp);
// the control point might not necessarily exist yet, if currentGroupItems was empty.
- SelectedGroup.Value = Beatmap.Value.Beatmap.ControlPointInfo.GroupAt(time, true);
+ SelectedGroup.Value = Beatmap.ControlPointInfo.GroupAt(time, true);
changeHandler?.EndChange();
}
diff --git a/osu.Game/Screens/Edit/Timing/SampleSection.cs b/osu.Game/Screens/Edit/Timing/SampleSection.cs
index 280e19c99a..cc73af6349 100644
--- a/osu.Game/Screens/Edit/Timing/SampleSection.cs
+++ b/osu.Game/Screens/Edit/Timing/SampleSection.cs
@@ -44,7 +44,7 @@ namespace osu.Game.Screens.Edit.Timing
protected override SampleControlPoint CreatePoint()
{
- var reference = Beatmap.Value.Beatmap.ControlPointInfo.SamplePointAt(SelectedGroup.Value.Time);
+ var reference = Beatmap.ControlPointInfo.SamplePointAt(SelectedGroup.Value.Time);
return new SampleControlPoint
{
diff --git a/osu.Game/Screens/Edit/Timing/Section.cs b/osu.Game/Screens/Edit/Timing/Section.cs
index 7a81eeb1a4..5269fa9774 100644
--- a/osu.Game/Screens/Edit/Timing/Section.cs
+++ b/osu.Game/Screens/Edit/Timing/Section.cs
@@ -7,7 +7,6 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
-using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
@@ -27,7 +26,7 @@ namespace osu.Game.Screens.Edit.Timing
private const float header_height = 20;
[Resolved]
- protected IBindable Beatmap { get; private set; }
+ protected EditorBeatmap Beatmap { get; private set; }
[Resolved]
protected Bindable SelectedGroup { get; private set; }
diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs
index eab909b798..c5d2dd756a 100644
--- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs
+++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs
@@ -7,7 +7,6 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
-using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
@@ -62,7 +61,7 @@ namespace osu.Game.Screens.Edit.Timing
private EditorClock clock { get; set; }
[Resolved]
- protected IBindable Beatmap { get; private set; }
+ protected EditorBeatmap Beatmap { get; private set; }
[Resolved]
private Bindable selectedGroup { get; set; }
@@ -124,7 +123,7 @@ namespace osu.Game.Screens.Edit.Timing
selectedGroup.BindValueChanged(selected => { deleteButton.Enabled.Value = selected.NewValue != null; }, true);
- controlPointGroups.BindTo(Beatmap.Value.Beatmap.ControlPointInfo.Groups);
+ controlPointGroups.BindTo(Beatmap.ControlPointInfo.Groups);
controlPointGroups.BindCollectionChanged((sender, args) =>
{
table.ControlGroups = controlPointGroups;
@@ -137,14 +136,14 @@ namespace osu.Game.Screens.Edit.Timing
if (selectedGroup.Value == null)
return;
- Beatmap.Value.Beatmap.ControlPointInfo.RemoveGroup(selectedGroup.Value);
+ Beatmap.ControlPointInfo.RemoveGroup(selectedGroup.Value);
- selectedGroup.Value = Beatmap.Value.Beatmap.ControlPointInfo.Groups.FirstOrDefault(g => g.Time >= clock.CurrentTime);
+ selectedGroup.Value = Beatmap.ControlPointInfo.Groups.FirstOrDefault(g => g.Time >= clock.CurrentTime);
}
private void addNew()
{
- selectedGroup.Value = Beatmap.Value.Beatmap.ControlPointInfo.GroupAt(clock.CurrentTime, true);
+ selectedGroup.Value = Beatmap.ControlPointInfo.GroupAt(clock.CurrentTime, true);
}
}
}
diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs
index 1ae2a86885..a0bb9ac506 100644
--- a/osu.Game/Screens/Edit/Timing/TimingSection.cs
+++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs
@@ -49,7 +49,7 @@ namespace osu.Game.Screens.Edit.Timing
protected override TimingControlPoint CreatePoint()
{
- var reference = Beatmap.Value.Beatmap.ControlPointInfo.TimingPointAt(SelectedGroup.Value.Time);
+ var reference = Beatmap.ControlPointInfo.TimingPointAt(SelectedGroup.Value.Time);
return new TimingControlPoint
{
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs
index 0f06188dc2..f13d623eae 100644
--- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs
@@ -1,7 +1,10 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
+using System.Diagnostics;
using System.Linq;
+using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@@ -26,6 +29,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
protected override UserActivity InitialActivity => new UserActivity.SearchingForLobby();
private readonly IBindable initialRoomsReceived = new Bindable();
+ private readonly IBindable operationInProgress = new Bindable();
private FilterControl filter;
private Container content;
@@ -37,7 +41,11 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
[Resolved]
private MusicController music { get; set; }
- private bool joiningRoom;
+ [Resolved(CanBeNull = true)]
+ private OngoingOperationTracker ongoingOperationTracker { get; set; }
+
+ [CanBeNull]
+ private IDisposable joiningRoomOperation { get; set; }
[BackgroundDependencyLoader]
private void load()
@@ -98,7 +106,13 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
base.LoadComplete();
initialRoomsReceived.BindTo(RoomManager.InitialRoomsReceived);
- initialRoomsReceived.BindValueChanged(onInitialRoomsReceivedChanged, true);
+ initialRoomsReceived.BindValueChanged(_ => updateLoadingLayer());
+
+ if (ongoingOperationTracker != null)
+ {
+ operationInProgress.BindTo(ongoingOperationTracker.InProgress);
+ operationInProgress.BindValueChanged(_ => updateLoadingLayer(), true);
+ }
}
protected override void UpdateAfterChildren()
@@ -156,26 +170,24 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
private void joinRequested(Room room)
{
- joiningRoom = true;
- updateLoadingLayer();
+ Debug.Assert(joiningRoomOperation == null);
+ joiningRoomOperation = ongoingOperationTracker?.BeginOperation();
RoomManager?.JoinRoom(room, r =>
{
Open(room);
- joiningRoom = false;
- updateLoadingLayer();
+ joiningRoomOperation?.Dispose();
+ joiningRoomOperation = null;
}, _ =>
{
- joiningRoom = false;
- updateLoadingLayer();
+ joiningRoomOperation?.Dispose();
+ joiningRoomOperation = null;
});
}
- private void onInitialRoomsReceivedChanged(ValueChangedEvent received) => updateLoadingLayer();
-
private void updateLoadingLayer()
{
- if (joiningRoom || !initialRoomsReceived.Value)
+ if (operationInProgress.Value || !initialRoomsReceived.Value)
loadingLayer.Show();
else
loadingLayer.Hide();
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/CreateMultiplayerMatchButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/CreateMultiplayerMatchButton.cs
index 163efd9c20..87b0e49b5b 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/CreateMultiplayerMatchButton.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/CreateMultiplayerMatchButton.cs
@@ -10,14 +10,34 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
{
public class CreateMultiplayerMatchButton : PurpleTriangleButton
{
+ private IBindable isConnected;
+ private IBindable operationInProgress;
+
+ [Resolved]
+ private StatefulMultiplayerClient multiplayerClient { get; set; }
+
+ [Resolved]
+ private OngoingOperationTracker ongoingOperationTracker { get; set; }
+
[BackgroundDependencyLoader]
- private void load(StatefulMultiplayerClient multiplayerClient)
+ private void load()
{
Triangles.TriangleScale = 1.5f;
Text = "Create room";
- ((IBindable)Enabled).BindTo(multiplayerClient.IsConnected);
+ isConnected = multiplayerClient.IsConnected.GetBoundCopy();
+ operationInProgress = ongoingOperationTracker.InProgress.GetBoundCopy();
}
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ isConnected.BindValueChanged(_ => updateState());
+ operationInProgress.BindValueChanged(_ => updateState(), true);
+ }
+
+ private void updateState() => Enabled.Value = isConnected.Value && !operationInProgress.Value;
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchFooter.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchFooter.cs
index a52f62fe00..bbf861fac3 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchFooter.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchFooter.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
@@ -19,7 +20,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
public readonly Bindable SelectedItem = new Bindable();
+ public Action OnReadyClick
+ {
+ set => readyButton.OnReadyClick = value;
+ }
+
private readonly Drawable background;
+ private readonly MultiplayerReadyButton readyButton;
public MultiplayerMatchFooter()
{
@@ -29,7 +36,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
InternalChildren = new[]
{
background = new Box { RelativeSizeAxes = Axes.Both },
- new MultiplayerReadyButton
+ readyButton = new MultiplayerReadyButton
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs
index 67c6aa7add..f0064ae0b4 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs
@@ -2,6 +2,8 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Diagnostics;
+using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
@@ -68,6 +70,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
[Resolved]
private Bindable ruleset { get; set; }
+ [Resolved]
+ private OngoingOperationTracker ongoingOperationTracker { get; set; }
+
+ private readonly IBindable operationInProgress = new BindableBool();
+
+ [CanBeNull]
+ private IDisposable applyingSettingsOperation;
+
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
@@ -265,13 +275,22 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
Type.BindValueChanged(type => TypePicker.Current.Value = type.NewValue, true);
MaxParticipants.BindValueChanged(count => MaxParticipantsField.Text = count.NewValue?.ToString(), true);
RoomID.BindValueChanged(roomId => initialBeatmapControl.Alpha = roomId.NewValue == null ? 1 : 0, true);
+
+ operationInProgress.BindTo(ongoingOperationTracker.InProgress);
+ operationInProgress.BindValueChanged(v =>
+ {
+ if (v.NewValue)
+ loadingLayer.Show();
+ else
+ loadingLayer.Hide();
+ });
}
protected override void Update()
{
base.Update();
- ApplyButton.Enabled.Value = Playlist.Count > 0 && NameField.Text.Length > 0;
+ ApplyButton.Enabled.Value = Playlist.Count > 0 && NameField.Text.Length > 0 && !operationInProgress.Value;
}
private void apply()
@@ -280,7 +299,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
return;
hideError();
- loadingLayer.Show();
+
+ Debug.Assert(applyingSettingsOperation == null);
+ applyingSettingsOperation = ongoingOperationTracker.BeginOperation();
// If the client is already in a room, update via the client.
// Otherwise, update the room directly in preparation for it to be submitted to the API on match creation.
@@ -313,16 +334,23 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
private void onSuccess(Room room)
{
- loadingLayer.Hide();
+ Debug.Assert(applyingSettingsOperation != null);
+
SettingsApplied?.Invoke();
+
+ applyingSettingsOperation.Dispose();
+ applyingSettingsOperation = null;
}
private void onError(string text)
{
+ Debug.Assert(applyingSettingsOperation != null);
+
ErrorText.Text = text;
ErrorText.FadeIn(50);
- loadingLayer.Hide();
+ applyingSettingsOperation.Dispose();
+ applyingSettingsOperation = null;
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs
index 281e92404c..04030cdbfd 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs
@@ -1,15 +1,14 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using System.Diagnostics;
using System.Linq;
-using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
-using osu.Game.Extensions;
using osu.Game.Graphics;
using osu.Game.Graphics.Backgrounds;
using osu.Game.Online.API;
@@ -24,15 +23,22 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
{
public Bindable SelectedItem => button.SelectedItem;
+ public Action OnReadyClick
+ {
+ set => button.Action = value;
+ }
+
[Resolved]
private IAPIProvider api { get; set; }
- [CanBeNull]
- private MultiplayerRoomUser localUser;
-
[Resolved]
private OsuColour colours { get; set; }
+ [Resolved]
+ private OngoingOperationTracker ongoingOperationTracker { get; set; }
+
+ private IBindable operationInProgress;
+
private SampleChannel sampleReadyCount;
private readonly ButtonWithTrianglesExposed button;
@@ -46,7 +52,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
RelativeSizeAxes = Axes.Both,
Size = Vector2.One,
Enabled = { Value = true },
- Action = onClick
};
}
@@ -54,21 +59,22 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
private void load(AudioManager audio)
{
sampleReadyCount = audio.Samples.Get(@"SongSelect/select-difficulty");
+
+ operationInProgress = ongoingOperationTracker.InProgress.GetBoundCopy();
+ operationInProgress.BindValueChanged(_ => updateState());
}
protected override void OnRoomUpdated()
{
base.OnRoomUpdated();
- // this method is called on leaving the room, so the local user may not exist in the room any more.
- localUser = Room?.Users.SingleOrDefault(u => u.User?.Id == api.LocalUser.Value.Id);
-
- button.Enabled.Value = Client.Room?.State == MultiplayerRoomState.Open;
updateState();
}
private void updateState()
{
+ var localUser = Client.LocalUser;
+
if (localUser == null)
return;
@@ -100,6 +106,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
break;
}
+ button.Enabled.Value = Client.Room?.State == MultiplayerRoomState.Open && !operationInProgress.Value;
+
if (newCountReady != countReady)
{
countReady = newCountReady;
@@ -132,22 +140,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
}
}
- private void onClick()
- {
- if (localUser == null)
- return;
-
- if (localUser.State == MultiplayerUserState.Idle)
- Client.ChangeState(MultiplayerUserState.Ready).CatchUnobservedExceptions(true);
- else
- {
- if (Room?.Host?.Equals(localUser) == true)
- Client.StartMatch().CatchUnobservedExceptions(true);
- else
- Client.ChangeState(MultiplayerUserState.Idle).CatchUnobservedExceptions(true);
- }
- }
-
private class ButtonWithTrianglesExposed : ReadyButton
{
public new Triangles Triangles => base.Triangles;
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs
index ffa36ecfdb..e539b315e4 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs
@@ -1,14 +1,17 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Linq;
+using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Screens;
+using osu.Game.Extensions;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Components;
@@ -31,10 +34,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
[Resolved]
private StatefulMultiplayerClient client { get; set; }
+ [Resolved]
+ private OngoingOperationTracker ongoingOperationTracker { get; set; }
+
private MultiplayerMatchSettingsOverlay settingsOverlay;
private IBindable isConnected;
+ [CanBeNull]
+ private IDisposable readyClickOperation;
+
public MultiplayerMatchSubScreen(Room room)
{
Title = room.RoomID.Value == null ? "New room" : room.Name.Value;
@@ -150,7 +159,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
},
new Drawable[]
{
- new MultiplayerMatchFooter { SelectedItem = { BindTarget = SelectedItem } }
+ new MultiplayerMatchFooter
+ {
+ SelectedItem = { BindTarget = SelectedItem },
+ OnReadyClick = onReadyClick
+ }
}
},
RowDimensions = new[]
@@ -196,6 +209,44 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
private void onPlaylistChanged(object sender, NotifyCollectionChangedEventArgs e) => SelectedItem.Value = Playlist.FirstOrDefault();
+ private void onReadyClick()
+ {
+ Debug.Assert(readyClickOperation == null);
+ readyClickOperation = ongoingOperationTracker.BeginOperation();
+
+ if (client.IsHost && client.LocalUser?.State == MultiplayerUserState.Ready)
+ {
+ client.StartMatch()
+ .ContinueWith(t =>
+ {
+ // accessing Exception here silences any potential errors from the antecedent task
+ if (t.Exception != null)
+ {
+ t.CatchUnobservedExceptions(true); // will run immediately.
+ // gameplay was not started due to an exception; unblock button.
+ endOperation();
+ }
+
+ // gameplay is starting, the button will be unblocked on load requested.
+ });
+ return;
+ }
+
+ client.ToggleReady()
+ .ContinueWith(t =>
+ {
+ t.CatchUnobservedExceptions(true); // will run immediately.
+ endOperation();
+ });
+
+ void endOperation()
+ {
+ Debug.Assert(readyClickOperation != null);
+ readyClickOperation.Dispose();
+ readyClickOperation = null;
+ }
+ }
+
private void onLoadRequested()
{
Debug.Assert(client.Room != null);
@@ -203,6 +254,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
int[] userIds = client.CurrentMatchPlayingUserIds.ToArray();
StartPlay(() => new MultiplayerPlayer(SelectedItem.Value, userIds));
+
+ Debug.Assert(readyClickOperation != null);
+
+ readyClickOperation.Dispose();
+ readyClickOperation = null;
}
protected override void Dispose(bool isDisposing)
diff --git a/osu.Game/Screens/OnlinePlay/OngoingOperationTracker.cs b/osu.Game/Screens/OnlinePlay/OngoingOperationTracker.cs
new file mode 100644
index 0000000000..5c9e9ce90b
--- /dev/null
+++ b/osu.Game/Screens/OnlinePlay/OngoingOperationTracker.cs
@@ -0,0 +1,59 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+
+namespace osu.Game.Screens.OnlinePlay
+{
+ ///
+ /// Utility class to track ongoing online operations' progress.
+ /// Can be used to disable interactivity while waiting for a response from online sources.
+ ///
+ public class OngoingOperationTracker : Component
+ {
+ ///
+ /// Whether there is an online operation in progress.
+ ///
+ public IBindable InProgress => inProgress;
+
+ private readonly Bindable inProgress = new BindableBool();
+
+ private LeasedBindable leasedInProgress;
+
+ public OngoingOperationTracker()
+ {
+ AlwaysPresent = true;
+ }
+
+ ///
+ /// Begins tracking a new online operation.
+ ///
+ ///
+ /// An that will automatically mark the operation as ended on disposal.
+ ///
+ /// An operation has already been started.
+ public IDisposable BeginOperation()
+ {
+ if (leasedInProgress != null)
+ throw new InvalidOperationException("Cannot begin operation while another is in progress.");
+
+ leasedInProgress = inProgress.BeginLease(true);
+ leasedInProgress.Value = true;
+
+ // for extra safety, marshal the end of operation back to the update thread if necessary.
+ return new InvokeOnDisposal(() => Scheduler.Add(endOperation, false));
+ }
+
+ private void endOperation()
+ {
+ if (leasedInProgress == null)
+ throw new InvalidOperationException("Cannot end operation multiple times.");
+
+ leasedInProgress.Return();
+ leasedInProgress = null;
+ }
+ }
+}
diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs
index 75612516a9..71fd0d5c76 100644
--- a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs
@@ -53,6 +53,9 @@ namespace osu.Game.Screens.OnlinePlay
[Cached]
private readonly Bindable currentFilter = new Bindable(new FilterCriteria());
+ [Cached]
+ private OngoingOperationTracker ongoingOperationTracker { get; set; }
+
[Resolved(CanBeNull = true)]
private MusicController music { get; set; }
@@ -141,7 +144,8 @@ namespace osu.Game.Screens.OnlinePlay
};
button.Action = () => OpenNewRoom();
}),
- RoomManager = CreateRoomManager()
+ RoomManager = CreateRoomManager(),
+ ongoingOperationTracker = new OngoingOperationTracker()
}
};
diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs
index e1a29946f4..9b716b323d 100644
--- a/osu.Game/Screens/OsuScreen.cs
+++ b/osu.Game/Screens/OsuScreen.cs
@@ -143,7 +143,11 @@ namespace osu.Game.Screens
private void load(OsuGame osu, AudioManager audio)
{
sampleExit = audio.Samples.Get(@"UI/screen-back");
+ }
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
Activity.Value ??= InitialActivity;
}
diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs
index 36f8fbedb3..7ba6e400bf 100644
--- a/osu.Game/Screens/Select/BeatmapCarousel.cs
+++ b/osu.Game/Screens/Select/BeatmapCarousel.cs
@@ -936,7 +936,6 @@ namespace osu.Game.Screens.Select
Masking = false;
}
- // ReSharper disable once OptionalParameterHierarchyMismatch 2020.3 EAP4 bug. (https://youtrack.jetbrains.com/issue/RSRP-481535?p=RIDER-51910)
protected override void OnUserScroll(float value, bool animated = true, double? distanceDecay = default)
{
UserScrolling = true;
diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs
index 50a61ed4c2..e61d5cce85 100644
--- a/osu.Game/Screens/Select/PlaySongSelect.cs
+++ b/osu.Game/Screens/Select/PlaySongSelect.cs
@@ -9,6 +9,7 @@ using osu.Framework.Screens;
using osu.Game.Graphics;
using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
+using osu.Game.Rulesets.Mods;
using osu.Game.Scoring;
using osu.Game.Screens.Play;
using osu.Game.Screens.Ranking;
@@ -42,6 +43,8 @@ namespace osu.Game.Screens.Select
protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea();
+ private ModAutoplay getAutoplayMod() => Ruleset.Value.CreateInstance().GetAutoplayMod();
+
public override void OnResuming(IScreen last)
{
base.OnResuming(last);
@@ -50,10 +53,10 @@ namespace osu.Game.Screens.Select
if (removeAutoModOnResume)
{
- var autoType = Ruleset.Value.CreateInstance().GetAutoplayMod()?.GetType();
+ var autoType = getAutoplayMod()?.GetType();
if (autoType != null)
- ModSelect.DeselectTypes(new[] { autoType }, true);
+ Mods.Value = Mods.Value.Where(m => m.GetType() != autoType).ToArray();
removeAutoModOnResume = false;
}
@@ -81,12 +84,9 @@ namespace osu.Game.Screens.Select
// Ctrl+Enter should start map with autoplay enabled.
if (GetContainingInputManager().CurrentState?.Keyboard.ControlPressed == true)
{
- var auto = Ruleset.Value.CreateInstance().GetAutoplayMod();
- var autoType = auto?.GetType();
+ var autoplayMod = getAutoplayMod();
- var mods = Mods.Value;
-
- if (autoType == null)
+ if (autoplayMod == null)
{
notifications?.Post(new SimpleNotification
{
@@ -95,9 +95,11 @@ namespace osu.Game.Screens.Select
return false;
}
- if (mods.All(m => m.GetType() != autoType))
+ var mods = Mods.Value;
+
+ if (mods.All(m => m.GetType() != autoplayMod.GetType()))
{
- Mods.Value = mods.Append(auto).ToArray();
+ Mods.Value = mods.Append(autoplayMod).ToArray();
removeAutoModOnResume = true;
}
}
diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs
index da0e39d965..a87b22affe 100644
--- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs
+++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs
@@ -23,6 +23,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Cached]
public Bindable Filter { get; }
+ [Cached]
+ public OngoingOperationTracker OngoingOperationTracker { get; }
+
protected override Container Content => content;
private readonly TestMultiplayerRoomContainer content;
@@ -36,6 +39,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
Client = content.Client;
RoomManager = content.RoomManager;
Filter = content.Filter;
+ OngoingOperationTracker = content.OngoingOperationTracker;
}
[SetUp]
diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs
index ad3e2f7105..860caef071 100644
--- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs
+++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs
@@ -25,6 +25,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Cached]
public readonly Bindable Filter = new Bindable(new FilterCriteria());
+ [Cached]
+ public readonly OngoingOperationTracker OngoingOperationTracker;
+
public TestMultiplayerRoomContainer()
{
RelativeSizeAxes = Axes.Both;
@@ -33,6 +36,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
Client = new TestMultiplayerClient(),
RoomManager = new TestMultiplayerRoomManager(),
+ OngoingOperationTracker = new OngoingOperationTracker(),
content = new Container { RelativeSizeAxes = Axes.Both }
});
}
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 6c220a5c21..f28a55e016 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -26,7 +26,7 @@
-
+
diff --git a/osu.iOS.props b/osu.iOS.props
index 5445adb3fb..93be3645ee 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -70,7 +70,7 @@
-
+
@@ -88,7 +88,7 @@
-
+