diff --git a/fastlane/Fastfile b/fastlane/Fastfile
index 510b53054b..4fd0e5e8c7 100644
--- a/fastlane/Fastfile
+++ b/fastlane/Fastfile
@@ -97,8 +97,10 @@ platform :ios do
changelog.gsub!('$BUILD_ID', options[:build])
pilot(
- wait_processing_interval: 1800,
+ wait_processing_interval: 900,
changelog: changelog,
+ groups: ['osu! supporters', 'public'],
+ distribute_external: true,
ipa: './osu.iOS/bin/iPhone/Release/osu.iOS.ipa'
)
end
diff --git a/osu.Android.props b/osu.Android.props
index 942970c890..b147fdd05b 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -52,6 +52,6 @@
-
+
diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs
index b9d791fdb1..212365caad 100644
--- a/osu.Game.Rulesets.Catch/CatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs
@@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Catch
new KeyBinding(InputKey.Shift, CatchAction.Dash),
};
- public override IEnumerable ConvertLegacyMods(LegacyMods mods)
+ public override IEnumerable ConvertFromLegacyMods(LegacyMods mods)
{
if (mods.HasFlag(LegacyMods.Nightcore))
yield return new CatchModNightcore();
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs
index e2465d727e..acdd0a420c 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.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.Linq;
using osu.Framework.Bindables;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
@@ -30,6 +31,22 @@ namespace osu.Game.Rulesets.Catch.Mods
Value = 5,
};
+ public override string SettingDescription
+ {
+ get
+ {
+ string circleSize = CircleSize.IsDefault ? string.Empty : $"CS {CircleSize.Value:N1}";
+ string approachRate = ApproachRate.IsDefault ? string.Empty : $"AR {ApproachRate.Value:N1}";
+
+ return string.Join(", ", new[]
+ {
+ circleSize,
+ base.SettingDescription,
+ approachRate
+ }.Where(s => !string.IsNullOrEmpty(s)));
+ }
+ }
+
protected override void TransferSettings(BeatmapDifficulty difficulty)
{
base.TransferSettings(difficulty);
diff --git a/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs b/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs
index b41a5e0612..9dab3ed630 100644
--- a/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs
+++ b/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs
@@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Catch.Replays
}
}
- public void ConvertFrom(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null)
+ public void FromLegacy(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null)
{
Position = currentFrame.Position.X / CatchPlayfield.BASE_WIDTH;
Dashing = currentFrame.ButtonState == ReplayButtonState.Left1;
@@ -56,5 +56,14 @@ namespace osu.Game.Rulesets.Catch.Replays
Actions.Add(CatchAction.MoveLeft);
}
}
+
+ public LegacyReplayFrame ToLegacy(IBeatmap beatmap)
+ {
+ ReplayButtonState state = ReplayButtonState.None;
+
+ if (Actions.Contains(CatchAction.Dash)) state |= ReplayButtonState.Left1;
+
+ return new LegacyReplayFrame(Time, Position * CatchPlayfield.BASE_WIDTH, null, state);
+ }
}
}
diff --git a/osu.Game.Rulesets.Catch/UI/CatchReplayRecorder.cs b/osu.Game.Rulesets.Catch/UI/CatchReplayRecorder.cs
new file mode 100644
index 0000000000..9a4d1f9585
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/UI/CatchReplayRecorder.cs
@@ -0,0 +1,26 @@
+// 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 osu.Game.Replays;
+using osu.Game.Rulesets.Catch.Replays;
+using osu.Game.Rulesets.Replays;
+using osu.Game.Rulesets.UI;
+using osuTK;
+
+namespace osu.Game.Rulesets.Catch.UI
+{
+ public class CatchReplayRecorder : ReplayRecorder
+ {
+ private readonly CatchPlayfield playfield;
+
+ public CatchReplayRecorder(Replay target, CatchPlayfield playfield)
+ : base(target)
+ {
+ this.playfield = playfield;
+ }
+
+ protected override ReplayFrame HandleFrame(Vector2 mousePosition, List actions, ReplayFrame previousFrame)
+ => new CatchReplayFrame(Time.Current, playfield.CatcherArea.MovableCatcher.X, actions.Contains(CatchAction.Dash), previousFrame as CatchReplayFrame);
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs
index e361b29a9d..8fa9c61b6f 100644
--- a/osu.Game.Rulesets.Catch/UI/Catcher.cs
+++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs
@@ -141,14 +141,14 @@ namespace osu.Game.Rulesets.Catch.UI
var ourRadius = fruit.DisplayRadius;
float theirRadius = 0;
- const float allowance = 6;
+ const float allowance = 10;
while (caughtFruit.Any(f =>
f.LifetimeEnd == double.MaxValue &&
Vector2Extensions.Distance(f.Position, fruit.Position) < (ourRadius + (theirRadius = f.DrawSize.X / 2 * f.Scale.X)) / (allowance / 2)))
{
var diff = (ourRadius + theirRadius) / allowance;
- fruit.X += (RNG.NextSingle() - 0.5f) * 2 * diff;
+ fruit.X += (RNG.NextSingle() - 0.5f) * diff * 2;
fruit.Y -= RNG.NextSingle() * diff;
}
diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs
index fd8a1d175d..ebe45aa3ab 100644
--- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs
@@ -32,6 +32,8 @@ namespace osu.Game.Rulesets.Catch.UI
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay);
+ protected override ReplayRecorder CreateReplayRecorder(Replay replay) => new CatchReplayRecorder(replay, (CatchPlayfield)Playfield);
+
protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty, CreateDrawableRepresentation);
public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new CatchPlayfieldAdjustmentContainer();
diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs b/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs
index 909d0d45c6..9049bb3a82 100644
--- a/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs
+++ b/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs
@@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Mania.Tests
{
}
- protected override RulesetKeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
+ protected override KeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
=> new LocalKeyBindingContainer(ruleset, variant, unique);
private class LocalKeyBindingContainer : RulesetKeyBindingContainer
diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
index b7b523a94d..9d06bd7c25 100644
--- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
@@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Mania
public override ISkin CreateLegacySkinProvider(ISkinSource source) => new ManiaLegacySkinTransformer(source);
- public override IEnumerable ConvertLegacyMods(LegacyMods mods)
+ public override IEnumerable ConvertFromLegacyMods(LegacyMods mods)
{
if (mods.HasFlag(LegacyMods.Nightcore))
yield return new ManiaModNightcore();
@@ -118,6 +118,59 @@ namespace osu.Game.Rulesets.Mania
yield return new ManiaModRandom();
}
+ public override LegacyMods ConvertToLegacyMods(Mod[] mods)
+ {
+ var value = base.ConvertToLegacyMods(mods);
+
+ foreach (var mod in mods)
+ {
+ switch (mod)
+ {
+ case ManiaModKey1 _:
+ value |= LegacyMods.Key1;
+ break;
+
+ case ManiaModKey2 _:
+ value |= LegacyMods.Key2;
+ break;
+
+ case ManiaModKey3 _:
+ value |= LegacyMods.Key3;
+ break;
+
+ case ManiaModKey4 _:
+ value |= LegacyMods.Key4;
+ break;
+
+ case ManiaModKey5 _:
+ value |= LegacyMods.Key5;
+ break;
+
+ case ManiaModKey6 _:
+ value |= LegacyMods.Key6;
+ break;
+
+ case ManiaModKey7 _:
+ value |= LegacyMods.Key7;
+ break;
+
+ case ManiaModKey8 _:
+ value |= LegacyMods.Key8;
+ break;
+
+ case ManiaModKey9 _:
+ value |= LegacyMods.Key9;
+ break;
+
+ case ManiaModFadeIn _:
+ value |= LegacyMods.FadeIn;
+ break;
+ }
+ }
+
+ return value;
+ }
+
public override IEnumerable GetModsFor(ModType type)
{
switch (type)
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs
index 14b36fb765..699c58c373 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs
@@ -3,24 +3,17 @@
using System.Linq;
using osu.Framework.Extensions.IEnumerableExtensions;
-using osu.Framework.Graphics.Sprites;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
-using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Mania.Mods
{
- public class ManiaModRandom : Mod, IApplicableToBeatmap
+ public class ManiaModRandom : ModRandom, IApplicableToBeatmap
{
- public override string Name => "Random";
- public override string Acronym => "RD";
- public override ModType Type => ModType.Conversion;
- public override IconUsage? Icon => OsuIcon.Dice;
public override string Description => @"Shuffle around the keys!";
- public override double ScoreMultiplier => 1;
public void ApplyToBeatmap(IBeatmap beatmap)
{
diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs
index 877a9ee410..8c73c36e99 100644
--- a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs
+++ b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
+using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Replays.Legacy;
using osu.Game.Rulesets.Mania.Beatmaps;
@@ -24,15 +25,9 @@ namespace osu.Game.Rulesets.Mania.Replays
Actions.AddRange(actions);
}
- public void ConvertFrom(LegacyReplayFrame legacyFrame, IBeatmap beatmap, ReplayFrame lastFrame = null)
+ public void FromLegacy(LegacyReplayFrame legacyFrame, IBeatmap beatmap, ReplayFrame lastFrame = null)
{
- // We don't need to fully convert, just create the converter
- var converter = new ManiaBeatmapConverter(beatmap, new ManiaRuleset());
-
- // NB: Via co-op mod, osu-stable can have two stages with floor(col/2) and ceil(col/2) columns. This will need special handling
- // elsewhere in the game if we do choose to support the old co-op mod anyway. For now, assume that there is only one stage.
-
- var stage = new StageDefinition { Columns = converter.TargetColumns };
+ var maniaBeatmap = (ManiaBeatmap)beatmap;
var normalAction = ManiaAction.Key1;
var specialAction = ManiaAction.Special1;
@@ -42,7 +37,7 @@ namespace osu.Game.Rulesets.Mania.Replays
while (activeColumns > 0)
{
- var isSpecial = stage.IsSpecialColumn(counter);
+ var isSpecial = maniaBeatmap.Stages.First().IsSpecialColumn(counter);
if ((activeColumns & 1) > 0)
Actions.Add(isSpecial ? specialAction : normalAction);
@@ -56,5 +51,40 @@ namespace osu.Game.Rulesets.Mania.Replays
activeColumns >>= 1;
}
}
+
+ public LegacyReplayFrame ToLegacy(IBeatmap beatmap)
+ {
+ var maniaBeatmap = (ManiaBeatmap)beatmap;
+
+ int keys = 0;
+
+ var specialColumns = new List();
+
+ for (int i = 0; i < maniaBeatmap.TotalColumns; i++)
+ {
+ if (maniaBeatmap.Stages.First().IsSpecialColumn(i))
+ specialColumns.Add(i);
+ }
+
+ foreach (var action in Actions)
+ {
+ switch (action)
+ {
+ case ManiaAction.Special1:
+ keys |= 1 << specialColumns[0];
+ break;
+
+ case ManiaAction.Special2:
+ keys |= 1 << specialColumns[1];
+ break;
+
+ default:
+ keys |= 1 << (action - ManiaAction.Key1);
+ break;
+ }
+ }
+
+ return new LegacyReplayFrame(Time, keys, null, ReplayButtonState.None);
+ }
}
}
diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs
index 2c497541a8..e5ec054fa7 100644
--- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs
@@ -85,5 +85,7 @@ namespace osu.Game.Rulesets.Mania.UI
}
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay);
+
+ protected override ReplayRecorder CreateReplayRecorder(Replay replay) => new ManiaReplayRecorder(replay);
}
}
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaReplayRecorder.cs b/osu.Game.Rulesets.Mania/UI/ManiaReplayRecorder.cs
new file mode 100644
index 0000000000..18275000a2
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/UI/ManiaReplayRecorder.cs
@@ -0,0 +1,23 @@
+// 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 osu.Game.Replays;
+using osu.Game.Rulesets.Mania.Replays;
+using osu.Game.Rulesets.Replays;
+using osu.Game.Rulesets.UI;
+using osuTK;
+
+namespace osu.Game.Rulesets.Mania.UI
+{
+ public class ManiaReplayRecorder : ReplayRecorder
+ {
+ public ManiaReplayRecorder(Replay replay)
+ : base(replay)
+ {
+ }
+
+ protected override ReplayFrame HandleFrame(Vector2 mousePosition, List actions, ReplayFrame previousFrame)
+ => new ManiaReplayFrame(Time.Current, actions.ToArray());
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs
index 67b6dac787..0649989dc0 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleArea.cs
@@ -4,48 +4,43 @@
using System;
using NUnit.Framework;
using osu.Framework.Graphics;
-using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning;
+using osu.Game.Tests.Visual;
using osuTK;
namespace osu.Game.Rulesets.Osu.Tests
{
- public class TestSceneHitCircleArea : ManualInputManagerTestScene
+ public class TestSceneHitCircleArea : OsuManualInputManagerTestScene
{
private HitCircle hitCircle;
private DrawableHitCircle drawableHitCircle;
private DrawableHitCircle.HitReceptor hitAreaReceptor => drawableHitCircle.HitArea;
[SetUp]
- public new void SetUp()
+ public void SetUp() => Schedule(() =>
{
- base.SetUp();
-
- Schedule(() =>
+ hitCircle = new HitCircle
{
- hitCircle = new HitCircle
- {
- Position = new Vector2(100, 100),
- StartTime = Time.Current + 500
- };
+ Position = new Vector2(100, 100),
+ StartTime = Time.Current + 500
+ };
- hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+ hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
- Child = new SkinProvidingContainer(new DefaultSkin())
+ Child = new SkinProvidingContainer(new DefaultSkin())
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = drawableHitCircle = new DrawableHitCircle(hitCircle)
{
- RelativeSizeAxes = Axes.Both,
- Child = drawableHitCircle = new DrawableHitCircle(hitCircle)
- {
- Size = new Vector2(100)
- }
- };
- });
- }
+ Size = new Vector2(100)
+ }
+ };
+ });
[Test]
public void TestCircleHitCentre()
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs
index 4af4d5f966..0ae49790cd 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs
@@ -23,7 +23,7 @@ using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Tests
{
- public class TestSceneOsuDistanceSnapGrid : ManualInputManagerTestScene
+ public class TestSceneOsuDistanceSnapGrid : OsuManualInputManagerTestScene
{
private const double beat_length = 100;
private static readonly Vector2 grid_position = new Vector2(512, 384);
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneResumeOverlay.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneResumeOverlay.cs
index 8e73d6152f..f4809b0c9b 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneResumeOverlay.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneResumeOverlay.cs
@@ -12,7 +12,7 @@ using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests
{
- public class TestSceneResumeOverlay : ManualInputManagerTestScene
+ public class TestSceneResumeOverlay : OsuManualInputManagerTestScene
{
public override IReadOnlyList RequiredTypes => new[]
{
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs
index defd3a6f22..a201364de4 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs
@@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Tests
typeof(DrawableSliderTick),
typeof(DrawableSliderTail),
typeof(DrawableSliderHead),
- typeof(DrawableRepeatPoint),
+ typeof(DrawableSliderRepeat),
typeof(DrawableOsuHitObject)
};
@@ -146,7 +146,7 @@ namespace osu.Game.Rulesets.Osu.Tests
AddAssert("head samples updated", () => assertSamples(((Slider)slider.HitObject).HeadCircle));
AddAssert("tick samples not updated", () => ((Slider)slider.HitObject).NestedHitObjects.OfType().All(assertTickSamples));
- AddAssert("repeat samples updated", () => ((Slider)slider.HitObject).NestedHitObjects.OfType().All(assertSamples));
+ AddAssert("repeat samples updated", () => ((Slider)slider.HitObject).NestedHitObjects.OfType().All(assertSamples));
AddAssert("tail has no samples", () => ((Slider)slider.HitObject).TailCircle.Samples.Count == 0);
static bool assertTickSamples(SliderTick tick) => tick.Samples.Single().Name == "slidertick";
@@ -181,7 +181,7 @@ namespace osu.Game.Rulesets.Osu.Tests
AddAssert("head samples not updated", () => assertSamples(((Slider)slider.HitObject).HeadCircle));
AddAssert("tick samples not updated", () => ((Slider)slider.HitObject).NestedHitObjects.OfType().All(assertTickSamples));
- AddAssert("repeat samples not updated", () => ((Slider)slider.HitObject).NestedHitObjects.OfType().All(assertSamples));
+ AddAssert("repeat samples not updated", () => ((Slider)slider.HitObject).NestedHitObjects.OfType().All(assertSamples));
AddAssert("tail has no samples", () => ((Slider)slider.HitObject).TailCircle.Samples.Count == 0);
static bool assertTickSamples(SliderTick tick) => tick.Samples.Single().Name == "slidertick";
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs
index 94df239267..67e1b77770 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs
@@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Tests
typeof(SliderBall),
typeof(DrawableSlider),
typeof(DrawableSliderTick),
- typeof(DrawableRepeatPoint),
+ typeof(DrawableSliderRepeat),
typeof(DrawableOsuHitObject),
typeof(DrawableSliderHead),
typeof(DrawableSliderTail),
@@ -327,7 +327,7 @@ namespace osu.Game.Rulesets.Osu.Tests
AddAssert("Tracking dropped", assertMidSliderJudgementFail);
}
- private bool assertGreatJudge() => judgementResults.Last().Type == HitResult.Great;
+ private bool assertGreatJudge() => judgementResults.Any() && judgementResults.All(t => t.Type == HitResult.Great);
private bool assertHeadMissTailTracked() => judgementResults[^2].Type == HitResult.Great && judgementResults.First().Type == HitResult.Miss;
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSpunOut.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSpunOut.cs
new file mode 100644
index 0000000000..e406f9ddff
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSpunOut.cs
@@ -0,0 +1,70 @@
+// 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.Generic;
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Graphics;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Osu.Mods;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
+using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ [TestFixture]
+ public class TestSceneSpinnerSpunOut : OsuTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(SpinnerDisc),
+ typeof(DrawableSpinner),
+ typeof(DrawableOsuHitObject),
+ typeof(OsuModSpunOut)
+ };
+
+ [SetUp]
+ public void SetUp() => Schedule(() =>
+ {
+ SelectedMods.Value = new[] { new OsuModSpunOut() };
+ });
+
+ [Test]
+ public void TestSpunOut()
+ {
+ DrawableSpinner spinner = null;
+
+ AddStep("create spinner", () => spinner = createSpinner());
+
+ AddUntilStep("wait for end", () => Time.Current > spinner.LifetimeEnd);
+
+ AddAssert("spinner is completed", () => spinner.Progress >= 1);
+ }
+
+ private DrawableSpinner createSpinner()
+ {
+ var spinner = new Spinner
+ {
+ StartTime = Time.Current + 500,
+ EndTime = Time.Current + 2500
+ };
+ spinner.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+
+ var drawableSpinner = new DrawableSpinner(spinner)
+ {
+ Anchor = Anchor.Centre
+ };
+
+ foreach (var mod in SelectedMods.Value.OfType())
+ mod.ApplyToDrawableHitObjects(new[] { drawableSpinner });
+
+ Add(drawableSpinner);
+ return drawableSpinner;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Judgements/OsuIgnoreJudgement.cs b/osu.Game.Rulesets.Osu/Judgements/OsuIgnoreJudgement.cs
new file mode 100644
index 0000000000..e528f65dca
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Judgements/OsuIgnoreJudgement.cs
@@ -0,0 +1,16 @@
+// 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.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Osu.Judgements
+{
+ public class OsuIgnoreJudgement : OsuJudgement
+ {
+ public override bool AffectsCombo => false;
+
+ protected override int NumericResultFor(HitResult result) => 0;
+
+ protected override double HealthIncreaseFor(HitResult result) => 0;
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs
index 75de6896a3..8228161008 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.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.Linq;
using osu.Framework.Bindables;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
@@ -30,6 +31,22 @@ namespace osu.Game.Rulesets.Osu.Mods
Value = 5,
};
+ public override string SettingDescription
+ {
+ get
+ {
+ string circleSize = CircleSize.IsDefault ? string.Empty : $"CS {CircleSize.Value:N1}";
+ string approachRate = ApproachRate.IsDefault ? string.Empty : $"AR {ApproachRate.Value:N1}";
+
+ return string.Join(", ", new[]
+ {
+ circleSize,
+ base.SettingDescription,
+ approachRate
+ }.Where(s => !string.IsNullOrEmpty(s)));
+ }
+ }
+
protected override void TransferSettings(BeatmapDifficulty difficulty)
{
base.TransferSettings(difficulty);
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs
index bc5f79331f..cf6677a55d 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs
@@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods
return;
slider.NestedHitObjects.OfType().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
- slider.NestedHitObjects.OfType().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
+ slider.NestedHitObjects.OfType().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
foreach (var point in slider.Path.ControlPoints)
point.Position.Value = new Vector2(point.Position.Value.X, -point.Position.Value.Y);
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs
index 9d5d300a9e..7b54baa99b 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs
@@ -2,21 +2,46 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Collections.Generic;
+using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
+using osu.Framework.Utils;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
namespace osu.Game.Rulesets.Osu.Mods
{
- public class OsuModSpunOut : Mod
+ public class OsuModSpunOut : Mod, IApplicableToDrawableHitObjects
{
public override string Name => "Spun Out";
public override string Acronym => "SO";
public override IconUsage? Icon => OsuIcon.ModSpunout;
- public override ModType Type => ModType.DifficultyReduction;
+ public override ModType Type => ModType.Automation;
public override string Description => @"Spinners will be automatically completed.";
public override double ScoreMultiplier => 0.9;
public override bool Ranked => true;
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(OsuModAutopilot) };
+
+ public void ApplyToDrawableHitObjects(IEnumerable drawables)
+ {
+ foreach (var hitObject in drawables)
+ {
+ if (hitObject is DrawableSpinner spinner)
+ {
+ spinner.HandleUserInput = false;
+ spinner.OnUpdate += onSpinnerUpdate;
+ }
+ }
+ }
+
+ private void onSpinnerUpdate(Drawable drawable)
+ {
+ var spinner = (DrawableSpinner)drawable;
+
+ spinner.Disc.Tracking = true;
+ spinner.Disc.Rotate(MathUtils.RadiansToDegrees((float)spinner.Clock.ElapsedFrameTime * 0.03f));
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs
index 41daef1f38..44dba7715a 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs
@@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Mods
case DrawableSliderHead _:
case DrawableSliderTail _:
case DrawableSliderTick _:
- case DrawableRepeatPoint _:
+ case DrawableSliderRepeat _:
return;
default:
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs
index cc2f4c3f70..297a0fea79 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs
@@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.Mods
// Wiggle the repeat points with the slider instead of independently.
// Also fixes an issue with repeat points being positioned incorrectly.
- if (osuObject is RepeatPoint)
+ if (osuObject is SliderRepeat)
return;
Random objRand = new Random((int)osuObject.StartTime);
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs
index 3e9c0f341b..d0935e46f7 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs
@@ -88,8 +88,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
private void refresh()
{
- ClearInternal();
-
OsuHitObject osuStart = Start.HitObject;
double startTime = osuStart.GetEndTime();
@@ -116,6 +114,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
double? firstTransformStartTime = null;
double finalTransformEndTime = startTime;
+ int point = 0;
+
for (int d = (int)(spacing * 1.5); d < distance - spacing; d += spacing)
{
float fraction = (float)d / distance;
@@ -126,13 +126,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
FollowPoint fp;
- AddInternal(fp = new FollowPoint
+ if (InternalChildren.Count > point)
{
- Position = pointStartPosition,
- Rotation = rotation,
- Alpha = 0,
- Scale = new Vector2(1.5f * osuEnd.Scale),
- });
+ fp = (FollowPoint)InternalChildren[point];
+ fp.ClearTransforms();
+ }
+ else
+ AddInternal(fp = new FollowPoint());
+
+ fp.Position = pointStartPosition;
+ fp.Rotation = rotation;
+ fp.Alpha = 0;
+ fp.Scale = new Vector2(1.5f * osuEnd.Scale);
if (firstTransformStartTime == null)
firstTransformStartTime = fadeInTime;
@@ -146,8 +151,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
finalTransformEndTime = fadeOutTime + osuEnd.TimeFadeIn;
}
+
+ point++;
}
+ int excessPoints = InternalChildren.Count - point;
+ for (int i = 0; i < excessPoints; i++)
+ RemoveInternal(InternalChildren[^1]);
+
// todo: use Expire() on FollowPoints and take lifetime from them when https://github.com/ppy/osu-framework/issues/3300 is fixed.
LifetimeStart = firstTransformStartTime ?? startTime;
LifetimeEnd = finalTransformEndTime;
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
index 7403649184..5c7f4a42b3 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
@@ -6,7 +6,6 @@ using osuTK;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
-using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers;
@@ -26,12 +25,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public readonly SliderBall Ball;
public readonly SkinnableDrawable Body;
+ public override bool DisplayResult => false;
+
private PlaySliderBody sliderBody => Body.Drawable as PlaySliderBody;
private readonly Container headContainer;
private readonly Container tailContainer;
private readonly Container tickContainer;
- private readonly Container repeatContainer;
+ private readonly Container repeatContainer;
private readonly Slider slider;
@@ -50,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
Body = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderBody), _ => new DefaultSliderBody(), confineMode: ConfineMode.NoScaling),
tickContainer = new Container { RelativeSizeAxes = Axes.Both },
- repeatContainer = new Container { RelativeSizeAxes = Axes.Both },
+ repeatContainer = new Container { RelativeSizeAxes = Axes.Both },
Ball = new SliderBall(s, this)
{
GetInitialHitAction = () => HeadCircle.HitAction,
@@ -100,7 +101,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
tickContainer.Add(tick);
break;
- case DrawableRepeatPoint repeat:
+ case DrawableSliderRepeat repeat:
repeatContainer.Add(repeat);
break;
}
@@ -129,8 +130,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
case SliderTick tick:
return new DrawableSliderTick(tick) { Position = tick.Position - slider.Position };
- case RepeatPoint repeat:
- return new DrawableRepeatPoint(repeat, this) { Position = repeat.Position - slider.Position };
+ case SliderRepeat repeat:
+ return new DrawableSliderRepeat(repeat, this) { Position = repeat.Position - slider.Position };
}
return base.CreateNestedHitObject(hitObject);
@@ -193,22 +194,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
if (userTriggered || Time.Current < slider.EndTime)
return;
- ApplyResult(r =>
- {
- var judgementsCount = NestedHitObjects.Count;
- var judgementsHit = NestedHitObjects.Count(h => h.IsHit);
+ ApplyResult(r => r.Type = r.Judgement.MaxResult);
+ }
- var hitFraction = (double)judgementsHit / judgementsCount;
-
- if (hitFraction == 1 && HeadCircle.Result.Type == HitResult.Great)
- r.Type = HitResult.Great;
- else if (hitFraction >= 0.5 && HeadCircle.Result.Type >= HitResult.Good)
- r.Type = HitResult.Good;
- else if (hitFraction > 0)
- r.Type = HitResult.Meh;
- else
- r.Type = HitResult.Miss;
- });
+ public override void PlaySamples()
+ {
+ // rather than doing it this way, we should probably attach the sample to the tail circle.
+ // this can only be done after we stop using LegacyLastTick.
+ if (TailCircle.Result.Type != HitResult.Miss)
+ base.PlaySamples();
}
protected override void UpdateStateTransforms(ArmedState state)
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs
similarity index 89%
rename from osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs
rename to osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs
index 8fdcd060e7..b9cee71ca1 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs
@@ -14,19 +14,19 @@ using osuTK;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
- public class DrawableRepeatPoint : DrawableOsuHitObject, ITrackSnaking
+ public class DrawableSliderRepeat : DrawableOsuHitObject, ITrackSnaking
{
- private readonly RepeatPoint repeatPoint;
+ private readonly SliderRepeat sliderRepeat;
private readonly DrawableSlider drawableSlider;
private double animDuration;
private readonly Drawable scaleContainer;
- public DrawableRepeatPoint(RepeatPoint repeatPoint, DrawableSlider drawableSlider)
- : base(repeatPoint)
+ public DrawableSliderRepeat(SliderRepeat sliderRepeat, DrawableSlider drawableSlider)
+ : base(sliderRepeat)
{
- this.repeatPoint = repeatPoint;
+ this.sliderRepeat = sliderRepeat;
this.drawableSlider = drawableSlider;
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
@@ -48,13 +48,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
- if (repeatPoint.StartTime <= Time.Current)
+ if (sliderRepeat.StartTime <= Time.Current)
ApplyResult(r => r.Type = drawableSlider.Tracking.Value ? HitResult.Great : HitResult.Miss);
}
protected override void UpdateInitialTransforms()
{
- animDuration = Math.Min(300, repeatPoint.SpanDuration);
+ animDuration = Math.Min(300, sliderRepeat.SpanDuration);
this.Animate(
d => d.FadeIn(animDuration),
@@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public void UpdateSnakingPosition(Vector2 start, Vector2 end)
{
- bool isRepeatAtEnd = repeatPoint.RepeatIndex % 2 == 0;
+ bool isRepeatAtEnd = sliderRepeat.RepeatIndex % 2 == 0;
List curve = ((PlaySliderBody)drawableSlider.Body.Drawable).CurrentCurve;
Position = isRepeatAtEnd ? end : start;
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs
index 21a3a0d236..29a4929c1b 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs
@@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
if (!userTriggered && timeOffset >= 0)
- ApplyResult(r => r.Type = Tracking ? HitResult.Great : HitResult.Miss);
+ ApplyResult(r => r.Type = Tracking ? r.Judgement.MaxResult : HitResult.Miss);
}
private void updatePosition() => Position = HitObject.Position - slider.Position;
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs
index 60b5c335d6..66eb60aa28 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs
@@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
if (timeOffset >= 0)
- ApplyResult(r => r.Type = Tracking ? HitResult.Great : HitResult.Miss);
+ ApplyResult(r => r.Type = Tracking ? r.Judgement.MaxResult : HitResult.Miss);
}
protected override void UpdateInitialTransforms()
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
index 0ec7f2ebfe..3c8ab0f5ab 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
@@ -176,17 +176,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected override void Update()
{
- Disc.Tracking = OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false;
- if (!SpmCounter.IsPresent && Disc.Tracking)
- SpmCounter.FadeIn(HitObject.TimeFadeIn);
-
base.Update();
+ if (HandleUserInput)
+ Disc.Tracking = OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false;
}
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
+ if (!SpmCounter.IsPresent && Disc.Tracking)
+ SpmCounter.FadeIn(HitObject.TimeFadeIn);
+
circle.Rotation = Disc.Rotation;
Ticks.Rotation = Disc.Rotation;
SpmCounter.SetRotation(Disc.RotationAbsolute);
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs
index e3dd2b1b4f..d4ef039b79 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs
@@ -73,6 +73,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
}
}
+ ///
+ /// Whether currently in the correct time range to allow spinning.
+ ///
+ private bool isSpinnableTime => spinner.StartTime <= Time.Current && spinner.EndTime > Time.Current;
+
protected override bool OnMouseMove(MouseMoveEvent e)
{
mousePosition = Parent.ToLocalSpace(e.ScreenSpaceMousePosition);
@@ -93,27 +98,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
protected override void Update()
{
base.Update();
-
var thisAngle = -MathUtils.RadiansToDegrees(MathF.Atan2(mousePosition.X - DrawSize.X / 2, mousePosition.Y - DrawSize.Y / 2));
- bool validAndTracking = tracking && spinner.StartTime <= Time.Current && spinner.EndTime > Time.Current;
+ var delta = thisAngle - lastAngle;
- if (validAndTracking)
- {
- if (!rotationTransferred)
- {
- currentRotation = Rotation * 2;
- rotationTransferred = true;
- }
-
- if (thisAngle - lastAngle > 180)
- lastAngle += 360;
- else if (lastAngle - thisAngle > 180)
- lastAngle -= 360;
-
- currentRotation += thisAngle - lastAngle;
- RotationAbsolute += Math.Abs(thisAngle - lastAngle) * Math.Sign(Clock.ElapsedFrameTime);
- }
+ if (tracking)
+ Rotate(delta);
lastAngle = thisAngle;
@@ -126,7 +116,40 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
.FadeTo(tracking_alpha, 250, Easing.OutQuint);
}
- this.RotateTo(currentRotation / 2, validAndTracking ? 500 : 1500, Easing.OutExpo);
+ Rotation = (float)Interpolation.Lerp(Rotation, currentRotation / 2, Math.Clamp(Math.Abs(Time.Elapsed) / 40, 0, 1));
+ }
+
+ ///
+ /// Rotate the disc by the provided angle (in addition to any existing rotation).
+ ///
+ ///
+ /// Will be a no-op if not a valid time to spin.
+ ///
+ /// The delta angle.
+ public void Rotate(float angle)
+ {
+ if (!isSpinnableTime)
+ return;
+
+ if (!rotationTransferred)
+ {
+ currentRotation = Rotation * 2;
+ rotationTransferred = true;
+ }
+
+ if (angle > 180)
+ {
+ lastAngle += 360;
+ angle -= 360;
+ }
+ else if (-angle > 180)
+ {
+ lastAngle -= 360;
+ angle += 360;
+ }
+
+ currentRotation += angle;
+ RotationAbsolute += Math.Abs(angle) * Math.Sign(Clock.ElapsedFrameTime);
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs
index 77f8ec6cc8..db1f46d8e2 100644
--- a/osu.Game.Rulesets.Osu/Objects/Slider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs
@@ -177,7 +177,7 @@ namespace osu.Game.Rulesets.Osu.Objects
break;
case SliderEventType.Repeat:
- AddNested(new RepeatPoint
+ AddNested(new SliderRepeat
{
RepeatIndex = e.SpanIndex,
SpanDuration = SpanDuration,
@@ -223,7 +223,7 @@ namespace osu.Game.Rulesets.Osu.Objects
foreach (var tick in NestedHitObjects.OfType())
tick.Samples = sampleList;
- foreach (var repeat in NestedHitObjects.OfType())
+ foreach (var repeat in NestedHitObjects.OfType())
repeat.Samples = getNodeSamples(repeat.RepeatIndex + 1);
if (HeadCircle != null)
@@ -233,7 +233,7 @@ namespace osu.Game.Rulesets.Osu.Objects
private IList getNodeSamples(int nodeIndex) =>
nodeIndex < NodeSamples.Count ? NodeSamples[nodeIndex] : Samples;
- public override Judgement CreateJudgement() => new OsuJudgement();
+ public override Judgement CreateJudgement() => new OsuIgnoreJudgement();
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
}
diff --git a/osu.Game.Rulesets.Osu/Objects/RepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs
similarity index 80%
rename from osu.Game.Rulesets.Osu/Objects/RepeatPoint.cs
rename to osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs
index a277517f9f..ac6c6905e4 100644
--- a/osu.Game.Rulesets.Osu/Objects/RepeatPoint.cs
+++ b/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs
@@ -10,7 +10,7 @@ using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects
{
- public class RepeatPoint : OsuHitObject
+ public class SliderRepeat : OsuHitObject
{
public int RepeatIndex { get; set; }
public double SpanDuration { get; set; }
@@ -28,8 +28,13 @@ namespace osu.Game.Rulesets.Osu.Objects
TimePreempt = Math.Min(SpanDuration * 2, TimePreempt);
}
- public override Judgement CreateJudgement() => new OsuJudgement();
-
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
+
+ public override Judgement CreateJudgement() => new SliderRepeatJudgement();
+
+ public class SliderRepeatJudgement : OsuJudgement
+ {
+ protected override int NumericResultFor(HitResult result) => result == MaxResult ? 30 : 0;
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs
index 127c36fcc0..c11e20c9e7 100644
--- a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs
+++ b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs
@@ -22,8 +22,8 @@ namespace osu.Game.Rulesets.Osu.Objects
pathVersion.BindValueChanged(_ => Position = slider.EndPosition);
}
- public override Judgement CreateJudgement() => new IgnoreJudgement();
-
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
+
+ public override Judgement CreateJudgement() => new SliderRepeat.SliderRepeatJudgement();
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs
index a49f4cef8b..22f3f559db 100644
--- a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs
+++ b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs
@@ -30,8 +30,13 @@ namespace osu.Game.Rulesets.Osu.Objects
TimePreempt = (StartTime - SpanStartTime) / 2 + offset;
}
- public override Judgement CreateJudgement() => new OsuJudgement();
-
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
+
+ public override Judgement CreateJudgement() => new SliderTickJudgement();
+
+ public class SliderTickJudgement : OsuJudgement
+ {
+ protected override int NumericResultFor(HitResult result) => result == MaxResult ? 10 : 0;
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/OsuInputManager.cs b/osu.Game.Rulesets.Osu/OsuInputManager.cs
index cdea7276f3..c8fe4f41ca 100644
--- a/osu.Game.Rulesets.Osu/OsuInputManager.cs
+++ b/osu.Game.Rulesets.Osu/OsuInputManager.cs
@@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu
///
public bool AllowUserCursorMovement { get; set; } = true;
- protected override RulesetKeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
+ protected override KeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
=> new OsuKeyBindingContainer(ruleset, variant, unique);
public OsuInputManager(RulesetInfo ruleset)
diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs
index 148869f5e8..a0f5b8fe01 100644
--- a/osu.Game.Rulesets.Osu/OsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs
@@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Osu
new KeyBinding(InputKey.MouseRight, OsuAction.RightButton),
};
- public override IEnumerable ConvertLegacyMods(LegacyMods mods)
+ public override IEnumerable ConvertFromLegacyMods(LegacyMods mods)
{
if (mods.HasFlag(LegacyMods.Nightcore))
yield return new OsuModNightcore();
@@ -113,7 +113,6 @@ namespace osu.Game.Rulesets.Osu
new OsuModEasy(),
new OsuModNoFail(),
new MultiMod(new OsuModHalfTime(), new OsuModDaycore()),
- new OsuModSpunOut(),
};
case ModType.DifficultyIncrease:
@@ -139,6 +138,7 @@ namespace osu.Game.Rulesets.Osu
new MultiMod(new OsuModAutoplay(), new OsuModCinema()),
new OsuModRelax(),
new OsuModAutopilot(),
+ new OsuModSpunOut(),
};
case ModType.Fun:
diff --git a/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs b/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs
index e6c6db5e61..3db81d70da 100644
--- a/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs
+++ b/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs
@@ -26,11 +26,23 @@ namespace osu.Game.Rulesets.Osu.Replays
Actions.AddRange(actions);
}
- public void ConvertFrom(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null)
+ public void FromLegacy(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null)
{
Position = currentFrame.Position;
if (currentFrame.MouseLeft) Actions.Add(OsuAction.LeftButton);
if (currentFrame.MouseRight) Actions.Add(OsuAction.RightButton);
}
+
+ public LegacyReplayFrame ToLegacy(IBeatmap beatmap)
+ {
+ ReplayButtonState state = ReplayButtonState.None;
+
+ if (Actions.Contains(OsuAction.LeftButton))
+ state |= ReplayButtonState.Left1;
+ if (Actions.Contains(OsuAction.RightButton))
+ state |= ReplayButtonState.Right1;
+
+ return new LegacyReplayFrame(Time, Position.X, Position.Y, state);
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs
index a37ef8d9a0..b4d51d11c9 100644
--- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs
@@ -58,6 +58,8 @@ namespace osu.Game.Rulesets.Osu.UI
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new OsuFramedReplayInputHandler(replay);
+ protected override ReplayRecorder CreateReplayRecorder(Replay replay) => new OsuReplayRecorder(replay);
+
public override double GameplayStartTime
{
get
diff --git a/osu.Game.Rulesets.Osu/UI/OsuReplayRecorder.cs b/osu.Game.Rulesets.Osu/UI/OsuReplayRecorder.cs
new file mode 100644
index 0000000000..b68ea136d5
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/UI/OsuReplayRecorder.cs
@@ -0,0 +1,23 @@
+// 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 osu.Game.Replays;
+using osu.Game.Rulesets.Osu.Replays;
+using osu.Game.Rulesets.Replays;
+using osu.Game.Rulesets.UI;
+using osuTK;
+
+namespace osu.Game.Rulesets.Osu.UI
+{
+ public class OsuReplayRecorder : ReplayRecorder
+ {
+ public OsuReplayRecorder(Replay replay)
+ : base(replay)
+ {
+ }
+
+ protected override ReplayFrame HandleFrame(Vector2 mousePosition, List actions, ReplayFrame previousFrame)
+ => new OsuReplayFrame(Time.Current, mousePosition, actions.ToArray());
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModPerfect.cs b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModPerfect.cs
index d3be2cdf0d..26c90ad295 100644
--- a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModPerfect.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModPerfect.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods
[TestCase(false)]
[TestCase(true)]
- public void TestHit(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new CentreHit { StartTime = 1000 }), shouldMiss);
+ public void TestHit(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new Hit { StartTime = 1000, Type = HitType.Centre }), shouldMiss);
[TestCase(false)]
[TestCase(true)]
diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
index f23fd6d3f9..8c26ca70ac 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
@@ -27,8 +27,8 @@ namespace osu.Game.Rulesets.Taiko.Tests
{
StartTime = hitObject.StartTime,
EndTime = hitObject.GetEndTime(),
- IsRim = hitObject is RimHit,
- IsCentre = hitObject is CentreHit,
+ IsRim = (hitObject as Hit)?.Type == HitType.Rim,
+ IsCentre = (hitObject as Hit)?.Type == HitType.Centre,
IsDrumRoll = hitObject is DrumRoll,
IsSwell = hitObject is Swell,
IsStrong = ((TaikoHitObject)hitObject).IsStrong
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs
index c01eef5252..0d9e813c60 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs
@@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
WorkingBeatmap beatmap = CreateWorkingBeatmap(new Beatmap
{
- HitObjects = new List { new CentreHit() },
+ HitObjects = new List { new Hit { Type = HitType.Centre } },
BeatmapInfo = new BeatmapInfo
{
BaseDifficulty = new BeatmapDifficulty(),
diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
index cc9d6e4470..695ada3a00 100644
--- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
@@ -124,24 +124,13 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
bool isRim = currentSamples.Any(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE);
strong = currentSamples.Any(s => s.Name == HitSampleInfo.HIT_FINISH);
- if (isRim)
+ yield return new Hit
{
- yield return new RimHit
- {
- StartTime = j,
- Samples = currentSamples,
- IsStrong = strong
- };
- }
- else
- {
- yield return new CentreHit
- {
- StartTime = j,
- Samples = currentSamples,
- IsStrong = strong
- };
- }
+ StartTime = j,
+ Type = isRim ? HitType.Rim : HitType.Centre,
+ Samples = currentSamples,
+ IsStrong = strong
+ };
i = (i + 1) % allSamples.Count;
}
@@ -180,24 +169,13 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
{
bool isRim = samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE);
- if (isRim)
+ yield return new Hit
{
- yield return new RimHit
- {
- StartTime = obj.StartTime,
- Samples = obj.Samples,
- IsStrong = strong
- };
- }
- else
- {
- yield return new CentreHit
- {
- StartTime = obj.StartTime,
- Samples = obj.Samples,
- IsStrong = strong
- };
- }
+ StartTime = obj.StartTime,
+ Type = isRim ? HitType.Rim : HitType.Centre,
+ Samples = obj.Samples,
+ IsStrong = strong
+ };
break;
}
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs
index 24345275c1..6807142327 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs
@@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing
public TaikoDifficultyHitObject(HitObject hitObject, HitObject lastObject, double clockRate)
: base(hitObject, lastObject, clockRate)
{
- HasTypeChange = lastObject is RimHit != hitObject is RimHit;
+ HasTypeChange = (lastObject as Hit)?.Type != (hitObject as Hit)?.Type;
}
}
}
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs
new file mode 100644
index 0000000000..1cf19ac18e
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs
@@ -0,0 +1,27 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Utils;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Taiko.Beatmaps;
+using osu.Game.Rulesets.Taiko.Objects;
+
+namespace osu.Game.Rulesets.Taiko.Mods
+{
+ public class TaikoModRandom : ModRandom, IApplicableToBeatmap
+ {
+ public override string Description => @"Shuffle around the colours!";
+
+ public void ApplyToBeatmap(IBeatmap beatmap)
+ {
+ var taikoBeatmap = (TaikoBeatmap)beatmap;
+
+ foreach (var obj in taikoBeatmap.HitObjects)
+ {
+ if (obj is Hit hit)
+ hit.Type = RNG.Next(2) == 0 ? HitType.Centre : HitType.Rim;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Objects/CentreHit.cs b/osu.Game.Rulesets.Taiko/Objects/CentreHit.cs
deleted file mode 100644
index a6354b16ed..0000000000
--- a/osu.Game.Rulesets.Taiko/Objects/CentreHit.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-namespace osu.Game.Rulesets.Taiko.Objects
-{
- public class CentreHit : Hit
- {
- }
-}
diff --git a/osu.Game.Rulesets.Taiko/Objects/Hit.cs b/osu.Game.Rulesets.Taiko/Objects/Hit.cs
index 6cc9357580..2aca701515 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Hit.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Hit.cs
@@ -5,5 +5,9 @@ namespace osu.Game.Rulesets.Taiko.Objects
{
public class Hit : TaikoHitObject
{
+ ///
+ /// The that actuates this .
+ ///
+ public HitType Type { get; set; }
}
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/HitType.cs b/osu.Game.Rulesets.Taiko/Objects/HitType.cs
new file mode 100644
index 0000000000..17b3fdbd04
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Objects/HitType.cs
@@ -0,0 +1,21 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Game.Rulesets.Taiko.Objects
+{
+ ///
+ /// The type of a .
+ ///
+ public enum HitType
+ {
+ ///
+ /// A that can be hit by the centre portion of the drum.
+ ///
+ Centre,
+
+ ///
+ /// A that can be hit by the rim portion of the drum.
+ ///
+ Rim
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Objects/RimHit.cs b/osu.Game.Rulesets.Taiko/Objects/RimHit.cs
deleted file mode 100644
index 6f6b089e03..0000000000
--- a/osu.Game.Rulesets.Taiko/Objects/RimHit.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-namespace osu.Game.Rulesets.Taiko.Objects
-{
- public class RimHit : Hit
- {
- }
-}
diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs
index 48eb33976e..273f4e4105 100644
--- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs
+++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs
@@ -97,7 +97,7 @@ namespace osu.Game.Rulesets.Taiko.Replays
{
TaikoAction[] actions;
- if (hit is CentreHit)
+ if (hit.Type == HitType.Centre)
{
actions = h.IsStrong
? new[] { TaikoAction.LeftCentre, TaikoAction.RightCentre }
diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs
index c5ebefc397..d2a7329a28 100644
--- a/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs
+++ b/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs
@@ -23,12 +23,24 @@ namespace osu.Game.Rulesets.Taiko.Replays
Actions.AddRange(actions);
}
- public void ConvertFrom(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null)
+ public void FromLegacy(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null)
{
if (currentFrame.MouseRight1) Actions.Add(TaikoAction.LeftRim);
if (currentFrame.MouseRight2) Actions.Add(TaikoAction.RightRim);
if (currentFrame.MouseLeft1) Actions.Add(TaikoAction.LeftCentre);
if (currentFrame.MouseLeft2) Actions.Add(TaikoAction.RightCentre);
}
+
+ public LegacyReplayFrame ToLegacy(IBeatmap beatmap)
+ {
+ ReplayButtonState state = ReplayButtonState.None;
+
+ if (Actions.Contains(TaikoAction.LeftRim)) state |= ReplayButtonState.Right1;
+ if (Actions.Contains(TaikoAction.RightRim)) state |= ReplayButtonState.Right2;
+ if (Actions.Contains(TaikoAction.LeftCentre)) state |= ReplayButtonState.Left1;
+ if (Actions.Contains(TaikoAction.RightCentre)) state |= ReplayButtonState.Left2;
+
+ return new LegacyReplayFrame(Time, null, null, state);
+ }
}
}
diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
index fc79e59864..a6c9a33569 100644
--- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
+++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
@@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Taiko
new KeyBinding(InputKey.K, TaikoAction.RightRim),
};
- public override IEnumerable ConvertLegacyMods(LegacyMods mods)
+ public override IEnumerable ConvertFromLegacyMods(LegacyMods mods)
{
if (mods.HasFlag(LegacyMods.Nightcore))
yield return new TaikoModNightcore();
@@ -114,6 +114,7 @@ namespace osu.Game.Rulesets.Taiko
case ModType.Conversion:
return new Mod[]
{
+ new TaikoModRandom(),
new TaikoModDifficultyAdjust(),
};
diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs
index 0c7495aa52..e4a4b555a7 100644
--- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs
+++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs
@@ -48,11 +48,11 @@ namespace osu.Game.Rulesets.Taiko.UI
{
switch (h)
{
- case CentreHit centreHit:
- return new DrawableCentreHit(centreHit);
-
- case RimHit rimHit:
- return new DrawableRimHit(rimHit);
+ case Hit hit:
+ if (hit.Type == HitType.Centre)
+ return new DrawableCentreHit(hit);
+ else
+ return new DrawableRimHit(hit);
case DrumRoll drumRoll:
return new DrawableDrumRoll(drumRoll);
@@ -65,5 +65,7 @@ namespace osu.Game.Rulesets.Taiko.UI
}
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new TaikoFramedReplayInputHandler(replay);
+
+ protected override ReplayRecorder CreateReplayRecorder(Replay replay) => new TaikoReplayRecorder(replay);
}
}
diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
index a10f70a344..bde9085c23 100644
--- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
+++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
@@ -14,9 +14,9 @@ using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
-using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Objects.Drawables;
using osu.Game.Rulesets.Taiko.Judgements;
+using osu.Game.Rulesets.Taiko.Objects;
using osuTK;
using osuTK.Graphics;
@@ -245,7 +245,7 @@ namespace osu.Game.Rulesets.Taiko.UI
if (!result.IsHit)
break;
- bool isRim = judgedObject.HitObject is RimHit;
+ bool isRim = (judgedObject.HitObject as Hit)?.Type == HitType.Rim;
hitExplosionContainer.Add(new HitExplosion(judgedObject, isRim));
diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoReplayRecorder.cs b/osu.Game.Rulesets.Taiko/UI/TaikoReplayRecorder.cs
new file mode 100644
index 0000000000..1859dabf03
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/UI/TaikoReplayRecorder.cs
@@ -0,0 +1,23 @@
+// 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 osu.Game.Replays;
+using osu.Game.Rulesets.Replays;
+using osu.Game.Rulesets.Taiko.Replays;
+using osu.Game.Rulesets.UI;
+using osuTK;
+
+namespace osu.Game.Rulesets.Taiko.UI
+{
+ public class TaikoReplayRecorder : ReplayRecorder
+ {
+ public TaikoReplayRecorder(Replay replay)
+ : base(replay)
+ {
+ }
+
+ protected override ReplayFrame HandleFrame(Vector2 mousePosition, List actions, ReplayFrame previousFrame) =>
+ new TaikoReplayFrame(Time.Current, actions.ToArray());
+ }
+}
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs
index 76b76aa357..2fdeadca02 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs
@@ -26,34 +26,34 @@ namespace osu.Game.Tests.Beatmaps.Formats
var storyboard = decoder.Decode(stream);
Assert.IsTrue(storyboard.HasDrawable);
- Assert.AreEqual(4, storyboard.Layers.Count());
+ Assert.AreEqual(5, storyboard.Layers.Count());
StoryboardLayer background = storyboard.Layers.FirstOrDefault(l => l.Depth == 3);
Assert.IsNotNull(background);
Assert.AreEqual(16, background.Elements.Count);
- Assert.IsTrue(background.EnabledWhenFailing);
- Assert.IsTrue(background.EnabledWhenPassing);
+ Assert.IsTrue(background.VisibleWhenFailing);
+ Assert.IsTrue(background.VisibleWhenPassing);
Assert.AreEqual("Background", background.Name);
StoryboardLayer fail = storyboard.Layers.FirstOrDefault(l => l.Depth == 2);
Assert.IsNotNull(fail);
Assert.AreEqual(0, fail.Elements.Count);
- Assert.IsTrue(fail.EnabledWhenFailing);
- Assert.IsFalse(fail.EnabledWhenPassing);
+ Assert.IsTrue(fail.VisibleWhenFailing);
+ Assert.IsFalse(fail.VisibleWhenPassing);
Assert.AreEqual("Fail", fail.Name);
StoryboardLayer pass = storyboard.Layers.FirstOrDefault(l => l.Depth == 1);
Assert.IsNotNull(pass);
Assert.AreEqual(0, pass.Elements.Count);
- Assert.IsFalse(pass.EnabledWhenFailing);
- Assert.IsTrue(pass.EnabledWhenPassing);
+ Assert.IsFalse(pass.VisibleWhenFailing);
+ Assert.IsTrue(pass.VisibleWhenPassing);
Assert.AreEqual("Pass", pass.Name);
StoryboardLayer foreground = storyboard.Layers.FirstOrDefault(l => l.Depth == 0);
Assert.IsNotNull(foreground);
Assert.AreEqual(151, foreground.Elements.Count);
- Assert.IsTrue(foreground.EnabledWhenFailing);
- Assert.IsTrue(foreground.EnabledWhenPassing);
+ Assert.IsTrue(foreground.VisibleWhenFailing);
+ Assert.IsTrue(foreground.VisibleWhenPassing);
Assert.AreEqual("Foreground", foreground.Name);
int spriteCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardSprite));
diff --git a/osu.Game.Tests/Resources/storyboard_no_video.osu b/osu.Game.Tests/Resources/storyboard_no_video.osu
new file mode 100644
index 0000000000..25f1ff6361
--- /dev/null
+++ b/osu.Game.Tests/Resources/storyboard_no_video.osu
@@ -0,0 +1,31 @@
+osu file format v14
+
+[Events]
+//Background and Video events
+0,0,"BG.jpg",0,0
+Video,0,"video.avi"
+//Break Periods
+//Storyboard Layer 0 (Background)
+//Storyboard Layer 1 (Fail)
+//Storyboard Layer 2 (Pass)
+//Storyboard Layer 3 (Foreground)
+//Storyboard Layer 4 (Overlay)
+//Storyboard Sound Samples
+
+[TimingPoints]
+1674,333.333333333333,4,2,1,70,1,0
+1674,-100,4,2,1,70,0,0
+3340,-100,4,2,1,70,0,0
+3507,-100,4,2,1,70,0,0
+3673,-100,4,2,1,70,0,0
+
+[Colours]
+Combo1 : 240,80,80
+Combo2 : 171,252,203
+Combo3 : 128,128,255
+Combo4 : 249,254,186
+
+[HitObjects]
+148,303,1674,5,6,3:2:0:0:
+378,252,1840,1,0,0:0:0:0:
+389,270,2340,5,2,0:1:0:0:
diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
index a139c3a8c2..90bf419644 100644
--- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
+++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
@@ -236,8 +236,6 @@ namespace osu.Game.Tests.Scores.IO
}
public override IEnumerable Filenames => new[] { "test_file.osr" };
-
- public override Stream GetUnderlyingStream() => new MemoryStream();
}
}
}
diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs
index b51555db3e..f97aa48f11 100644
--- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs
+++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs
@@ -37,7 +37,7 @@ using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Background
{
[TestFixture]
- public class TestSceneUserDimBackgrounds : ManualInputManagerTestScene
+ public class TestSceneUserDimBackgrounds : OsuManualInputManagerTestScene
{
public override IReadOnlyList RequiredTypes => new[]
{
@@ -278,6 +278,7 @@ namespace osu.Game.Tests.Visual.Background
private void setupUserSettings()
{
+ AddUntilStep("Song select is current", () => songSelect.IsCurrentScreen());
AddUntilStep("Song select has selection", () => songSelect.Carousel?.SelectedBeatmap != null);
AddStep("Set default user settings", () =>
{
diff --git a/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs b/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs
index 55aaeed8bf..4d64c7d35d 100644
--- a/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs
+++ b/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs
@@ -12,7 +12,7 @@ using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Components
{
[TestFixture]
- public class TestSceneIdleTracker : ManualInputManagerTestScene
+ public class TestSceneIdleTracker : OsuManualInputManagerTestScene
{
private IdleTrackingBox box1;
private IdleTrackingBox box2;
diff --git a/osu.Game.Tests/Visual/Editor/TestSceneBeatDivisorControl.cs b/osu.Game.Tests/Visual/Editor/TestSceneBeatDivisorControl.cs
index 7531a7be2c..fd7a5980f3 100644
--- a/osu.Game.Tests/Visual/Editor/TestSceneBeatDivisorControl.cs
+++ b/osu.Game.Tests/Visual/Editor/TestSceneBeatDivisorControl.cs
@@ -3,27 +3,83 @@
using System;
using System.Collections.Generic;
-using osu.Framework.Allocation;
+using System.Linq;
+using NUnit.Framework;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.UserInterface;
+using osu.Framework.Testing;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose.Components;
using osuTK;
+using osuTK.Input;
namespace osu.Game.Tests.Visual.Editor
{
- public class TestSceneBeatDivisorControl : OsuTestScene
+ public class TestSceneBeatDivisorControl : OsuManualInputManagerTestScene
{
public override IReadOnlyList RequiredTypes => new[] { typeof(BindableBeatDivisor) };
+ private BeatDivisorControl beatDivisorControl;
+ private BindableBeatDivisor bindableBeatDivisor;
- [BackgroundDependencyLoader]
- private void load()
+ private SliderBar tickSliderBar;
+ private EquilateralTriangle tickMarkerHead;
+
+ [SetUp]
+ public void SetUp() => Schedule(() =>
{
- Child = new BeatDivisorControl(new BindableBeatDivisor())
+ Child = beatDivisorControl = new BeatDivisorControl(bindableBeatDivisor = new BindableBeatDivisor(16))
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(90, 90)
};
+
+ tickSliderBar = beatDivisorControl.ChildrenOfType>().Single();
+ tickMarkerHead = tickSliderBar.ChildrenOfType().Single();
+ });
+
+ [Test]
+ public void TestBindableBeatDivisor()
+ {
+ AddRepeatStep("move previous", () => bindableBeatDivisor.Previous(), 4);
+ AddAssert("divisor is 4", () => bindableBeatDivisor.Value == 4);
+ AddRepeatStep("move next", () => bindableBeatDivisor.Next(), 3);
+ AddAssert("divisor is 12", () => bindableBeatDivisor.Value == 12);
+ }
+
+ [Test]
+ public void TestMouseInput()
+ {
+ AddStep("hold marker", () =>
+ {
+ InputManager.MoveMouseTo(tickMarkerHead.ScreenSpaceDrawQuad.Centre);
+ InputManager.PressButton(MouseButton.Left);
+ });
+ AddStep("move to 8 and release", () =>
+ {
+ InputManager.MoveMouseTo(tickSliderBar.ScreenSpaceDrawQuad.Centre);
+ InputManager.ReleaseButton(MouseButton.Left);
+ });
+ AddAssert("divisor is 8", () => bindableBeatDivisor.Value == 8);
+ AddStep("hold marker", () => InputManager.PressButton(MouseButton.Left));
+ AddStep("move to 16", () => InputManager.MoveMouseTo(getPositionForDivisor(16)));
+ AddStep("move to ~10 and release", () =>
+ {
+ InputManager.MoveMouseTo(getPositionForDivisor(10));
+ InputManager.ReleaseButton(MouseButton.Left);
+ });
+ AddAssert("divisor clamped to 8", () => bindableBeatDivisor.Value == 8);
+ }
+
+ private Vector2 getPositionForDivisor(int divisor)
+ {
+ var relativePosition = (float)Math.Clamp(divisor, 0, 16) / 16;
+ var sliderDrawQuad = tickSliderBar.ScreenSpaceDrawQuad;
+ return new Vector2(
+ sliderDrawQuad.TopLeft.X + sliderDrawQuad.Width * relativePosition,
+ sliderDrawQuad.Centre.Y
+ );
}
}
}
diff --git a/osu.Game.Tests/Visual/Editor/TestSceneZoomableScrollContainer.cs b/osu.Game.Tests/Visual/Editor/TestSceneZoomableScrollContainer.cs
index fd248abbc9..19d19c2759 100644
--- a/osu.Game.Tests/Visual/Editor/TestSceneZoomableScrollContainer.cs
+++ b/osu.Game.Tests/Visual/Editor/TestSceneZoomableScrollContainer.cs
@@ -17,7 +17,7 @@ using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Editor
{
- public class TestSceneZoomableScrollContainer : ManualInputManagerTestScene
+ public class TestSceneZoomableScrollContainer : OsuManualInputManagerTestScene
{
private ZoomableScrollContainer scrollContainer;
private Drawable innerBox;
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAllRulesetPlayers.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAllRulesetPlayers.cs
index 83a7b896d2..b7dcad3825 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneAllRulesetPlayers.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAllRulesetPlayers.cs
@@ -4,7 +4,6 @@
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
-using osu.Framework.Screens;
using osu.Game.Configuration;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Catch;
@@ -74,9 +73,6 @@ namespace osu.Game.Tests.Visual.Gameplay
Beatmap.Value = working;
SelectedMods.Value = new[] { ruleset.GetAllMods().First(m => m is ModNoFail) };
- Player?.Exit();
- Player = null;
-
Player = CreatePlayer(ruleset);
LoadScreen(Player);
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs
index afeda5fb7c..4b1c2ec256 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs
@@ -3,8 +3,14 @@
using System.ComponentModel;
using System.Linq;
+using osu.Framework.Testing;
+using osu.Game.Beatmaps.Timing;
using osu.Game.Rulesets;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play;
+using osu.Game.Screens.Play.Break;
+using osu.Game.Screens.Ranking;
namespace osu.Game.Tests.Visual.Gameplay
{
@@ -15,20 +21,38 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override Player CreatePlayer(Ruleset ruleset)
{
- SelectedMods.Value = SelectedMods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray();
- return new TestPlayer(false, false);
+ SelectedMods.Value = new[] { ruleset.GetAutoplayMod() };
+ return new TestPlayer(false);
}
protected override void AddCheckSteps()
{
AddUntilStep("score above zero", () => Player.ScoreProcessor.TotalScore.Value > 0);
AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 2));
- AddStep("seek to break time", () => Player.GameplayClockContainer.Seek(Player.BreakOverlay.Breaks.First().StartTime));
- AddUntilStep("wait for seek to complete", () =>
- Player.HUDOverlay.Progress.ReferenceClock.CurrentTime >= Player.BreakOverlay.Breaks.First().StartTime);
- AddAssert("test keys not counting", () => !Player.HUDOverlay.KeyCounter.IsCounting);
+ seekToBreak(0);
+ AddAssert("keys not counting", () => !Player.HUDOverlay.KeyCounter.IsCounting);
+ AddAssert("overlay displays 100% accuracy", () => Player.BreakOverlay.ChildrenOfType().Single().AccuracyDisplay.Current.Value == 1);
AddStep("rewind", () => Player.GameplayClockContainer.Seek(-80000));
AddUntilStep("key counter reset", () => Player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0));
+
+ seekToBreak(0);
+ seekToBreak(1);
+
+ AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime()));
+ AddUntilStep("results displayed", () => getResultsScreen() != null);
+
+ AddAssert("score has combo", () => getResultsScreen().Score.Combo > 100);
+ AddAssert("score has no misses", () => getResultsScreen().Score.Statistics[HitResult.Miss] == 0);
+
+ ResultsScreen getResultsScreen() => Stack.CurrentScreen as ResultsScreen;
+ }
+
+ private void seekToBreak(int breakIndex)
+ {
+ AddStep($"seek to break {breakIndex}", () => Player.GameplayClockContainer.Seek(destBreak().StartTime));
+ AddUntilStep("wait for seek to complete", () => Player.HUDOverlay.Progress.ReferenceClock.CurrentTime >= destBreak().StartTime);
+
+ BreakPeriod destBreak() => Player.ChildrenOfType().First().Breaks.ElementAt(breakIndex);
}
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs
similarity index 80%
rename from osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs
rename to osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs
index 19dce303ea..ff25e609c1 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs
@@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
+using osu.Framework.Graphics;
using osu.Framework.Timing;
using osu.Game.Beatmaps.Timing;
using osu.Game.Screens.Play;
@@ -12,14 +13,16 @@ using osu.Game.Screens.Play;
namespace osu.Game.Tests.Visual.Gameplay
{
[TestFixture]
- public class TestSceneBreakOverlay : OsuTestScene
+ public class TestSceneBreakTracker : OsuTestScene
{
public override IReadOnlyList RequiredTypes => new[]
{
typeof(BreakOverlay),
};
- private readonly TestBreakOverlay breakOverlay;
+ private readonly BreakOverlay breakOverlay;
+
+ private readonly TestBreakTracker breakTracker;
private readonly IReadOnlyList testBreaks = new List
{
@@ -35,9 +38,23 @@ namespace osu.Game.Tests.Visual.Gameplay
},
};
- public TestSceneBreakOverlay()
+ public TestSceneBreakTracker()
{
- Add(breakOverlay = new TestBreakOverlay(true));
+ AddRange(new Drawable[]
+ {
+ breakTracker = new TestBreakTracker(),
+ breakOverlay = new BreakOverlay(true, null)
+ {
+ ProcessCustomClock = false,
+ }
+ });
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ breakOverlay.Clock = breakTracker.Clock;
}
[Test]
@@ -88,7 +105,7 @@ namespace osu.Game.Tests.Visual.Gameplay
loadBreaksStep("multiple breaks", testBreaks);
seekAndAssertBreak("seek to break start", testBreaks[1].StartTime, true);
- AddAssert("is skipped to break #2", () => breakOverlay.CurrentBreakIndex == 1);
+ AddAssert("is skipped to break #2", () => breakTracker.CurrentBreakIndex == 1);
seekAndAssertBreak("seek to break middle", testBreaks[1].StartTime + testBreaks[1].Duration / 2, true);
seekAndAssertBreak("seek to break end", testBreaks[1].EndTime, false);
@@ -110,7 +127,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private void addShowBreakStep(double seconds)
{
- AddStep($"show '{seconds}s' break", () => breakOverlay.Breaks = new List
+ AddStep($"show '{seconds}s' break", () => breakOverlay.Breaks = breakTracker.Breaks = new List
{
new BreakPeriod
{
@@ -122,12 +139,12 @@ namespace osu.Game.Tests.Visual.Gameplay
private void setClock(bool useManual)
{
- AddStep($"set {(useManual ? "manual" : "realtime")} clock", () => breakOverlay.SwitchClock(useManual));
+ AddStep($"set {(useManual ? "manual" : "realtime")} clock", () => breakTracker.SwitchClock(useManual));
}
private void loadBreaksStep(string breakDescription, IReadOnlyList breaks)
{
- AddStep($"load {breakDescription}", () => breakOverlay.Breaks = breaks);
+ AddStep($"load {breakDescription}", () => breakOverlay.Breaks = breakTracker.Breaks = breaks);
seekAndAssertBreak("seek back to 0", 0, false);
}
@@ -151,17 +168,18 @@ namespace osu.Game.Tests.Visual.Gameplay
private void seekAndAssertBreak(string seekStepDescription, double time, bool shouldBeBreak)
{
- AddStep(seekStepDescription, () => breakOverlay.ManualClockTime = time);
+ AddStep(seekStepDescription, () => breakTracker.ManualClockTime = time);
AddAssert($"is{(!shouldBeBreak ? " not" : string.Empty)} break time", () =>
{
- breakOverlay.ProgressTime();
- return breakOverlay.IsBreakTime.Value == shouldBeBreak;
+ breakTracker.ProgressTime();
+ return breakTracker.IsBreakTime.Value == shouldBeBreak;
});
}
- private class TestBreakOverlay : BreakOverlay
+ private class TestBreakTracker : BreakTracker
{
- private readonly FramedClock framedManualClock;
+ public readonly FramedClock FramedManualClock;
+
private readonly ManualClock manualClock;
private IFrameBasedClock originalClock;
@@ -173,20 +191,19 @@ namespace osu.Game.Tests.Visual.Gameplay
set => manualClock.CurrentTime = value;
}
- public TestBreakOverlay(bool letterboxing)
- : base(letterboxing)
+ public TestBreakTracker()
{
- framedManualClock = new FramedClock(manualClock = new ManualClock());
+ FramedManualClock = new FramedClock(manualClock = new ManualClock());
ProcessCustomClock = false;
}
public void ProgressTime()
{
- framedManualClock.ProcessFrame();
+ FramedManualClock.ProcessFrame();
Update();
}
- public void SwitchClock(bool setManual) => Clock = setManual ? framedManualClock : originalClock;
+ public void SwitchClock(bool setManual) => Clock = setManual ? FramedManualClock : originalClock;
protected override void LoadComplete()
{
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs
index c1635ffc83..ea3e0c2293 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs
@@ -18,7 +18,7 @@ using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay
{
[Description("player pause/fail screens")]
- public class TestSceneGameplayMenuOverlay : ManualInputManagerTestScene
+ public class TestSceneGameplayMenuOverlay : OsuManualInputManagerTestScene
{
public override IReadOnlyList RequiredTypes => new[] { typeof(FailOverlay), typeof(PauseOverlay) };
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
index fc03dc6ed3..c192a7b0e0 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
@@ -15,7 +15,7 @@ using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay
{
- public class TestSceneHUDOverlay : ManualInputManagerTestScene
+ public class TestSceneHUDOverlay : OsuManualInputManagerTestScene
{
private HUDOverlay hudOverlay;
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs
index 0c5ead10cf..235842acc9 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs
@@ -13,7 +13,7 @@ using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay
{
[Description("'Hold to Quit' UI element")]
- public class TestSceneHoldForMenuButton : ManualInputManagerTestScene
+ public class TestSceneHoldForMenuButton : OsuManualInputManagerTestScene
{
private bool exitAction;
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs
index 227ada70fe..593dcd245c 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs
@@ -13,7 +13,7 @@ using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay
{
[TestFixture]
- public class TestSceneKeyCounter : ManualInputManagerTestScene
+ public class TestSceneKeyCounter : OsuManualInputManagerTestScene
{
public override IReadOnlyList RequiredTypes => new[]
{
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs
index 175f909a5a..4c73065087 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs
@@ -29,7 +29,7 @@ using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay
{
- public class TestScenePlayerLoader : ManualInputManagerTestScene
+ public class TestScenePlayerLoader : OsuManualInputManagerTestScene
{
private TestPlayerLoader loader;
private TestPlayerLoaderContainer container;
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs
new file mode 100644
index 0000000000..c7455583e4
--- /dev/null
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs
@@ -0,0 +1,281 @@
+// 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;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Input.Bindings;
+using osu.Framework.Input.Events;
+using osu.Framework.Input.StateChanges;
+using osu.Framework.Testing;
+using osu.Framework.Threading;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Replays;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Replays;
+using osu.Game.Rulesets.UI;
+using osu.Game.Tests.Visual.UserInterface;
+using osuTK;
+using osuTK.Graphics;
+using osuTK.Input;
+
+namespace osu.Game.Tests.Visual.Gameplay
+{
+ public class TestSceneReplayRecorder : OsuManualInputManagerTestScene
+ {
+ private TestRulesetInputManager playbackManager;
+ private TestRulesetInputManager recordingManager;
+
+ private Replay replay;
+
+ private TestReplayRecorder recorder;
+
+ [SetUp]
+ public void SetUp() => Schedule(() =>
+ {
+ replay = new Replay();
+
+ Add(new GridContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Content = new[]
+ {
+ new Drawable[]
+ {
+ recordingManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
+ {
+ Recorder = recorder = new TestReplayRecorder(replay)
+ {
+ ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos),
+ },
+ Child = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ Colour = Color4.Brown,
+ RelativeSizeAxes = Axes.Both,
+ },
+ new OsuSpriteText
+ {
+ Text = "Recording",
+ Scale = new Vector2(3),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ },
+ new TestInputConsumer()
+ }
+ },
+ }
+ },
+ new Drawable[]
+ {
+ playbackManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
+ {
+ ReplayInputHandler = new TestFramedReplayInputHandler(replay)
+ {
+ GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos),
+ },
+ Child = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ Colour = Color4.DarkBlue,
+ RelativeSizeAxes = Axes.Both,
+ },
+ new OsuSpriteText
+ {
+ Text = "Playback",
+ Scale = new Vector2(3),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ },
+ new TestInputConsumer()
+ }
+ },
+ }
+ }
+ }
+ });
+ });
+
+ [Test]
+ public void TestBasic()
+ {
+ AddStep("move to center", () => InputManager.MoveMouseTo(recordingManager.ScreenSpaceDrawQuad.Centre));
+ AddUntilStep("one frame recorded", () => replay.Frames.Count == 1);
+ AddAssert("position matches", () => playbackManager.ChildrenOfType().First().Position == recordingManager.ChildrenOfType().First().Position);
+ }
+
+ [Test]
+ public void TestHighFrameRate()
+ {
+ ScheduledDelegate moveFunction = null;
+
+ AddStep("move to center", () => InputManager.MoveMouseTo(recordingManager.ScreenSpaceDrawQuad.Centre));
+ AddStep("much move", () => moveFunction = Scheduler.AddDelayed(() =>
+ InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(-1, 0)), 10, true));
+ AddWaitStep("move", 10);
+ AddStep("stop move", () => moveFunction.Cancel());
+ AddAssert("at least 60 frames recorded", () => replay.Frames.Count > 60);
+ }
+
+ [Test]
+ public void TestLimitedFrameRate()
+ {
+ ScheduledDelegate moveFunction = null;
+
+ AddStep("lower rate", () => recorder.RecordFrameRate = 2);
+ AddStep("move to center", () => InputManager.MoveMouseTo(recordingManager.ScreenSpaceDrawQuad.Centre));
+ AddStep("much move", () => moveFunction = Scheduler.AddDelayed(() =>
+ InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(-1, 0)), 10, true));
+ AddWaitStep("move", 10);
+ AddStep("stop move", () => moveFunction.Cancel());
+ AddAssert("less than 10 frames recorded", () => replay.Frames.Count < 10);
+ }
+
+ [Test]
+ public void TestLimitedFrameRateWithImportantFrames()
+ {
+ ScheduledDelegate moveFunction = null;
+
+ AddStep("lower rate", () => recorder.RecordFrameRate = 2);
+ AddStep("move to center", () => InputManager.MoveMouseTo(recordingManager.ScreenSpaceDrawQuad.Centre));
+ AddStep("much move with press", () => moveFunction = Scheduler.AddDelayed(() =>
+ {
+ InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(-1, 0));
+ InputManager.PressButton(MouseButton.Left);
+ InputManager.ReleaseButton(MouseButton.Left);
+ }, 10, true));
+ AddWaitStep("move", 10);
+ AddStep("stop move", () => moveFunction.Cancel());
+ AddAssert("at least 60 frames recorded", () => replay.Frames.Count > 60);
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+ playbackManager?.ReplayInputHandler.SetFrameFromTime(Time.Current - 100);
+ }
+
+ public class TestFramedReplayInputHandler : FramedReplayInputHandler
+ {
+ public TestFramedReplayInputHandler(Replay replay)
+ : base(replay)
+ {
+ }
+
+ public override List GetPendingInputs()
+ {
+ return new List
+ {
+ new MousePositionAbsoluteInput
+ {
+ Position = GamefieldToScreenSpace(CurrentFrame?.Position ?? Vector2.Zero)
+ },
+ new ReplayState
+ {
+ PressedActions = CurrentFrame?.Actions ?? new List()
+ }
+ };
+ }
+ }
+
+ public class TestInputConsumer : CompositeDrawable, IKeyBindingHandler
+ {
+ public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parent.ReceivePositionalInputAt(screenSpacePos);
+
+ private readonly Box box;
+
+ public TestInputConsumer()
+ {
+ Size = new Vector2(30);
+
+ Origin = Anchor.Centre;
+
+ InternalChildren = new Drawable[]
+ {
+ box = new Box
+ {
+ Colour = Color4.Black,
+ RelativeSizeAxes = Axes.Both,
+ },
+ };
+ }
+
+ protected override bool OnMouseMove(MouseMoveEvent e)
+ {
+ Position = e.MousePosition;
+ return base.OnMouseMove(e);
+ }
+
+ public bool OnPressed(TestAction action)
+ {
+ box.Colour = Color4.White;
+ return true;
+ }
+
+ public void OnReleased(TestAction action)
+ {
+ box.Colour = Color4.Black;
+ }
+ }
+
+ public class TestRulesetInputManager : RulesetInputManager
+ {
+ public TestRulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
+ : base(ruleset, variant, unique)
+ {
+ }
+
+ protected override KeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
+ => new TestKeyBindingContainer();
+
+ internal class TestKeyBindingContainer : KeyBindingContainer
+ {
+ public override IEnumerable DefaultKeyBindings => new[]
+ {
+ new KeyBinding(InputKey.MouseLeft, TestAction.Down),
+ };
+ }
+ }
+
+ public class TestReplayFrame : ReplayFrame
+ {
+ public Vector2 Position;
+
+ public List Actions = new List();
+
+ public TestReplayFrame(double time, Vector2 position, params TestAction[] actions)
+ : base(time)
+ {
+ Position = position;
+ Actions.AddRange(actions);
+ }
+ }
+
+ public enum TestAction
+ {
+ Down,
+ }
+
+ internal class TestReplayRecorder : ReplayRecorder
+ {
+ public TestReplayRecorder(Replay target)
+ : base(target)
+ {
+ }
+
+ protected override ReplayFrame HandleFrame(Vector2 mousePosition, List actions, ReplayFrame previousFrame)
+ => new TestReplayFrame(Time.Current, mousePosition, actions.ToArray());
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs
new file mode 100644
index 0000000000..7822f07957
--- /dev/null
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs
@@ -0,0 +1,220 @@
+// 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 osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Input.Bindings;
+using osu.Framework.Input.Events;
+using osu.Framework.Input.StateChanges;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Replays;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Replays;
+using osu.Game.Rulesets.UI;
+using osu.Game.Tests.Visual.UserInterface;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Tests.Visual.Gameplay
+{
+ public class TestSceneReplayRecording : OsuTestScene
+ {
+ private readonly TestRulesetInputManager playbackManager;
+
+ private readonly TestRulesetInputManager recordingManager;
+
+ public TestSceneReplayRecording()
+ {
+ Replay replay = new Replay();
+
+ Add(new GridContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Content = new[]
+ {
+ new Drawable[]
+ {
+ recordingManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
+ {
+ Recorder = new TestReplayRecorder(replay)
+ {
+ ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos)
+ },
+ Child = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ Colour = Color4.Brown,
+ RelativeSizeAxes = Axes.Both,
+ },
+ new OsuSpriteText
+ {
+ Text = "Recording",
+ Scale = new Vector2(3),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ },
+ new TestConsumer()
+ }
+ },
+ }
+ },
+ new Drawable[]
+ {
+ playbackManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
+ {
+ ReplayInputHandler = new TestFramedReplayInputHandler(replay)
+ {
+ GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos),
+ },
+ Child = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ Colour = Color4.DarkBlue,
+ RelativeSizeAxes = Axes.Both,
+ },
+ new OsuSpriteText
+ {
+ Text = "Playback",
+ Scale = new Vector2(3),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ },
+ new TestConsumer()
+ }
+ },
+ }
+ }
+ }
+ });
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ playbackManager.ReplayInputHandler.SetFrameFromTime(Time.Current - 500);
+ }
+ }
+
+ public class TestFramedReplayInputHandler : FramedReplayInputHandler
+ {
+ public TestFramedReplayInputHandler(Replay replay)
+ : base(replay)
+ {
+ }
+
+ public override List GetPendingInputs()
+ {
+ return new List
+ {
+ new MousePositionAbsoluteInput
+ {
+ Position = GamefieldToScreenSpace(CurrentFrame?.Position ?? Vector2.Zero)
+ },
+ new ReplayState
+ {
+ PressedActions = CurrentFrame?.Actions ?? new List()
+ }
+ };
+ }
+ }
+
+ public class TestConsumer : CompositeDrawable, IKeyBindingHandler
+ {
+ public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parent.ReceivePositionalInputAt(screenSpacePos);
+
+ private readonly Box box;
+
+ public TestConsumer()
+ {
+ Size = new Vector2(30);
+
+ Origin = Anchor.Centre;
+
+ InternalChildren = new Drawable[]
+ {
+ box = new Box
+ {
+ Colour = Color4.Black,
+ RelativeSizeAxes = Axes.Both,
+ },
+ };
+ }
+
+ protected override bool OnMouseMove(MouseMoveEvent e)
+ {
+ Position = e.MousePosition;
+ return base.OnMouseMove(e);
+ }
+
+ public bool OnPressed(TestAction action)
+ {
+ box.Colour = Color4.White;
+ return true;
+ }
+
+ public void OnReleased(TestAction action)
+ {
+ box.Colour = Color4.Black;
+ }
+ }
+
+ public class TestRulesetInputManager : RulesetInputManager
+ {
+ public TestRulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
+ : base(ruleset, variant, unique)
+ {
+ }
+
+ protected override KeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
+ => new TestKeyBindingContainer();
+
+ internal class TestKeyBindingContainer : KeyBindingContainer
+ {
+ public override IEnumerable DefaultKeyBindings => new[]
+ {
+ new KeyBinding(InputKey.MouseLeft, TestAction.Down),
+ };
+ }
+ }
+
+ public class TestReplayFrame : ReplayFrame
+ {
+ public Vector2 Position;
+
+ public List Actions = new List();
+
+ public TestReplayFrame(double time, Vector2 position, params TestAction[] actions)
+ : base(time)
+ {
+ Position = position;
+ Actions.AddRange(actions);
+ }
+ }
+
+ public enum TestAction
+ {
+ Down,
+ }
+
+ internal class TestReplayRecorder : ReplayRecorder
+ {
+ public TestReplayRecorder(Replay target)
+ : base(target)
+ {
+ }
+
+ protected override ReplayFrame HandleFrame(Vector2 mousePosition, List actions, ReplayFrame previousFrame) =>
+ new TestReplayFrame(Time.Current, mousePosition, actions.ToArray());
+ }
+}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs
index 4c5c18f38a..6a0f86fe53 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs
@@ -14,7 +14,7 @@ using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay
{
[TestFixture]
- public class TestSceneSkipOverlay : ManualInputManagerTestScene
+ public class TestSceneSkipOverlay : OsuManualInputManagerTestScene
{
private SkipOverlay skip;
private int requestCount;
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs
index ff8437311e..9f1492a25f 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs
@@ -9,8 +9,12 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.Formats;
+using osu.Game.IO;
using osu.Game.Overlays;
+using osu.Game.Storyboards;
using osu.Game.Storyboards.Drawables;
+using osu.Game.Tests.Resources;
using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Gameplay
@@ -54,7 +58,11 @@ namespace osu.Game.Tests.Visual.Gameplay
State = { Value = Visibility.Visible },
}
});
+ }
+ [Test]
+ public void TestStoryboard()
+ {
AddStep("Restart", restart);
AddToggleStep("Passing", passing =>
{
@@ -62,6 +70,12 @@ namespace osu.Game.Tests.Visual.Gameplay
});
}
+ [Test]
+ public void TestStoryboardMissingVideo()
+ {
+ AddStep("Load storyboard with missing video", loadStoryboardNoVideo);
+ }
+
[BackgroundDependencyLoader]
private void load()
{
@@ -94,5 +108,28 @@ namespace osu.Game.Tests.Visual.Gameplay
storyboardContainer.Add(storyboard);
decoupledClock.ChangeSource(working.Track);
}
+
+ private void loadStoryboardNoVideo()
+ {
+ if (storyboard != null)
+ storyboardContainer.Remove(storyboard);
+
+ var decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = true };
+ storyboardContainer.Clock = decoupledClock;
+
+ Storyboard sb;
+
+ using (var str = TestResources.OpenResource("storyboard_no_video.osu"))
+ using (var bfr = new LineBufferedReader(str))
+ {
+ var decoder = new LegacyStoryboardDecoder();
+ sb = decoder.Decode(bfr);
+ }
+
+ storyboard = sb.CreateDrawable(Beatmap.Value);
+
+ storyboardContainer.Add(storyboard);
+ decoupledClock.ChangeSource(Beatmap.Value.Track);
+ }
}
}
diff --git a/osu.Game.Tests/Visual/Menus/IntroTestScene.cs b/osu.Game.Tests/Visual/Menus/IntroTestScene.cs
index 5870ef9813..1ad4d9dca9 100644
--- a/osu.Game.Tests/Visual/Menus/IntroTestScene.cs
+++ b/osu.Game.Tests/Visual/Menus/IntroTestScene.cs
@@ -64,6 +64,8 @@ namespace osu.Game.Tests.Visual.Menus
introStack.Push(CreateScreen());
});
+
+ AddUntilStep("wait for menu", () => introStack.CurrentScreen is MainMenu);
}
protected abstract IScreen CreateScreen();
diff --git a/osu.Game.Tests/Visual/Menus/TestSceneLoader.cs b/osu.Game.Tests/Visual/Menus/TestSceneLoader.cs
index b3064ba9be..c44363d9ea 100644
--- a/osu.Game.Tests/Visual/Menus/TestSceneLoader.cs
+++ b/osu.Game.Tests/Visual/Menus/TestSceneLoader.cs
@@ -36,8 +36,6 @@ namespace osu.Game.Tests.Visual.Menus
[Test]
public void TestInstantLoad()
{
- // visual only, very impossible to test this using asserts.
-
AddStep("load immediately", () =>
{
loader = new TestLoader();
@@ -46,12 +44,17 @@ namespace osu.Game.Tests.Visual.Menus
LoadScreen(loader);
});
- AddAssert("spinner did not display", () => loader.LoadingSpinner?.Alpha == 0);
+ spinnerNotPresentOrHidden();
AddUntilStep("loaded", () => loader.ScreenLoaded);
AddUntilStep("not current", () => !loader.IsCurrentScreen());
+
+ spinnerNotPresentOrHidden();
}
+ private void spinnerNotPresentOrHidden() =>
+ AddAssert("spinner did not display", () => loader.LoadingSpinner == null || loader.LoadingSpinner.Alpha == 0);
+
[Test]
public void TestDelayedLoad()
{
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs
index 9fbe8f7ffe..713ba13439 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs
@@ -20,7 +20,7 @@ using osuTK.Input;
namespace osu.Game.Tests.Visual.Multiplayer
{
- public class TestSceneDrawableRoomPlaylist : ManualInputManagerTestScene
+ public class TestSceneDrawableRoomPlaylist : OsuManualInputManagerTestScene
{
public override IReadOnlyList RequiredTypes => new[]
{
diff --git a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs
index 0d64eb651f..31afce86ae 100644
--- a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs
+++ b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs
@@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.Navigation
///
/// A scene which tests full game flow.
///
- public abstract class OsuGameTestScene : ManualInputManagerTestScene
+ public abstract class OsuGameTestScene : OsuManualInputManagerTestScene
{
private GameHost host;
diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs
index 6665452d94..14924dda21 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs
@@ -22,7 +22,7 @@ using osuTK.Input;
namespace osu.Game.Tests.Visual.Online
{
- public class TestSceneChatOverlay : ManualInputManagerTestScene
+ public class TestSceneChatOverlay : OsuManualInputManagerTestScene
{
public override IReadOnlyList RequiredTypes => new[]
{
diff --git a/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs
new file mode 100644
index 0000000000..cf365a7614
--- /dev/null
+++ b/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs
@@ -0,0 +1,87 @@
+// 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.Generic;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Overlays.Dashboard.Friends;
+using osu.Framework.Graphics;
+using osu.Game.Users;
+using osu.Game.Overlays;
+using osu.Framework.Allocation;
+using NUnit.Framework;
+
+namespace osu.Game.Tests.Visual.Online
+{
+ public class TestSceneFriendDisplay : OsuTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(FriendDisplay),
+ typeof(FriendOnlineStreamControl),
+ typeof(UserListToolbar)
+ };
+
+ protected override bool UseOnlineAPI => true;
+
+ [Cached]
+ private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
+
+ private FriendDisplay display;
+
+ [SetUp]
+ public void Setup() => Schedule(() =>
+ {
+ Child = new BasicScrollContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = display = new FriendDisplay()
+ };
+ });
+
+ [Test]
+ public void TestOffline()
+ {
+ AddStep("Populate", () => display.Users = getUsers());
+ }
+
+ [Test]
+ public void TestOnline()
+ {
+ AddStep("Fetch online", () => display?.Fetch());
+ }
+
+ private List getUsers() => new List
+ {
+ new User
+ {
+ Username = "flyte",
+ Id = 3103765,
+ IsOnline = true,
+ CurrentModeRank = 1111,
+ Country = new Country { FlagName = "JP" },
+ CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c6.jpg"
+ },
+ new User
+ {
+ Username = "peppy",
+ Id = 2,
+ IsOnline = false,
+ CurrentModeRank = 2222,
+ Country = new Country { FlagName = "AU" },
+ CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
+ IsSupporter = true,
+ SupportLevel = 3,
+ },
+ new User
+ {
+ Username = "Evast",
+ Id = 8195163,
+ Country = new Country { FlagName = "BY" },
+ CoverUrl = "https://assets.ppy.sh/user-profile-covers/8195163/4a8e2ad5a02a2642b631438cfa6c6bd7e2f9db289be881cb27df18331f64144c.jpeg",
+ IsOnline = false,
+ LastVisit = DateTimeOffset.Now
+ }
+ };
+ }
+}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs
index ccae778745..a38f045e7f 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs
@@ -28,7 +28,7 @@ namespace osu.Game.Tests.Visual.Online
private readonly Bindable status = new Bindable();
private UserGridPanel peppy;
- private UserListPanel evast;
+ private TestUserListPanel evast;
[Resolved]
private RulesetStore rulesetStore { get; set; }
@@ -38,6 +38,9 @@ namespace osu.Game.Tests.Visual.Online
{
UserGridPanel flyte;
+ activity.Value = null;
+ status.Value = null;
+
Child = new FillFlowContainer
{
Anchor = Anchor.Centre,
@@ -63,7 +66,7 @@ namespace osu.Game.Tests.Visual.Online
IsSupporter = true,
SupportLevel = 3,
}) { Width = 300 },
- evast = new UserListPanel(new User
+ evast = new TestUserListPanel(new User
{
Username = @"Evast",
Id = 8195163,
@@ -96,7 +99,7 @@ namespace osu.Game.Tests.Visual.Online
[Test]
public void TestUserActivity()
{
- AddStep("set online status", () => peppy.Status.Value = evast.Status.Value = new UserStatusOnline());
+ AddStep("set online status", () => status.Value = new UserStatusOnline());
AddStep("idle", () => activity.Value = null);
AddStep("spectating", () => activity.Value = new UserActivity.Spectating());
@@ -109,6 +112,29 @@ namespace osu.Game.Tests.Visual.Online
AddStep("modding", () => activity.Value = new UserActivity.Modding());
}
+ [Test]
+ public void TestUserActivityChange()
+ {
+ AddAssert("visit message is visible", () => evast.LastVisitMessage.IsPresent);
+ AddStep("set online status", () => status.Value = new UserStatusOnline());
+ AddAssert("visit message is not visible", () => !evast.LastVisitMessage.IsPresent);
+ AddStep("set choosing activity", () => activity.Value = new UserActivity.ChoosingBeatmap());
+ AddStep("set offline status", () => status.Value = new UserStatusOffline());
+ AddAssert("visit message is visible", () => evast.LastVisitMessage.IsPresent);
+ AddStep("set online status", () => status.Value = new UserStatusOnline());
+ AddAssert("visit message is not visible", () => !evast.LastVisitMessage.IsPresent);
+ }
+
private UserActivity soloGameStatusForRuleset(int rulesetId) => new UserActivity.SoloGame(null, rulesetStore.GetRuleset(rulesetId));
+
+ private class TestUserListPanel : UserListPanel
+ {
+ public TestUserListPanel(User user)
+ : base(user)
+ {
+ }
+
+ public new TextFlowContainer LastVisitMessage => base.LastVisitMessage;
+ }
}
}
diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs
index d0b9d43f51..0781cba924 100644
--- a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs
+++ b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs
@@ -32,6 +32,16 @@ namespace osu.Game.Tests.Visual.Ranking
typeof(SmoothCircularProgress)
};
+ [Test]
+ public void TestLowDRank()
+ {
+ var score = createScore();
+ score.Accuracy = 0.2;
+ score.Rank = ScoreRank.D;
+
+ addCircleStep(score);
+ }
+
[Test]
public void TestDRank()
{
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs
index 8df75c78f5..76a8ee9914 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs
@@ -83,6 +83,82 @@ namespace osu.Game.Tests.Visual.SongSelect
waitForSelection(set_count, 3);
}
+ [TestCase(true)]
+ [TestCase(false)]
+ public void TestTraversalBeyondVisible(bool forwards)
+ {
+ var sets = new List();
+
+ const int total_set_count = 200;
+
+ for (int i = 0; i < total_set_count; i++)
+ sets.Add(createTestBeatmapSet(i + 1));
+
+ loadBeatmaps(sets);
+
+ for (int i = 1; i < total_set_count; i += i)
+ selectNextAndAssert(i);
+
+ void selectNextAndAssert(int amount)
+ {
+ setSelected(forwards ? 1 : total_set_count, 1);
+
+ AddStep($"{(forwards ? "Next" : "Previous")} beatmap {amount} times", () =>
+ {
+ for (int i = 0; i < amount; i++)
+ {
+ carousel.SelectNext(forwards ? 1 : -1);
+ }
+ });
+
+ waitForSelection(forwards ? amount + 1 : total_set_count - amount);
+ }
+ }
+
+ [Test]
+ public void TestTraversalBeyondVisibleDifficulties()
+ {
+ var sets = new List();
+
+ const int total_set_count = 20;
+
+ for (int i = 0; i < total_set_count; i++)
+ sets.Add(createTestBeatmapSet(i + 1));
+
+ loadBeatmaps(sets);
+
+ // Selects next set once, difficulty index doesn't change
+ selectNextAndAssert(3, true, 2, 1);
+
+ // Selects next set 16 times (50 \ 3 == 16), difficulty index changes twice (50 % 3 == 2)
+ selectNextAndAssert(50, true, 17, 3);
+
+ // Travels around the carousel thrice (200 \ 60 == 3)
+ // continues to select 20 times (200 \ 60 == 20)
+ // selects next set 6 times (20 \ 3 == 6)
+ // difficulty index changes twice (20 % 3 == 2)
+ selectNextAndAssert(200, true, 7, 3);
+
+ // All same but in reverse
+ selectNextAndAssert(3, false, 19, 3);
+ selectNextAndAssert(50, false, 4, 1);
+ selectNextAndAssert(200, false, 14, 1);
+
+ void selectNextAndAssert(int amount, bool forwards, int expectedSet, int expectedDiff)
+ {
+ // Select very first or very last difficulty
+ setSelected(forwards ? 1 : 20, forwards ? 1 : 3);
+
+ AddStep($"{(forwards ? "Next" : "Previous")} difficulty {amount} times", () =>
+ {
+ for (int i = 0; i < amount; i++)
+ carousel.SelectNext(forwards ? 1 : -1, false);
+ });
+
+ waitForSelection(expectedSet, expectedDiff);
+ }
+ }
+
///
/// Test filtering
///
@@ -227,6 +303,34 @@ namespace osu.Game.Tests.Visual.SongSelect
waitForSelection(set_count);
}
+ [Test]
+ public void TestSelectionEnteringFromEmptyRuleset()
+ {
+ var sets = new List();
+
+ AddStep("Create beatmaps for taiko only", () =>
+ {
+ sets.Clear();
+
+ var rulesetBeatmapSet = createTestBeatmapSet(1);
+ var taikoRuleset = rulesets.AvailableRulesets.ElementAt(1);
+ rulesetBeatmapSet.Beatmaps.ForEach(b =>
+ {
+ b.Ruleset = taikoRuleset;
+ b.RulesetID = 1;
+ });
+
+ sets.Add(rulesetBeatmapSet);
+ });
+
+ loadBeatmaps(sets, () => new FilterCriteria { Ruleset = rulesets.AvailableRulesets.ElementAt(0) });
+
+ AddStep("Set non-empty mode filter", () =>
+ carousel.Filter(new FilterCriteria { Ruleset = rulesets.AvailableRulesets.ElementAt(1) }, false));
+
+ AddAssert("Something is selected", () => carousel.SelectedBeatmap != null);
+ }
+
///
/// Test sorting
///
@@ -399,27 +503,32 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("filter to ruleset 0", () =>
carousel.Filter(new FilterCriteria { Ruleset = rulesets.AvailableRulesets.ElementAt(0) }, false));
AddStep("select filtered map skipping filtered", () => carousel.SelectBeatmap(testMixed.Beatmaps[1], false));
- AddAssert("unfiltered beatmap not selected", () => carousel.SelectedBeatmap == null);
+ AddAssert("unfiltered beatmap not selected", () => carousel.SelectedBeatmap.RulesetID == 0);
AddStep("remove mixed set", () =>
{
carousel.RemoveBeatmapSet(testMixed);
testMixed = null;
});
- var testSingle = createTestBeatmapSet(set_count + 2);
- testSingle.Beatmaps.ForEach(b =>
+ BeatmapSetInfo testSingle = null;
+ AddStep("add single ruleset beatmapset", () =>
{
- b.Ruleset = rulesets.AvailableRulesets.ElementAt(1);
- b.RulesetID = b.Ruleset.ID ?? 1;
+ testSingle = createTestBeatmapSet(set_count + 2);
+ testSingle.Beatmaps.ForEach(b =>
+ {
+ b.Ruleset = rulesets.AvailableRulesets.ElementAt(1);
+ b.RulesetID = b.Ruleset.ID ?? 1;
+ });
+
+ carousel.UpdateBeatmapSet(testSingle);
});
- AddStep("add single ruleset beatmapset", () => carousel.UpdateBeatmapSet(testSingle));
AddStep("select filtered map skipping filtered", () => carousel.SelectBeatmap(testSingle.Beatmaps[0], false));
checkNoSelection();
AddStep("remove single ruleset set", () => carousel.RemoveBeatmapSet(testSingle));
}
[Test]
- public void TestCarouselRootIsRandom()
+ public void TestCarouselRemembersSelection()
{
List manySets = new List();
@@ -429,12 +538,74 @@ namespace osu.Game.Tests.Visual.SongSelect
loadBeatmaps(manySets);
advanceSelection(direction: 1, diff: false);
- checkNonmatchingFilter();
- checkNonmatchingFilter();
- checkNonmatchingFilter();
- checkNonmatchingFilter();
- checkNonmatchingFilter();
- AddAssert("Selection was random", () => eagerSelectedIDs.Count > 1);
+
+ for (int i = 0; i < 5; i++)
+ {
+ AddStep("Toggle non-matching filter", () =>
+ {
+ carousel.Filter(new FilterCriteria { SearchText = Guid.NewGuid().ToString() }, false);
+ });
+
+ AddStep("Restore no filter", () =>
+ {
+ carousel.Filter(new FilterCriteria(), false);
+ eagerSelectedIDs.Add(carousel.SelectedBeatmapSet.ID);
+ });
+ }
+
+ // always returns to same selection as long as it's available.
+ AddAssert("Selection was remembered", () => eagerSelectedIDs.Count == 1);
+ }
+
+ [Test]
+ public void TestRandomFallbackOnNonMatchingPrevious()
+ {
+ List manySets = new List();
+
+ AddStep("populate maps", () =>
+ {
+ for (int i = 0; i < 10; i++)
+ {
+ var set = createTestBeatmapSet(i);
+
+ foreach (var b in set.Beatmaps)
+ {
+ // all taiko except for first
+ int ruleset = i > 0 ? 1 : 0;
+
+ b.Ruleset = rulesets.GetRuleset(ruleset);
+ b.RulesetID = ruleset;
+ }
+
+ manySets.Add(set);
+ }
+ });
+
+ loadBeatmaps(manySets);
+
+ for (int i = 0; i < 10; i++)
+ {
+ AddStep("Reset filter", () => carousel.Filter(new FilterCriteria(), false));
+
+ AddStep("select first beatmap", () => carousel.SelectBeatmap(manySets.First().Beatmaps.First()));
+
+ AddStep("Toggle non-matching filter", () =>
+ {
+ carousel.Filter(new FilterCriteria { SearchText = Guid.NewGuid().ToString() }, false);
+ });
+
+ AddAssert("selection lost", () => carousel.SelectedBeatmap == null);
+
+ AddStep("Restore different ruleset filter", () =>
+ {
+ carousel.Filter(new FilterCriteria { Ruleset = rulesets.GetRuleset(1) }, false);
+ eagerSelectedIDs.Add(carousel.SelectedBeatmapSet.ID);
+ });
+
+ AddAssert("selection changed", () => carousel.SelectedBeatmap != manySets.First().Beatmaps.First());
+ }
+
+ AddAssert("Selection was random", () => eagerSelectedIDs.Count > 2);
}
[Test]
@@ -484,7 +655,7 @@ namespace osu.Game.Tests.Visual.SongSelect
checkVisibleItemCount(true, 15);
}
- private void loadBeatmaps(List beatmapSets = null)
+ private void loadBeatmaps(List beatmapSets = null, Func initialCriteria = null)
{
createCarousel();
@@ -499,7 +670,7 @@ namespace osu.Game.Tests.Visual.SongSelect
bool changed = false;
AddStep($"Load {(beatmapSets.Count > 0 ? beatmapSets.Count.ToString() : "some")} beatmaps", () =>
{
- carousel.Filter(new FilterCriteria());
+ carousel.Filter(initialCriteria?.Invoke() ?? new FilterCriteria());
carousel.BeatmapSetsChanged = () => changed = true;
carousel.BeatmapSets = beatmapSets;
});
@@ -593,16 +764,6 @@ namespace osu.Game.Tests.Visual.SongSelect
AddAssert("Selection is visible", selectedBeatmapVisible);
}
- private void checkNonmatchingFilter()
- {
- AddStep("Toggle non-matching filter", () =>
- {
- carousel.Filter(new FilterCriteria { SearchText = "Dingo" }, false);
- carousel.Filter(new FilterCriteria(), false);
- eagerSelectedIDs.Add(carousel.SelectedBeatmapSet.ID);
- });
- }
-
private BeatmapSetInfo createTestBeatmapSet(int id)
{
return new BeatmapSetInfo
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneCommentEditor.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneCommentEditor.cs
index 7b0b644dab..cef04a4c18 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneCommentEditor.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneCommentEditor.cs
@@ -15,7 +15,7 @@ using osuTK.Input;
namespace osu.Game.Tests.Visual.UserInterface
{
- public class TestSceneCommentEditor : ManualInputManagerTestScene
+ public class TestSceneCommentEditor : OsuManualInputManagerTestScene
{
public override IReadOnlyList RequiredTypes => new[]
{
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneCursors.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneCursors.cs
index d1dde4664a..5b74852259 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneCursors.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneCursors.cs
@@ -17,7 +17,7 @@ using osuTK.Graphics;
namespace osu.Game.Tests.Visual.UserInterface
{
[TestFixture]
- public class TestSceneCursors : ManualInputManagerTestScene
+ public class TestSceneCursors : OsuManualInputManagerTestScene
{
private readonly MenuCursorContainer menuCursorContainer;
private readonly CustomCursorBox[] cursorBoxes = new CustomCursorBox[6];
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs
index 1e5e26e4c5..a812b4dc79 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs
@@ -27,7 +27,7 @@ using osuTK.Input;
namespace osu.Game.Tests.Visual.UserInterface
{
- public class TestSceneDeleteLocalScore : ManualInputManagerTestScene
+ public class TestSceneDeleteLocalScore : OsuManualInputManagerTestScene
{
public override IReadOnlyList RequiredTypes => new[]
{
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFriendsOnlineStatusControl.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFriendsOnlineStatusControl.cs
index 0d841dfef1..f6dcf78d55 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneFriendsOnlineStatusControl.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFriendsOnlineStatusControl.cs
@@ -8,7 +8,7 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Overlays;
-using osu.Game.Overlays.Home.Friends;
+using osu.Game.Overlays.Dashboard.Friends;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.UserInterface
@@ -17,20 +17,20 @@ namespace osu.Game.Tests.Visual.UserInterface
{
public override IReadOnlyList RequiredTypes => new[]
{
- typeof(FriendsOnlineStatusControl),
+ typeof(FriendOnlineStreamControl),
typeof(FriendsOnlineStatusItem),
typeof(OverlayStreamControl<>),
typeof(OverlayStreamItem<>),
- typeof(FriendsBundle)
+ typeof(FriendStream)
};
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
- private FriendsOnlineStatusControl control;
+ private FriendOnlineStreamControl control;
[SetUp]
- public void SetUp() => Schedule(() => Child = control = new FriendsOnlineStatusControl
+ public void SetUp() => Schedule(() => Child = control = new FriendOnlineStreamControl
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -55,9 +55,9 @@ namespace osu.Game.Tests.Visual.UserInterface
}
}));
- AddAssert("3 users", () => control.Items.FirstOrDefault(item => item.Status == FriendsOnlineStatus.All)?.Count == 3);
- AddAssert("1 online user", () => control.Items.FirstOrDefault(item => item.Status == FriendsOnlineStatus.Online)?.Count == 1);
- AddAssert("2 offline users", () => control.Items.FirstOrDefault(item => item.Status == FriendsOnlineStatus.Offline)?.Count == 2);
+ AddAssert("3 users", () => control.Items.FirstOrDefault(item => item.Status == OnlineStatus.All)?.Count == 3);
+ AddAssert("1 online user", () => control.Items.FirstOrDefault(item => item.Status == OnlineStatus.Online)?.Count == 1);
+ AddAssert("2 offline users", () => control.Items.FirstOrDefault(item => item.Status == OnlineStatus.Offline)?.Count == 2);
}
}
}
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs
index d56324dbe8..03a19b6690 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs
@@ -91,13 +91,14 @@ namespace osu.Game.Tests.Visual.UserInterface
var easierMods = osu.GetModsFor(ModType.DifficultyReduction);
var harderMods = osu.GetModsFor(ModType.DifficultyIncrease);
+ var conversionMods = osu.GetModsFor(ModType.Conversion);
var noFailMod = osu.GetModsFor(ModType.DifficultyReduction).FirstOrDefault(m => m is OsuModNoFail);
var hiddenMod = harderMods.FirstOrDefault(m => m is OsuModHidden);
var doubleTimeMod = harderMods.OfType().FirstOrDefault(m => m.Mods.Any(a => a is OsuModDoubleTime));
- var spunOutMod = easierMods.FirstOrDefault(m => m is OsuModSpunOut);
+ var targetMod = conversionMods.FirstOrDefault(m => m is OsuModTarget);
var easy = easierMods.FirstOrDefault(m => m is OsuModEasy);
var hardRock = harderMods.FirstOrDefault(m => m is OsuModHardRock);
@@ -109,7 +110,7 @@ namespace osu.Game.Tests.Visual.UserInterface
testMultiplierTextColour(noFailMod, () => modSelect.LowMultiplierColour);
testMultiplierTextColour(hiddenMod, () => modSelect.HighMultiplierColour);
- testUnimplementedMod(spunOutMod);
+ testUnimplementedMod(targetMod);
}
[Test]
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuHoverContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuHoverContainer.cs
index dbef7d1686..396bec51b6 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuHoverContainer.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuHoverContainer.cs
@@ -12,7 +12,7 @@ using osuTK.Graphics;
namespace osu.Game.Tests.Visual.UserInterface
{
[TestFixture]
- public class TestSceneOsuHoverContainer : ManualInputManagerTestScene
+ public class TestSceneOsuHoverContainer : OsuManualInputManagerTestScene
{
private OsuHoverTestContainer hoverContainer;
private Box colourContainer;
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs
index 2ada5b927b..85fea73bf5 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs
@@ -12,7 +12,7 @@ using osuTK.Input;
namespace osu.Game.Tests.Visual.UserInterface
{
- public class TestSceneStatefulMenuItem : ManualInputManagerTestScene
+ public class TestSceneStatefulMenuItem : OsuManualInputManagerTestScene
{
public override IReadOnlyList RequiredTypes => new[]
{
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneSwitchButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneSwitchButton.cs
index 4a104b4a41..37fab75aee 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneSwitchButton.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneSwitchButton.cs
@@ -9,7 +9,7 @@ using osuTK.Input;
namespace osu.Game.Tests.Visual.UserInterface
{
- public class TestSceneSwitchButton : ManualInputManagerTestScene
+ public class TestSceneSwitchButton : OsuManualInputManagerTestScene
{
private SwitchButton switchButton;
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneUserListToolbar.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneUserListToolbar.cs
index 02b8839922..1546972580 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneUserListToolbar.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneUserListToolbar.cs
@@ -8,7 +8,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Overlays;
-using osu.Game.Overlays.Home.Friends;
+using osu.Game.Overlays.Dashboard.Friends;
using osuTK;
namespace osu.Game.Tests.Visual.UserInterface
diff --git a/osu.Game.Tests/WaveformTestBeatmap.cs b/osu.Game.Tests/WaveformTestBeatmap.cs
index 53ce5def32..90c91eb007 100644
--- a/osu.Game.Tests/WaveformTestBeatmap.cs
+++ b/osu.Game.Tests/WaveformTestBeatmap.cs
@@ -6,7 +6,6 @@ using System.Linq;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Textures;
-using osu.Framework.Graphics.Video;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Formats;
using osu.Game.IO;
@@ -51,8 +50,6 @@ namespace osu.Game.Tests
protected override Texture GetBackground() => null;
- protected override VideoSprite GetVideo() => null;
-
protected override Waveform GetWaveform() => new Waveform(trackStore.GetStream(firstAudioFile));
protected override Track GetTrack() => trackStore.Get(firstAudioFile);
diff --git a/osu.Game.Tournament.Tests/LadderTestScene.cs b/osu.Game.Tournament.Tests/LadderTestScene.cs
index dae0721023..b962d035ab 100644
--- a/osu.Game.Tournament.Tests/LadderTestScene.cs
+++ b/osu.Game.Tournament.Tests/LadderTestScene.cs
@@ -1,16 +1,147 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
+using osu.Framework.Utils;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets;
using osu.Game.Tournament.Models;
+using osu.Game.Users;
namespace osu.Game.Tournament.Tests
{
[TestFixture]
public abstract class LadderTestScene : TournamentTestScene
{
+ [Cached]
+ protected LadderInfo Ladder { get; private set; } = new LadderInfo();
+
[Resolved]
- protected LadderInfo Ladder { get; private set; }
+ private RulesetStore rulesetStore { get; set; }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ if (Ladder.Ruleset.Value == null)
+ Ladder.Ruleset.Value = rulesetStore.AvailableRulesets.First();
+
+ Ruleset.BindTo(Ladder.Ruleset);
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ TournamentMatch match = CreateSampleMatch();
+
+ Ladder.Rounds.Add(match.Round.Value);
+ Ladder.Matches.Add(match);
+ Ladder.Teams.Add(match.Team1.Value);
+ Ladder.Teams.Add(match.Team2.Value);
+
+ Ladder.CurrentMatch.Value = match;
+ }
+
+ public static TournamentMatch CreateSampleMatch() => new TournamentMatch
+ {
+ Team1 =
+ {
+ Value = new TournamentTeam
+ {
+ FlagName = { Value = "JP" },
+ FullName = { Value = "Japan" },
+ LastYearPlacing = { Value = 10 },
+ Seed = { Value = "Low" },
+ SeedingResults =
+ {
+ new SeedingResult
+ {
+ Mod = { Value = "NM" },
+ Seed = { Value = 10 },
+ Beatmaps =
+ {
+ new SeedingBeatmap
+ {
+ BeatmapInfo = CreateSampleBeatmapInfo(),
+ Score = 12345672,
+ Seed = { Value = 24 },
+ },
+ new SeedingBeatmap
+ {
+ BeatmapInfo = CreateSampleBeatmapInfo(),
+ Score = 1234567,
+ Seed = { Value = 12 },
+ },
+ new SeedingBeatmap
+ {
+ BeatmapInfo = CreateSampleBeatmapInfo(),
+ Score = 1234567,
+ Seed = { Value = 16 },
+ }
+ }
+ },
+ new SeedingResult
+ {
+ Mod = { Value = "DT" },
+ Seed = { Value = 5 },
+ Beatmaps =
+ {
+ new SeedingBeatmap
+ {
+ BeatmapInfo = CreateSampleBeatmapInfo(),
+ Score = 234567,
+ Seed = { Value = 3 },
+ },
+ new SeedingBeatmap
+ {
+ BeatmapInfo = CreateSampleBeatmapInfo(),
+ Score = 234567,
+ Seed = { Value = 6 },
+ },
+ new SeedingBeatmap
+ {
+ BeatmapInfo = CreateSampleBeatmapInfo(),
+ Score = 234567,
+ Seed = { Value = 12 },
+ }
+ }
+ }
+ },
+ Players =
+ {
+ new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 12 } } },
+ new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 16 } } },
+ new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 20 } } },
+ new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 24 } } },
+ new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 30 } } },
+ }
+ }
+ },
+ Team2 =
+ {
+ Value = new TournamentTeam
+ {
+ FlagName = { Value = "US" },
+ FullName = { Value = "United States" },
+ Players =
+ {
+ new User { Username = "Hello" },
+ new User { Username = "Hello" },
+ new User { Username = "Hello" },
+ new User { Username = "Hello" },
+ new User { Username = "Hello" },
+ }
+ }
+ },
+ Round =
+ {
+ Value = new TournamentRound { Name = { Value = "Quarterfinals" } }
+ }
+ };
+
+ public static BeatmapInfo CreateSampleBeatmapInfo() =>
+ new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist", ID = RNG.Next(0, 1000000) } };
}
}
diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs
index a7011c6d3c..a4538be384 100644
--- a/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs
+++ b/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs
@@ -1,24 +1,140 @@
// 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.Generic;
+using System.Linq;
+using NUnit.Framework;
using osu.Framework.Allocation;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Testing;
+using osu.Game.Tournament.Components;
+using osu.Game.Tournament.Models;
using osu.Game.Tournament.Screens.MapPool;
namespace osu.Game.Tournament.Tests.Screens
{
public class TestSceneMapPoolScreen : LadderTestScene
{
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(MapPoolScreen)
- };
+ private MapPoolScreen screen;
[BackgroundDependencyLoader]
private void load()
{
- Add(new MapPoolScreen { Width = 0.7f });
+ Add(screen = new MapPoolScreen { Width = 0.7f });
+ }
+
+ [Test]
+ public void TestFewMaps()
+ {
+ AddStep("load few maps", () =>
+ {
+ Ladder.CurrentMatch.Value.Round.Value.Beatmaps.Clear();
+
+ for (int i = 0; i < 8; i++)
+ addBeatmap();
+ });
+
+ AddStep("reset match", () =>
+ {
+ Ladder.CurrentMatch.Value = new TournamentMatch();
+ Ladder.CurrentMatch.Value = Ladder.Matches.First();
+ });
+
+ assertTwoWide();
+ }
+
+ [Test]
+ public void TestJustEnoughMaps()
+ {
+ AddStep("load just enough maps", () =>
+ {
+ Ladder.CurrentMatch.Value.Round.Value.Beatmaps.Clear();
+
+ for (int i = 0; i < 18; i++)
+ addBeatmap();
+ });
+
+ AddStep("reset match", () =>
+ {
+ Ladder.CurrentMatch.Value = new TournamentMatch();
+ Ladder.CurrentMatch.Value = Ladder.Matches.First();
+ });
+
+ assertTwoWide();
+ }
+
+ [Test]
+ public void TestManyMaps()
+ {
+ AddStep("load many maps", () =>
+ {
+ Ladder.CurrentMatch.Value.Round.Value.Beatmaps.Clear();
+
+ for (int i = 0; i < 19; i++)
+ addBeatmap();
+ });
+
+ AddStep("reset match", () =>
+ {
+ Ladder.CurrentMatch.Value = new TournamentMatch();
+ Ladder.CurrentMatch.Value = Ladder.Matches.First();
+ });
+
+ assertThreeWide();
+ }
+
+ [Test]
+ public void TestJustEnoughMods()
+ {
+ AddStep("load many maps", () =>
+ {
+ Ladder.CurrentMatch.Value.Round.Value.Beatmaps.Clear();
+
+ for (int i = 0; i < 11; i++)
+ addBeatmap(i > 4 ? $"M{i}" : "NM");
+ });
+
+ AddStep("reset match", () =>
+ {
+ Ladder.CurrentMatch.Value = new TournamentMatch();
+ Ladder.CurrentMatch.Value = Ladder.Matches.First();
+ });
+
+ assertTwoWide();
+ }
+
+ private void assertTwoWide() =>
+ AddAssert("ensure layout width is 2", () => screen.ChildrenOfType>>().First().Padding.Left > 0);
+
+ private void assertThreeWide() =>
+ AddAssert("ensure layout width is 3", () => screen.ChildrenOfType>>().First().Padding.Left == 0);
+
+ [Test]
+ public void TestManyMods()
+ {
+ AddStep("load many maps", () =>
+ {
+ Ladder.CurrentMatch.Value.Round.Value.Beatmaps.Clear();
+
+ for (int i = 0; i < 12; i++)
+ addBeatmap(i > 4 ? $"M{i}" : "NM");
+ });
+
+ AddStep("reset match", () =>
+ {
+ Ladder.CurrentMatch.Value = new TournamentMatch();
+ Ladder.CurrentMatch.Value = Ladder.Matches.First();
+ });
+
+ assertThreeWide();
+ }
+
+ private void addBeatmap(string mods = "nm")
+ {
+ Ladder.CurrentMatch.Value.Round.Value.Beatmaps.Add(new RoundBeatmap
+ {
+ BeatmapInfo = CreateSampleBeatmapInfo(),
+ Mods = mods
+ });
}
}
}
diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneSeedingEditorScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneSeedingEditorScreen.cs
index 014cd4663b..17cccd34b6 100644
--- a/osu.Game.Tournament.Tests/Screens/TestSceneSeedingEditorScreen.cs
+++ b/osu.Game.Tournament.Tests/Screens/TestSceneSeedingEditorScreen.cs
@@ -14,7 +14,7 @@ namespace osu.Game.Tournament.Tests.Screens
public TestSceneSeedingEditorScreen()
{
- var match = TestSceneSeedingScreen.CreateSampleSeededMatch();
+ var match = CreateSampleMatch();
Add(new SeedingEditorScreen(match.Team1.Value)
{
diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneSeedingScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneSeedingScreen.cs
index 335a6c80a1..4269f8f56a 100644
--- a/osu.Game.Tournament.Tests/Screens/TestSceneSeedingScreen.cs
+++ b/osu.Game.Tournament.Tests/Screens/TestSceneSeedingScreen.cs
@@ -3,10 +3,8 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
-using osu.Game.Beatmaps;
using osu.Game.Tournament.Models;
using osu.Game.Tournament.Screens.TeamIntro;
-using osu.Game.Users;
namespace osu.Game.Tournament.Tests.Screens
{
@@ -18,110 +16,11 @@ namespace osu.Game.Tournament.Tests.Screens
[BackgroundDependencyLoader]
private void load()
{
- ladder.CurrentMatch.Value = CreateSampleSeededMatch();
-
Add(new SeedingScreen
{
FillMode = FillMode.Fit,
FillAspectRatio = 16 / 9f
});
}
-
- public static TournamentMatch CreateSampleSeededMatch() => new TournamentMatch
- {
- Team1 =
- {
- Value = new TournamentTeam
- {
- FlagName = { Value = "JP" },
- FullName = { Value = "Japan" },
- LastYearPlacing = { Value = 10 },
- Seed = { Value = "Low" },
- SeedingResults =
- {
- new SeedingResult
- {
- Mod = { Value = "NM" },
- Seed = { Value = 10 },
- Beatmaps =
- {
- new SeedingBeatmap
- {
- BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist" } },
- Score = 12345672,
- Seed = { Value = 24 },
- },
- new SeedingBeatmap
- {
- BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist" } },
- Score = 1234567,
- Seed = { Value = 12 },
- },
- new SeedingBeatmap
- {
- BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist" } },
- Score = 1234567,
- Seed = { Value = 16 },
- }
- }
- },
- new SeedingResult
- {
- Mod = { Value = "DT" },
- Seed = { Value = 5 },
- Beatmaps =
- {
- new SeedingBeatmap
- {
- BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist" } },
- Score = 234567,
- Seed = { Value = 3 },
- },
- new SeedingBeatmap
- {
- BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist" } },
- Score = 234567,
- Seed = { Value = 6 },
- },
- new SeedingBeatmap
- {
- BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist" } },
- Score = 234567,
- Seed = { Value = 12 },
- }
- }
- }
- },
- Players =
- {
- new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 12 } } },
- new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 16 } } },
- new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 20 } } },
- new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 24 } } },
- new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 30 } } },
- }
- }
- },
- Team2 =
- {
- Value = new TournamentTeam
- {
- FlagName = { Value = "US" },
- FullName = { Value = "United States" },
- Players =
- {
- new User { Username = "Hello" },
- new User { Username = "Hello" },
- new User { Username = "Hello" },
- new User { Username = "Hello" },
- new User { Username = "Hello" },
- }
- }
- },
- Round =
- {
- Value = new TournamentRound { Name = { Value = "Quarterfinals" } }
- }
- };
}
}
diff --git a/osu.Game.Tournament/Components/TournamentMatchChatDisplay.cs b/osu.Game.Tournament/Components/TournamentMatchChatDisplay.cs
index 2a183d0d45..fe22d1e76d 100644
--- a/osu.Game.Tournament/Components/TournamentMatchChatDisplay.cs
+++ b/osu.Game.Tournament/Components/TournamentMatchChatDisplay.cs
@@ -70,6 +70,17 @@ namespace osu.Game.Tournament.Components
protected override ChatLine CreateMessage(Message message) => new MatchMessage(message);
+ protected override StandAloneDrawableChannel CreateDrawableChannel(Channel channel) => new MatchChannel(channel);
+
+ public class MatchChannel : StandAloneDrawableChannel
+ {
+ public MatchChannel(Channel channel)
+ : base(channel)
+ {
+ ScrollbarVisible = false;
+ }
+ }
+
protected class MatchMessage : StandAloneMessage
{
public MatchMessage(Message message)
diff --git a/osu.Game.Tournament/Models/LadderInfo.cs b/osu.Game.Tournament/Models/LadderInfo.cs
index 5db0b01547..c2e6da9ca5 100644
--- a/osu.Game.Tournament/Models/LadderInfo.cs
+++ b/osu.Game.Tournament/Models/LadderInfo.cs
@@ -24,7 +24,13 @@ namespace osu.Game.Tournament.Models
// only used for serialisation
public List Progressions = new List();
- [JsonIgnore]
+ [JsonIgnore] // updated manually in TournamentGameBase
public Bindable CurrentMatch = new Bindable();
+
+ public Bindable ChromaKeyWidth = new BindableInt(1024)
+ {
+ MinValue = 640,
+ MaxValue = 1366,
+ };
}
}
diff --git a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs
index 8920990d1b..64a5cd6dec 100644
--- a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs
+++ b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs
@@ -9,6 +9,7 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Platform;
using osu.Framework.Threading;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Overlays.Settings;
using osu.Game.Tournament.Components;
using osu.Game.Tournament.IPC;
using osu.Game.Tournament.Models;
@@ -35,6 +36,8 @@ namespace osu.Game.Tournament.Screens.Gameplay
[Resolved]
private TournamentMatchChatDisplay chat { get; set; }
+ private Box chroma;
+
[BackgroundDependencyLoader]
private void load(LadderInfo ladder, MatchIPCInfo ipc, Storage storage)
{
@@ -60,11 +63,10 @@ namespace osu.Game.Tournament.Screens.Gameplay
Origin = Anchor.TopCentre,
Children = new Drawable[]
{
- new Box
+ chroma = new Box
{
// chroma key area for stable gameplay
Name = "chroma",
- RelativeSizeAxes = Axes.X,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Height = 512,
@@ -93,6 +95,12 @@ namespace osu.Game.Tournament.Screens.Gameplay
RelativeSizeAxes = Axes.X,
Text = "Toggle chat",
Action = () => { State.Value = State.Value == TourneyState.Idle ? TourneyState.Playing : TourneyState.Idle; }
+ },
+ new SettingsSlider
+ {
+ LabelText = "Chroma Width",
+ Bindable = LadderInfo.ChromaKeyWidth,
+ KeyboardStep = 1,
}
}
}
@@ -101,6 +109,8 @@ namespace osu.Game.Tournament.Screens.Gameplay
State.BindTo(ipc.State);
State.BindValueChanged(stateChanged, true);
+ ladder.ChromaKeyWidth.BindValueChanged(width => chroma.Width = width.NewValue, true);
+
currentMatch.BindValueChanged(m =>
{
warmup.Value = m.NewValue.Team1Score.Value + m.NewValue.Team2Score.Value == 0;
diff --git a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs
index 2b0bfe0b74..b4c6d589d7 100644
--- a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs
+++ b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs
@@ -50,11 +50,11 @@ namespace osu.Game.Tournament.Screens.MapPool
new MatchHeader(),
mapFlows = new FillFlowContainer>
{
- Y = 140,
+ Y = 160,
Spacing = new Vector2(10, 10),
- Padding = new MarginPadding(25),
Direction = FillDirection.Vertical,
- RelativeSizeAxes = Axes.Both,
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
},
new ControlPanel
{
@@ -95,6 +95,7 @@ namespace osu.Game.Tournament.Screens.MapPool
Text = "Reset",
Action = reset
},
+ new ControlPanel.Spacer(),
}
}
};
@@ -211,11 +212,15 @@ namespace osu.Game.Tournament.Screens.MapPool
{
mapFlows.Clear();
+ int totalRows = 0;
+
if (match.NewValue.Round.Value != null)
{
FillFlowContainer currentFlow = null;
string currentMod = null;
+ int flowCount = 0;
+
foreach (var b in match.NewValue.Round.Value.Beatmaps)
{
if (currentFlow == null || currentMod != b.Mods)
@@ -229,6 +234,15 @@ namespace osu.Game.Tournament.Screens.MapPool
});
currentMod = b.Mods;
+
+ totalRows++;
+ flowCount = 0;
+ }
+
+ if (++flowCount > 2)
+ {
+ totalRows++;
+ flowCount = 1;
}
currentFlow.Add(new TournamentBeatmapPanel(b.BeatmapInfo, b.Mods)
@@ -239,6 +253,12 @@ namespace osu.Game.Tournament.Screens.MapPool
});
}
}
+
+ mapFlows.Padding = new MarginPadding(5)
+ {
+ // remove horizontal padding to increase flow width to 3 panels
+ Horizontal = totalRows > 9 ? 0 : 100
+ };
}
}
}
diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs
index b7f8b2bfd6..c91379b2d6 100644
--- a/osu.Game.Tournament/Screens/SetupScreen.cs
+++ b/osu.Game.Tournament/Screens/SetupScreen.cs
@@ -116,7 +116,7 @@ namespace osu.Game.Tournament.Screens
{
windowSize.Value = new Size((int)(1920 / TournamentSceneManager.STREAM_AREA_WIDTH * TournamentSceneManager.REQUIRED_WIDTH), 1080);
}
- }
+ },
};
}
diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs
index 31869f9310..abb3f8ac42 100644
--- a/osu.Game/Beatmaps/BeatmapManager.cs
+++ b/osu.Game/Beatmaps/BeatmapManager.cs
@@ -14,7 +14,6 @@ using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Extensions;
using osu.Framework.Graphics.Textures;
-using osu.Framework.Graphics.Video;
using osu.Framework.Lists;
using osu.Framework.Logging;
using osu.Framework.Platform;
@@ -403,7 +402,6 @@ namespace osu.Game.Beatmaps
protected override IBeatmap GetBeatmap() => beatmap;
protected override Texture GetBackground() => null;
- protected override VideoSprite GetVideo() => null;
protected override Track GetTrack() => null;
}
diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
index 1991770518..e62a9bb39d 100644
--- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
@@ -6,7 +6,6 @@ using System.Linq;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Textures;
-using osu.Framework.Graphics.Video;
using osu.Framework.IO.Stores;
using osu.Framework.Logging;
using osu.Game.Beatmaps.Formats;
@@ -67,24 +66,6 @@ namespace osu.Game.Beatmaps
}
}
- protected override VideoSprite GetVideo()
- {
- if (Metadata?.VideoFile == null)
- return null;
-
- try
- {
- var stream = textureStore.GetStream(getPathForFile(Metadata.VideoFile));
-
- return stream == null ? null : new VideoSprite(stream);
- }
- catch (Exception e)
- {
- Logger.Error(e, "Video failed to load");
- return null;
- }
- }
-
protected override Track GetTrack()
{
try
diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs
index 9267527d79..001f319307 100644
--- a/osu.Game/Beatmaps/BeatmapMetadata.cs
+++ b/osu.Game/Beatmaps/BeatmapMetadata.cs
@@ -52,7 +52,6 @@ namespace osu.Game.Beatmaps
public int PreviewTime { get; set; }
public string AudioFile { get; set; }
public string BackgroundFile { get; set; }
- public string VideoFile { get; set; }
public override string ToString() => $"{Artist} - {Title} ({Author})";
@@ -82,8 +81,7 @@ namespace osu.Game.Beatmaps
&& Tags == other.Tags
&& PreviewTime == other.PreviewTime
&& AudioFile == other.AudioFile
- && BackgroundFile == other.BackgroundFile
- && VideoFile == other.VideoFile;
+ && BackgroundFile == other.BackgroundFile;
}
}
}
diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
index bfcc38e4a9..8080e94075 100644
--- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
@@ -7,7 +7,6 @@ using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics.Textures;
-using osu.Framework.Graphics.Video;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
@@ -45,8 +44,6 @@ namespace osu.Game.Beatmaps
protected override Texture GetBackground() => textures?.Get(@"Backgrounds/bg4");
- protected override VideoSprite GetVideo() => null;
-
protected override Track GetTrack() => GetVirtualTrack();
private class DummyRulesetInfo : RulesetInfo
diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
index 4b01b2490e..f5b27eddd2 100644
--- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
@@ -6,11 +6,11 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using osu.Framework.Extensions;
-using osu.Game.Beatmaps.Timing;
-using osu.Game.Rulesets.Objects.Legacy;
using osu.Game.Beatmaps.ControlPoints;
-using osu.Game.IO;
using osu.Game.Beatmaps.Legacy;
+using osu.Game.Beatmaps.Timing;
+using osu.Game.IO;
+using osu.Game.Rulesets.Objects.Legacy;
namespace osu.Game.Beatmaps.Formats
{
@@ -303,10 +303,6 @@ namespace osu.Game.Beatmaps.Formats
beatmap.BeatmapInfo.Metadata.BackgroundFile = CleanFilename(split[2]);
break;
- case LegacyEventType.Video:
- beatmap.BeatmapInfo.Metadata.VideoFile = CleanFilename(split[2]);
- break;
-
case LegacyEventType.Break:
double start = getOffsetTime(Parsing.ParseDouble(split[1]));
diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs
index 09f40ce7b6..ec2ca30535 100644
--- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs
@@ -133,9 +133,6 @@ namespace osu.Game.Beatmaps.Formats
if (!string.IsNullOrEmpty(beatmap.BeatmapInfo.Metadata.BackgroundFile))
writer.WriteLine(FormattableString.Invariant($"{(int)LegacyEventType.Background},0,\"{beatmap.BeatmapInfo.Metadata.BackgroundFile}\",0,0"));
- if (!string.IsNullOrEmpty(beatmap.BeatmapInfo.Metadata.VideoFile))
- writer.WriteLine(FormattableString.Invariant($"{(int)LegacyEventType.Video},0,\"{beatmap.BeatmapInfo.Metadata.VideoFile}\",0,0"));
-
foreach (var b in beatmap.Breaks)
writer.WriteLine(FormattableString.Invariant($"{(int)LegacyEventType.Break},{b.StartTime},{b.EndTime}"));
}
diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs
index c81f933bca..269449ef80 100644
--- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs
@@ -4,13 +4,13 @@
using System;
using System.Collections.Generic;
using System.IO;
-using osuTK;
-using osuTK.Graphics;
using osu.Framework.Graphics;
+using osu.Framework.Utils;
+using osu.Game.Beatmaps.Legacy;
using osu.Game.IO;
using osu.Game.Storyboards;
-using osu.Game.Beatmaps.Legacy;
-using osu.Framework.Utils;
+using osuTK;
+using osuTK.Graphics;
namespace osu.Game.Beatmaps.Formats
{
@@ -87,6 +87,15 @@ namespace osu.Game.Beatmaps.Formats
switch (type)
{
+ case LegacyEventType.Video:
+ {
+ var offset = Parsing.ParseInt(split[1]);
+ var path = CleanFilename(split[2]);
+
+ storyboard.GetLayer("Video").Add(new StoryboardVideo(path, offset));
+ break;
+ }
+
case LegacyEventType.Sprite:
{
var layer = parseLayer(split[1]);
diff --git a/osu.Game/Beatmaps/IWorkingBeatmap.cs b/osu.Game/Beatmaps/IWorkingBeatmap.cs
index 526bc668af..31975157a0 100644
--- a/osu.Game/Beatmaps/IWorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/IWorkingBeatmap.cs
@@ -5,7 +5,6 @@ using System;
using System.Collections.Generic;
using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Textures;
-using osu.Framework.Graphics.Video;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
@@ -27,11 +26,6 @@ namespace osu.Game.Beatmaps
///
Texture Background { get; }
- ///
- /// Retrieves the video background file for this .
- ///
- VideoSprite Video { get; }
-
///
/// Retrieves the audio track for this .
///
diff --git a/osu.Game/Beatmaps/Legacy/LegacyStoryLayer.cs b/osu.Game/Beatmaps/Legacy/LegacyStoryLayer.cs
index 5237445640..48e8bdbb76 100644
--- a/osu.Game/Beatmaps/Legacy/LegacyStoryLayer.cs
+++ b/osu.Game/Beatmaps/Legacy/LegacyStoryLayer.cs
@@ -8,6 +8,7 @@ namespace osu.Game.Beatmaps.Legacy
Background = 0,
Fail = 1,
Pass = 2,
- Foreground = 3
+ Foreground = 3,
+ Video = 4
}
}
diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs
index dd4f893ac2..f30340956a 100644
--- a/osu.Game/Beatmaps/WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/WorkingBeatmap.cs
@@ -1,23 +1,22 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Framework.Audio.Track;
-using osu.Framework.Graphics.Textures;
-using osu.Game.Rulesets.Mods;
using System;
using System.Collections.Generic;
-using osu.Game.Storyboards;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using osu.Framework.Audio;
+using osu.Framework.Audio.Track;
+using osu.Framework.Graphics.Textures;
+using osu.Framework.Logging;
using osu.Framework.Statistics;
using osu.Game.Rulesets;
+using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.UI;
using osu.Game.Skinning;
-using osu.Framework.Graphics.Video;
-using osu.Framework.Logging;
+using osu.Game.Storyboards;
namespace osu.Game.Beatmaps
{
@@ -234,10 +233,6 @@ namespace osu.Game.Beatmaps
protected abstract Texture GetBackground();
private readonly RecyclableLazy background;
- public VideoSprite Video => GetVideo();
-
- protected abstract VideoSprite GetVideo();
-
public bool TrackLoaded => track.IsResultAvailable;
public Track Track => track.Value;
protected abstract Track GetTrack();
diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs
index 21de654670..41f6747b74 100644
--- a/osu.Game/Configuration/OsuConfigManager.cs
+++ b/osu.Game/Configuration/OsuConfigManager.cs
@@ -70,7 +70,6 @@ namespace osu.Game.Configuration
Set(OsuSetting.ShowFpsDisplay, false);
Set(OsuSetting.ShowStoryboard, true);
- Set(OsuSetting.ShowVideoBackground, true);
Set(OsuSetting.BeatmapSkins, true);
Set(OsuSetting.BeatmapHitsounds, true);
@@ -176,7 +175,6 @@ namespace osu.Game.Configuration
BlurLevel,
LightenDuringBreaks,
ShowStoryboard,
- ShowVideoBackground,
KeyOverlay,
ScoreMeter,
FloatingComments,
diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs
index 65c104b92f..39c1fdad52 100644
--- a/osu.Game/Graphics/Containers/UserDimContainer.cs
+++ b/osu.Game/Graphics/Containers/UserDimContainer.cs
@@ -40,7 +40,7 @@ namespace osu.Game.Graphics.Containers
///
/// Whether player is in break time.
- /// Must be bound to to allow for dim adjustments in gameplay.
+ /// Must be bound to to allow for dim adjustments in gameplay.
///
public readonly IBindable IsBreakTime = new Bindable();
@@ -55,8 +55,6 @@ namespace osu.Game.Graphics.Containers
protected Bindable ShowStoryboard { get; private set; }
- protected Bindable ShowVideo { get; private set; }
-
private float breakLightening => LightenDuringBreaks.Value && IsBreakTime.Value ? BREAK_LIGHTEN_AMOUNT : 0;
protected float DimLevel => Math.Max(EnableUserDim.Value && !IgnoreUserSettings.Value ? (float)UserDimLevel.Value - breakLightening : 0, 0);
@@ -79,14 +77,12 @@ namespace osu.Game.Graphics.Containers
UserDimLevel = config.GetBindable(OsuSetting.DimLevel);
LightenDuringBreaks = config.GetBindable(OsuSetting.LightenDuringBreaks);
ShowStoryboard = config.GetBindable(OsuSetting.ShowStoryboard);
- ShowVideo = config.GetBindable(OsuSetting.ShowVideoBackground);
EnableUserDim.ValueChanged += _ => UpdateVisuals();
UserDimLevel.ValueChanged += _ => UpdateVisuals();
LightenDuringBreaks.ValueChanged += _ => UpdateVisuals();
IsBreakTime.ValueChanged += _ => UpdateVisuals();
ShowStoryboard.ValueChanged += _ => UpdateVisuals();
- ShowVideo.ValueChanged += _ => UpdateVisuals();
StoryboardReplacesBackground.ValueChanged += _ => UpdateVisuals();
IgnoreUserSettings.ValueChanged += _ => UpdateVisuals();
}
diff --git a/osu.Game/Graphics/Sprites/OsuSpriteText.cs b/osu.Game/Graphics/Sprites/OsuSpriteText.cs
index cd988c347b..76e46513ba 100644
--- a/osu.Game/Graphics/Sprites/OsuSpriteText.cs
+++ b/osu.Game/Graphics/Sprites/OsuSpriteText.cs
@@ -1,9 +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 osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
-using osu.Framework.Graphics.Transforms;
namespace osu.Game.Graphics.Sprites
{
@@ -15,23 +13,4 @@ namespace osu.Game.Graphics.Sprites
Font = OsuFont.Default;
}
}
-
- public static class OsuSpriteTextTransformExtensions
- {
- ///
- /// Sets Text to a new value after a duration.
- ///
- /// A to which further transforms can be added.
- public static TransformSequence TransformTextTo(this T spriteText, string newText, double duration = 0, Easing easing = Easing.None)
- where T : OsuSpriteText
- => spriteText.TransformTo(nameof(OsuSpriteText.Text), newText, duration, easing);
-
- ///
- /// Sets Text to a new value after a duration.
- ///
- /// A to which further transforms can be added.
- public static TransformSequence TransformTextTo(this TransformSequence t, string newText, double duration = 0, Easing easing = Easing.None)
- where T : OsuSpriteText
- => t.Append(o => o.TransformTextTo(newText, duration, easing));
- }
}
diff --git a/osu.Game/IO/Archives/ArchiveReader.cs b/osu.Game/IO/Archives/ArchiveReader.cs
index 4ee7a19ebc..a30f961daf 100644
--- a/osu.Game/IO/Archives/ArchiveReader.cs
+++ b/osu.Game/IO/Archives/ArchiveReader.cs
@@ -45,7 +45,5 @@ namespace osu.Game.IO.Archives
return buffer;
}
}
-
- public abstract Stream GetUnderlyingStream();
}
}
diff --git a/osu.Game/IO/Archives/LegacyByteArrayReader.cs b/osu.Game/IO/Archives/LegacyByteArrayReader.cs
new file mode 100644
index 0000000000..0c3620403f
--- /dev/null
+++ b/osu.Game/IO/Archives/LegacyByteArrayReader.cs
@@ -0,0 +1,30 @@
+// 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.IO;
+
+namespace osu.Game.IO.Archives
+{
+ ///
+ /// Allows reading a single file from the provided stream.
+ ///
+ public class LegacyByteArrayReader : ArchiveReader
+ {
+ private readonly byte[] content;
+
+ public LegacyByteArrayReader(byte[] content, string filename)
+ : base(filename)
+ {
+ this.content = content;
+ }
+
+ public override Stream GetStream(string name) => new MemoryStream(content);
+
+ public override void Dispose()
+ {
+ }
+
+ public override IEnumerable Filenames => new[] { Name };
+ }
+}
diff --git a/osu.Game/IO/Archives/LegacyDirectoryArchiveReader.cs b/osu.Game/IO/Archives/LegacyDirectoryArchiveReader.cs
index eff02ae7a5..dfae58aed7 100644
--- a/osu.Game/IO/Archives/LegacyDirectoryArchiveReader.cs
+++ b/osu.Game/IO/Archives/LegacyDirectoryArchiveReader.cs
@@ -28,7 +28,5 @@ namespace osu.Game.IO.Archives
}
public override IEnumerable Filenames => Directory.GetFiles(path, "*", SearchOption.AllDirectories).Select(f => f.Replace(path, string.Empty).Trim(Path.DirectorySeparatorChar)).ToArray();
-
- public override Stream GetUnderlyingStream() => null;
}
}
diff --git a/osu.Game/IO/Archives/LegacyFileArchiveReader.cs b/osu.Game/IO/Archives/LegacyFileArchiveReader.cs
index bd5f9cbd07..72e5a21079 100644
--- a/osu.Game/IO/Archives/LegacyFileArchiveReader.cs
+++ b/osu.Game/IO/Archives/LegacyFileArchiveReader.cs
@@ -28,7 +28,5 @@ namespace osu.Game.IO.Archives
}
public override IEnumerable Filenames => new[] { Name };
-
- public override Stream GetUnderlyingStream() => null;
}
}
diff --git a/osu.Game/IO/Archives/ZipArchiveReader.cs b/osu.Game/IO/Archives/ZipArchiveReader.cs
index 35f38ea7e8..80dfa104f3 100644
--- a/osu.Game/IO/Archives/ZipArchiveReader.cs
+++ b/osu.Game/IO/Archives/ZipArchiveReader.cs
@@ -45,7 +45,5 @@ namespace osu.Game.IO.Archives
}
public override IEnumerable Filenames => archive.Entries.Select(e => e.Key).ExcludeSystemFileNames();
-
- public override Stream GetUnderlyingStream() => archiveStream;
}
}
diff --git a/osu.Game/Online/Chat/StandAloneChatDisplay.cs b/osu.Game/Online/Chat/StandAloneChatDisplay.cs
index 0914f688e9..4fbeac1db9 100644
--- a/osu.Game/Online/Chat/StandAloneChatDisplay.cs
+++ b/osu.Game/Online/Chat/StandAloneChatDisplay.cs
@@ -26,7 +26,7 @@ namespace osu.Game.Online.Chat
protected ChannelManager ChannelManager;
- private DrawableChannel drawableChannel;
+ private StandAloneDrawableChannel drawableChannel;
private readonly bool postingTextbox;
@@ -77,6 +77,9 @@ namespace osu.Game.Online.Chat
ChannelManager = manager;
}
+ protected virtual StandAloneDrawableChannel CreateDrawableChannel(Channel channel) =>
+ new StandAloneDrawableChannel(channel);
+
private void postMessage(TextBox sender, bool newtext)
{
var text = textbox.Text.Trim();
@@ -100,14 +103,14 @@ namespace osu.Game.Online.Chat
if (e.NewValue == null) return;
- AddInternal(drawableChannel = new StandAloneDrawableChannel(e.NewValue)
- {
- CreateChatLineAction = CreateMessage,
- Padding = new MarginPadding { Bottom = postingTextbox ? textbox_height : 0 }
- });
+ drawableChannel = CreateDrawableChannel(e.NewValue);
+ drawableChannel.CreateChatLineAction = CreateMessage;
+ drawableChannel.Padding = new MarginPadding { Bottom = postingTextbox ? textbox_height : 0 };
+
+ AddInternal(drawableChannel);
}
- protected class StandAloneDrawableChannel : DrawableChannel
+ public class StandAloneDrawableChannel : DrawableChannel
{
public Func CreateChatLineAction;
diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs
index 3c7ab27651..5487bd9320 100644
--- a/osu.Game/OsuGameBase.cs
+++ b/osu.Game/OsuGameBase.cs
@@ -47,6 +47,8 @@ namespace osu.Game
{
public const string CLIENT_STREAM_NAME = "lazer";
+ public const int SAMPLE_CONCURRENCY = 6;
+
protected OsuConfigManager LocalConfig;
protected BeatmapManager BeatmapManager;
@@ -153,6 +155,8 @@ namespace osu.Game
AddFont(Resources, @"Fonts/Venera-Bold");
AddFont(Resources, @"Fonts/Venera-Black");
+ Audio.Samples.PlaybackConcurrency = SAMPLE_CONCURRENCY;
+
runMigrations();
dependencies.Cache(SkinManager = new SkinManager(Storage, contextFactory, Host, Audio, new NamespacedResourceStore(Resources, "Skins/Legacy")));
diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs
index 443f2b7bf7..6019657cf0 100644
--- a/osu.Game/Overlays/Chat/DrawableChannel.cs
+++ b/osu.Game/Overlays/Chat/DrawableChannel.cs
@@ -26,6 +26,20 @@ namespace osu.Game.Overlays.Chat
protected FillFlowContainer ChatLineFlow;
private OsuScrollContainer scroll;
+ private bool scrollbarVisible = true;
+
+ public bool ScrollbarVisible
+ {
+ set
+ {
+ if (scrollbarVisible == value) return;
+
+ scrollbarVisible = value;
+ if (scroll != null)
+ scroll.ScrollbarVisible = value;
+ }
+ }
+
[Resolved]
private OsuColour colours { get; set; }
@@ -44,6 +58,7 @@ namespace osu.Game.Overlays.Chat
Masking = true,
Child = scroll = new OsuScrollContainer
{
+ ScrollbarVisible = scrollbarVisible,
RelativeSizeAxes = Axes.Both,
// Some chat lines have effects that slightly protrude to the bottom,
// which we do not want to mask away, hence the padding.
diff --git a/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs b/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs
new file mode 100644
index 0000000000..3c9b31daae
--- /dev/null
+++ b/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs
@@ -0,0 +1,267 @@
+// 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 System.Threading;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Online.API;
+using osu.Game.Online.API.Requests;
+using osu.Game.Users;
+using osuTK;
+
+namespace osu.Game.Overlays.Dashboard.Friends
+{
+ public class FriendDisplay : CompositeDrawable
+ {
+ private List users = new List();
+
+ public List Users
+ {
+ get => users;
+ set
+ {
+ users = value;
+
+ onlineStreamControl.Populate(value);
+ }
+ }
+
+ [Resolved]
+ private IAPIProvider api { get; set; }
+
+ private GetFriendsRequest request;
+ private CancellationTokenSource cancellationToken;
+
+ private Drawable currentContent;
+
+ private readonly FriendOnlineStreamControl onlineStreamControl;
+ private readonly Box background;
+ private readonly Box controlBackground;
+ private readonly UserListToolbar userListToolbar;
+ private readonly Container itemsPlaceholder;
+ private readonly LoadingLayer loading;
+
+ public FriendDisplay()
+ {
+ RelativeSizeAxes = Axes.X;
+ AutoSizeAxes = Axes.Y;
+ InternalChild = new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Children = new Drawable[]
+ {
+ new Container
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Children = new Drawable[]
+ {
+ controlBackground = new Box
+ {
+ RelativeSizeAxes = Axes.Both
+ },
+ new Container
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Padding = new MarginPadding
+ {
+ Top = 20,
+ Horizontal = 45
+ },
+ Child = onlineStreamControl = new FriendOnlineStreamControl(),
+ }
+ }
+ },
+ new Container
+ {
+ Name = "User List",
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Children = new Drawable[]
+ {
+ background = new Box
+ {
+ RelativeSizeAxes = Axes.Both
+ },
+ new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Direction = FillDirection.Vertical,
+ Margin = new MarginPadding { Bottom = 20 },
+ Children = new Drawable[]
+ {
+ new Container
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Padding = new MarginPadding
+ {
+ Horizontal = 40,
+ Vertical = 20
+ },
+ Child = userListToolbar = new UserListToolbar
+ {
+ Anchor = Anchor.CentreRight,
+ Origin = Anchor.CentreRight,
+ }
+ },
+ new Container
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Children = new Drawable[]
+ {
+ itemsPlaceholder = new Container
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Padding = new MarginPadding { Horizontal = 50 }
+ },
+ loading = new LoadingLayer(itemsPlaceholder)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ };
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OverlayColourProvider colourProvider)
+ {
+ background.Colour = colourProvider.Background4;
+ controlBackground.Colour = colourProvider.Background5;
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ onlineStreamControl.Current.BindValueChanged(_ => recreatePanels());
+ userListToolbar.DisplayStyle.BindValueChanged(_ => recreatePanels());
+ userListToolbar.SortCriteria.BindValueChanged(_ => recreatePanels());
+ }
+
+ public void Fetch()
+ {
+ if (!api.IsLoggedIn)
+ return;
+
+ request = new GetFriendsRequest();
+ request.Success += response => Schedule(() => Users = response);
+ api.Queue(request);
+ }
+
+ private void recreatePanels()
+ {
+ if (!users.Any())
+ return;
+
+ cancellationToken?.Cancel();
+
+ if (itemsPlaceholder.Any())
+ loading.Show();
+
+ var sortedUsers = sortUsers(getUsersInCurrentGroup());
+
+ LoadComponentAsync(createTable(sortedUsers), addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token);
+ }
+
+ private List getUsersInCurrentGroup()
+ {
+ switch (onlineStreamControl.Current.Value?.Status)
+ {
+ default:
+ case OnlineStatus.All:
+ return users;
+
+ case OnlineStatus.Offline:
+ return users.Where(u => !u.IsOnline).ToList();
+
+ case OnlineStatus.Online:
+ return users.Where(u => u.IsOnline).ToList();
+ }
+ }
+
+ private void addContentToPlaceholder(Drawable content)
+ {
+ loading.Hide();
+
+ var lastContent = currentContent;
+
+ if (lastContent != null)
+ {
+ lastContent.FadeOut(100, Easing.OutQuint).Expire();
+ lastContent.Delay(25).Schedule(() => lastContent.BypassAutoSizeAxes = Axes.Y);
+ }
+
+ itemsPlaceholder.Add(currentContent = content);
+ currentContent.FadeIn(200, Easing.OutQuint);
+ }
+
+ private FillFlowContainer createTable(List users)
+ {
+ var style = userListToolbar.DisplayStyle.Value;
+
+ return new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Spacing = new Vector2(style == OverlayPanelDisplayStyle.Card ? 10 : 2),
+ Children = users.Select(u => createUserPanel(u, style)).ToList()
+ };
+ }
+
+ private UserPanel createUserPanel(User user, OverlayPanelDisplayStyle style)
+ {
+ switch (style)
+ {
+ default:
+ case OverlayPanelDisplayStyle.Card:
+ return new UserGridPanel(user).With(panel =>
+ {
+ panel.Anchor = Anchor.TopCentre;
+ panel.Origin = Anchor.TopCentre;
+ panel.Width = 290;
+ });
+
+ case OverlayPanelDisplayStyle.List:
+ return new UserListPanel(user);
+ }
+ }
+
+ private List sortUsers(List unsorted)
+ {
+ switch (userListToolbar.SortCriteria.Value)
+ {
+ default:
+ case UserSortCriteria.LastVisit:
+ return unsorted.OrderByDescending(u => u.LastVisit).ToList();
+
+ case UserSortCriteria.Rank:
+ return unsorted.OrderByDescending(u => u.CurrentModeRank.HasValue).ThenBy(u => u.CurrentModeRank ?? 0).ToList();
+
+ case UserSortCriteria.Username:
+ return unsorted.OrderBy(u => u.Username).ToList();
+ }
+ }
+
+ protected override void Dispose(bool isDisposing)
+ {
+ request?.Cancel();
+ cancellationToken?.Cancel();
+
+ base.Dispose(isDisposing);
+ }
+ }
+}
diff --git a/osu.Game/Overlays/Dashboard/Friends/FriendOnlineStreamControl.cs b/osu.Game/Overlays/Dashboard/Friends/FriendOnlineStreamControl.cs
new file mode 100644
index 0000000000..28546ceab8
--- /dev/null
+++ b/osu.Game/Overlays/Dashboard/Friends/FriendOnlineStreamControl.cs
@@ -0,0 +1,28 @@
+// 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 osu.Game.Users;
+
+namespace osu.Game.Overlays.Dashboard.Friends
+{
+ public class FriendOnlineStreamControl : OverlayStreamControl
+ {
+ protected override OverlayStreamItem CreateStreamItem(FriendStream value) => new FriendsOnlineStatusItem(value);
+
+ public void Populate(List users)
+ {
+ Clear();
+
+ var userCount = users.Count;
+ var onlineUsersCount = users.Count(user => user.IsOnline);
+
+ AddItem(new FriendStream(OnlineStatus.All, userCount));
+ AddItem(new FriendStream(OnlineStatus.Online, onlineUsersCount));
+ AddItem(new FriendStream(OnlineStatus.Offline, userCount - onlineUsersCount));
+
+ Current.Value = Items.FirstOrDefault();
+ }
+ }
+}
diff --git a/osu.Game/Overlays/Dashboard/Friends/FriendStream.cs b/osu.Game/Overlays/Dashboard/Friends/FriendStream.cs
new file mode 100644
index 0000000000..4abece9a8d
--- /dev/null
+++ b/osu.Game/Overlays/Dashboard/Friends/FriendStream.cs
@@ -0,0 +1,18 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Game.Overlays.Dashboard.Friends
+{
+ public class FriendStream
+ {
+ public OnlineStatus Status { get; }
+
+ public int Count { get; }
+
+ public FriendStream(OnlineStatus status, int count)
+ {
+ Status = status;
+ Count = count;
+ }
+ }
+}
diff --git a/osu.Game/Overlays/Home/Friends/FriendsOnlineStatusItem.cs b/osu.Game/Overlays/Dashboard/Friends/FriendsOnlineStatusItem.cs
similarity index 78%
rename from osu.Game/Overlays/Home/Friends/FriendsOnlineStatusItem.cs
rename to osu.Game/Overlays/Dashboard/Friends/FriendsOnlineStatusItem.cs
index d9b780ce46..7e902203f8 100644
--- a/osu.Game/Overlays/Home/Friends/FriendsOnlineStatusItem.cs
+++ b/osu.Game/Overlays/Dashboard/Friends/FriendsOnlineStatusItem.cs
@@ -5,11 +5,11 @@ using System;
using osu.Game.Graphics;
using osuTK.Graphics;
-namespace osu.Game.Overlays.Home.Friends
+namespace osu.Game.Overlays.Dashboard.Friends
{
- public class FriendsOnlineStatusItem : OverlayStreamItem
+ public class FriendsOnlineStatusItem : OverlayStreamItem
{
- public FriendsOnlineStatusItem(FriendsBundle value)
+ public FriendsOnlineStatusItem(FriendStream value)
: base(value)
{
}
@@ -22,13 +22,13 @@ namespace osu.Game.Overlays.Home.Friends
{
switch (Value.Status)
{
- case FriendsOnlineStatus.All:
+ case OnlineStatus.All:
return Color4.White;
- case FriendsOnlineStatus.Online:
+ case OnlineStatus.Online:
return colours.GreenLight;
- case FriendsOnlineStatus.Offline:
+ case OnlineStatus.Offline:
return Color4.Black;
default:
diff --git a/osu.Game/Overlays/Dashboard/Friends/OnlineStatus.cs b/osu.Game/Overlays/Dashboard/Friends/OnlineStatus.cs
new file mode 100644
index 0000000000..6f2f55a6ed
--- /dev/null
+++ b/osu.Game/Overlays/Dashboard/Friends/OnlineStatus.cs
@@ -0,0 +1,12 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Game.Overlays.Dashboard.Friends
+{
+ public enum OnlineStatus
+ {
+ All,
+ Online,
+ Offline
+ }
+}
diff --git a/osu.Game/Overlays/Home/Friends/UserListToolbar.cs b/osu.Game/Overlays/Dashboard/Friends/UserListToolbar.cs
similarity index 96%
rename from osu.Game/Overlays/Home/Friends/UserListToolbar.cs
rename to osu.Game/Overlays/Dashboard/Friends/UserListToolbar.cs
index f7c5e9f4fd..fb4b938183 100644
--- a/osu.Game/Overlays/Home/Friends/UserListToolbar.cs
+++ b/osu.Game/Overlays/Dashboard/Friends/UserListToolbar.cs
@@ -6,7 +6,7 @@ using osu.Framework.Graphics.Containers;
using osuTK;
using osu.Framework.Bindables;
-namespace osu.Game.Overlays.Home.Friends
+namespace osu.Game.Overlays.Dashboard.Friends
{
public class UserListToolbar : CompositeDrawable
{
diff --git a/osu.Game/Overlays/Home/Friends/UserSortTabControl.cs b/osu.Game/Overlays/Dashboard/Friends/UserSortTabControl.cs
similarity index 90%
rename from osu.Game/Overlays/Home/Friends/UserSortTabControl.cs
rename to osu.Game/Overlays/Dashboard/Friends/UserSortTabControl.cs
index 2479fa4638..3a5f65212d 100644
--- a/osu.Game/Overlays/Home/Friends/UserSortTabControl.cs
+++ b/osu.Game/Overlays/Dashboard/Friends/UserSortTabControl.cs
@@ -3,7 +3,7 @@
using System.ComponentModel;
-namespace osu.Game.Overlays.Home.Friends
+namespace osu.Game.Overlays.Dashboard.Friends
{
public class UserSortTabControl : OverlaySortTabControl
{
diff --git a/osu.Game/Overlays/Home/Friends/FriendsBundle.cs b/osu.Game/Overlays/Home/Friends/FriendsBundle.cs
deleted file mode 100644
index 75d00dfef8..0000000000
--- a/osu.Game/Overlays/Home/Friends/FriendsBundle.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-namespace osu.Game.Overlays.Home.Friends
-{
- public class FriendsBundle
- {
- public FriendsOnlineStatus Status { get; }
-
- public int Count { get; }
-
- public FriendsBundle(FriendsOnlineStatus status, int count)
- {
- Status = status;
- Count = count;
- }
- }
-
- public enum FriendsOnlineStatus
- {
- All,
- Online,
- Offline
- }
-}
diff --git a/osu.Game/Overlays/Home/Friends/FriendsOnlineStatusControl.cs b/osu.Game/Overlays/Home/Friends/FriendsOnlineStatusControl.cs
deleted file mode 100644
index 196f01ab4a..0000000000
--- a/osu.Game/Overlays/Home/Friends/FriendsOnlineStatusControl.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.Collections.Generic;
-using System.Linq;
-using osu.Game.Users;
-
-namespace osu.Game.Overlays.Home.Friends
-{
- public class FriendsOnlineStatusControl : OverlayStreamControl
- {
- protected override OverlayStreamItem CreateStreamItem(FriendsBundle value) => new FriendsOnlineStatusItem(value);
-
- public void Populate(List users)
- {
- var userCount = users.Count;
- var onlineUsersCount = users.Count(user => user.IsOnline);
-
- AddItem(new FriendsBundle(FriendsOnlineStatus.All, userCount));
- AddItem(new FriendsBundle(FriendsOnlineStatus.Online, onlineUsersCount));
- AddItem(new FriendsBundle(FriendsOnlineStatus.Offline, userCount - onlineUsersCount));
-
- Current.Value = Items.FirstOrDefault();
- }
- }
-}
diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs
index ea2811e5cd..3089040f96 100644
--- a/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs
@@ -18,15 +18,10 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
{
new SettingsCheckbox
{
- LabelText = "Storyboards",
+ LabelText = "Storyboard / Video",
Bindable = config.GetBindable(OsuSetting.ShowStoryboard)
},
new SettingsCheckbox
- {
- LabelText = "Video",
- Bindable = config.GetBindable(OsuSetting.ShowVideoBackground)
- },
- new SettingsCheckbox
{
LabelText = "Hit Lighting",
Bindable = config.GetBindable(OsuSetting.HitLighting)
diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs
index 59d39a1c3c..e7f2f21465 100644
--- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.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 osu.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Configuration;
@@ -56,24 +57,32 @@ namespace osu.Game.Overlays.Settings.Sections.Input
},
};
- rawInputToggle.ValueChanged += enabled =>
+ if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows)
{
- // this is temporary until we support per-handler settings.
- const string raw_mouse_handler = @"OsuTKRawMouseHandler";
- const string standard_mouse_handler = @"OsuTKMouseHandler";
-
- ignoredInputHandler.Value = enabled.NewValue ? standard_mouse_handler : raw_mouse_handler;
- };
-
- ignoredInputHandler = config.GetBindable(FrameworkSetting.IgnoredInputHandlers);
- ignoredInputHandler.ValueChanged += handler =>
+ rawInputToggle.Disabled = true;
+ sensitivity.Bindable.Disabled = true;
+ }
+ else
{
- bool raw = !handler.NewValue.Contains("Raw");
- rawInputToggle.Value = raw;
- sensitivity.Bindable.Disabled = !raw;
- };
+ rawInputToggle.ValueChanged += enabled =>
+ {
+ // this is temporary until we support per-handler settings.
+ const string raw_mouse_handler = @"OsuTKRawMouseHandler";
+ const string standard_mouse_handler = @"OsuTKMouseHandler";
- ignoredInputHandler.TriggerChange();
+ ignoredInputHandler.Value = enabled.NewValue ? standard_mouse_handler : raw_mouse_handler;
+ };
+
+ ignoredInputHandler = config.GetBindable(FrameworkSetting.IgnoredInputHandlers);
+ ignoredInputHandler.ValueChanged += handler =>
+ {
+ bool raw = !handler.NewValue.Contains("Raw");
+ rawInputToggle.Value = raw;
+ sensitivity.Bindable.Disabled = !raw;
+ };
+
+ ignoredInputHandler.TriggerChange();
+ }
}
private class SensitivitySetting : SettingsSlider
diff --git a/osu.Game/Overlays/Settings/SettingsCheckbox.cs b/osu.Game/Overlays/Settings/SettingsCheckbox.cs
index a554159fd7..437b2e45b3 100644
--- a/osu.Game/Overlays/Settings/SettingsCheckbox.cs
+++ b/osu.Game/Overlays/Settings/SettingsCheckbox.cs
@@ -8,16 +8,14 @@ namespace osu.Game.Overlays.Settings
{
public class SettingsCheckbox : SettingsItem
{
- private OsuCheckbox checkbox;
-
private string labelText;
- protected override Drawable CreateControl() => checkbox = new OsuCheckbox();
+ protected override Drawable CreateControl() => new OsuCheckbox();
public override string LabelText
{
get => labelText;
- set => checkbox.LabelText = labelText = value;
+ set => ((OsuCheckbox)Control).LabelText = labelText = value;
}
}
}
diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs
index e89f2adf0b..c2dd40d2a6 100644
--- a/osu.Game/Overlays/Settings/SettingsItem.cs
+++ b/osu.Game/Overlays/Settings/SettingsItem.cs
@@ -33,22 +33,24 @@ namespace osu.Game.Overlays.Settings
protected readonly FillFlowContainer FlowContent;
- private SpriteText text;
+ private SpriteText labelText;
public bool ShowsDefaultIndicator = true;
public virtual string LabelText
{
- get => text?.Text ?? string.Empty;
+ get => labelText?.Text ?? string.Empty;
set
{
- if (text == null)
+ if (labelText == null)
{
// construct lazily for cases where the label is not needed (may be provided by the Control).
- FlowContent.Insert(-1, text = new OsuSpriteText());
+ FlowContent.Insert(-1, labelText = new OsuSpriteText());
+
+ updateDisabled();
}
- text.Text = value;
+ labelText.Text = value;
}
}
@@ -96,13 +98,19 @@ namespace osu.Game.Overlays.Settings
if (controlWithCurrent != null)
{
controlWithCurrent.Current.ValueChanged += _ => SettingChanged?.Invoke();
- controlWithCurrent.Current.DisabledChanged += disabled => { Colour = disabled ? Color4.Gray : Color4.White; };
+ controlWithCurrent.Current.DisabledChanged += _ => updateDisabled();
if (ShowsDefaultIndicator)
restoreDefaultButton.Bindable = controlWithCurrent.Current;
}
}
+ private void updateDisabled()
+ {
+ if (labelText != null)
+ labelText.Alpha = controlWithCurrent.Current.Disabled ? 0.3f : 1;
+ }
+
private class RestoreDefaultValueButton : Container, IHasTooltip
{
private Bindable bindable;
diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs
index 46c0c1da07..0e5fe3fc9c 100644
--- a/osu.Game/Rulesets/Mods/Mod.cs
+++ b/osu.Game/Rulesets/Mods/Mod.cs
@@ -2,9 +2,15 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
using Newtonsoft.Json;
+using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
+using osu.Game.Configuration;
using osu.Game.IO.Serialization;
+using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Mods
{
@@ -42,6 +48,51 @@ namespace osu.Game.Rulesets.Mods
[JsonIgnore]
public virtual string Description => string.Empty;
+ ///
+ /// The tooltip to display for this mod when used in a .
+ ///
+ ///
+ /// Differs from , as the value of attributes (AR, CS, etc) changeable via the mod
+ /// are displayed in the tooltip.
+ ///
+ [JsonIgnore]
+ public string IconTooltip
+ {
+ get
+ {
+ string description = SettingDescription;
+
+ return string.IsNullOrEmpty(description) ? Name : $"{Name} ({description})";
+ }
+ }
+
+ ///
+ /// The description of editable settings of a mod to use in the .
+ ///
+ ///
+ /// Parentheses are added to the tooltip, surrounding the value of this property. If this property is string.Empty,
+ /// the tooltip will not have parentheses.
+ ///
+ public virtual string SettingDescription
+ {
+ get
+ {
+ var tooltipTexts = new List();
+
+ foreach ((SettingSourceAttribute attr, PropertyInfo property) in this.GetOrderedSettingsSourceProperties())
+ {
+ object bindableObj = property.GetValue(this);
+
+ if ((bindableObj as IHasDefaultValue)?.IsDefault == true)
+ continue;
+
+ tooltipTexts.Add($"{attr.Label} {bindableObj}");
+ }
+
+ return string.Join(", ", tooltipTexts.Where(s => !string.IsNullOrEmpty(s)));
+ }
+ }
+
///
/// The score multiplier of this mod.
///
diff --git a/osu.Game/Rulesets/Mods/ModCinema.cs b/osu.Game/Rulesets/Mods/ModCinema.cs
index cd08aee453..cf8128301c 100644
--- a/osu.Game/Rulesets/Mods/ModCinema.cs
+++ b/osu.Game/Rulesets/Mods/ModCinema.cs
@@ -39,7 +39,6 @@ namespace osu.Game.Rulesets.Mods
{
player.Background.EnableUserDim.Value = false;
- player.DimmableVideo.IgnoreUserSettings.Value = true;
player.DimmableStoryboard.IgnoreUserSettings.Value = true;
player.BreakOverlay.Hide();
diff --git a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs
index 2083671072..c3a8efdd66 100644
--- a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs
+++ b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs
@@ -7,6 +7,7 @@ using osu.Framework.Graphics.Sprites;
using System;
using System.Collections.Generic;
using osu.Game.Configuration;
+using System.Linq;
namespace osu.Game.Rulesets.Mods
{
@@ -52,6 +53,21 @@ namespace osu.Game.Rulesets.Mods
Value = 5,
};
+ public override string SettingDescription
+ {
+ get
+ {
+ string drainRate = DrainRate.IsDefault ? string.Empty : $"HP {DrainRate.Value:N1}";
+ string overallDifficulty = OverallDifficulty.IsDefault ? string.Empty : $"OD {OverallDifficulty.Value:N1}";
+
+ return string.Join(", ", new[]
+ {
+ drainRate,
+ overallDifficulty
+ }.Where(s => !string.IsNullOrEmpty(s)));
+ }
+ }
+
private BeatmapDifficulty difficulty;
public void ReadFromDifficulty(BeatmapDifficulty difficulty)
@@ -79,7 +95,7 @@ namespace osu.Game.Rulesets.Mods
///
/// Transfer a setting from to a configuration bindable.
- /// Only performs the transfer if the user it not currently overriding..
+ /// Only performs the transfer if the user is not currently overriding.
///
protected void TransferSetting(BindableNumber bindable, T beatmapDefault)
where T : struct, IComparable, IConvertible, IEquatable
diff --git a/osu.Game/Rulesets/Mods/ModEasy.cs b/osu.Game/Rulesets/Mods/ModEasy.cs
index b56be95dfe..c1c4124b98 100644
--- a/osu.Game/Rulesets/Mods/ModEasy.cs
+++ b/osu.Game/Rulesets/Mods/ModEasy.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using Humanizer;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
using osu.Game.Beatmaps;
@@ -28,6 +29,8 @@ namespace osu.Game.Rulesets.Mods
MaxValue = 10
};
+ public override string SettingDescription => Retries.IsDefault ? string.Empty : $"{"lives".ToQuantity(Retries.Value)}";
+
private int retries;
private BindableNumber health;
diff --git a/osu.Game/Rulesets/Mods/ModRandom.cs b/osu.Game/Rulesets/Mods/ModRandom.cs
new file mode 100644
index 0000000000..da55ab3fbf
--- /dev/null
+++ b/osu.Game/Rulesets/Mods/ModRandom.cs
@@ -0,0 +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 osu.Framework.Graphics.Sprites;
+using osu.Game.Graphics;
+
+namespace osu.Game.Rulesets.Mods
+{
+ public abstract class ModRandom : Mod
+ {
+ public override string Name => "Random";
+ public override string Acronym => "RD";
+ public override ModType Type => ModType.Conversion;
+ public override IconUsage? Icon => OsuIcon.Dice;
+ public override double ScoreMultiplier => 1;
+ }
+}
diff --git a/osu.Game/Rulesets/Mods/ModRateAdjust.cs b/osu.Game/Rulesets/Mods/ModRateAdjust.cs
index 1739524bcd..cb2ff149f1 100644
--- a/osu.Game/Rulesets/Mods/ModRateAdjust.cs
+++ b/osu.Game/Rulesets/Mods/ModRateAdjust.cs
@@ -15,5 +15,7 @@ namespace osu.Game.Rulesets.Mods
{
track.AddAdjustment(AdjustableProperty.Tempo, SpeedChange);
}
+
+ public override string SettingDescription => SpeedChange.IsDefault ? string.Empty : $"{SpeedChange.Value:N2}x";
}
}
diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs
index 9e63142b42..c1f3e357a1 100644
--- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs
+++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs
@@ -26,6 +26,8 @@ namespace osu.Game.Rulesets.Mods
[SettingSource("Final rate", "The final speed to ramp to")]
public abstract BindableNumber FinalRate { get; }
+ public override string SettingDescription => $"{InitialRate.Value:N2}x to {FinalRate.Value:N2}x";
+
private double finalRateTime;
private double beginRampTime;
diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
index aa29e42fac..0011faefbb 100644
--- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
+++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
@@ -38,6 +38,15 @@ namespace osu.Game.Rulesets.Objects.Drawables
private readonly Lazy> nestedHitObjects = new Lazy>();
public IReadOnlyList NestedHitObjects => nestedHitObjects.IsValueCreated ? nestedHitObjects.Value : (IReadOnlyList)Array.Empty();
+ ///
+ /// Whether this object should handle any user input events.
+ ///
+ public bool HandleUserInput { get; set; } = true;
+
+ public override bool PropagatePositionalInputSubTree => HandleUserInput;
+
+ public override bool PropagateNonPositionalInputSubTree => HandleUserInput;
+
///
/// Invoked when a has been applied by this or a nested .
///
@@ -344,7 +353,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
/// Plays all the hit sounds for this .
/// This is invoked automatically when this is hit.
///
- public void PlaySamples() => Samples?.Play();
+ public virtual void PlaySamples() => Samples?.Play();
protected override void Update()
{
diff --git a/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs b/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs
index c2947c0aca..d9aa615c6e 100644
--- a/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs
+++ b/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs
@@ -17,6 +17,12 @@ namespace osu.Game.Rulesets.Replays.Types
/// The to extract values from.
/// The beatmap.
/// The last post-conversion , used to fill in missing delta information. May be null.
- void ConvertFrom(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null);
+ void FromLegacy(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null);
+
+ ///
+ /// Populates this using values from a .
+ ///
+ /// The beatmap.
+ LegacyReplayFrame ToLegacy(IBeatmap beatmap);
}
}
diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs
index c38a5c6af7..58f598a203 100644
--- a/osu.Game/Rulesets/Ruleset.cs
+++ b/osu.Game/Rulesets/Ruleset.cs
@@ -42,9 +42,63 @@ namespace osu.Game.Rulesets
///
/// Converts mods from legacy enum values. Do not override if you're not a legacy ruleset.
///
- /// The legacy enum which will be converted
- /// An enumerable of constructed s
- public virtual IEnumerable ConvertLegacyMods(LegacyMods mods) => Array.Empty();
+ /// The legacy enum which will be converted.
+ /// An enumerable of constructed s.
+ public virtual IEnumerable ConvertFromLegacyMods(LegacyMods mods) => Array.Empty();
+
+ ///
+ /// Converts mods to legacy enum values. Do not override if you're not a legacy ruleset.
+ ///
+ /// The mods which will be converted.
+ /// A single bitwise enumerable value representing (to the best of our ability) the mods.
+ public virtual LegacyMods ConvertToLegacyMods(Mod[] mods)
+ {
+ var value = LegacyMods.None;
+
+ foreach (var mod in mods)
+ {
+ switch (mod)
+ {
+ case ModNoFail _:
+ value |= LegacyMods.NoFail;
+ break;
+
+ case ModEasy _:
+ value |= LegacyMods.Easy;
+ break;
+
+ case ModHidden _:
+ value |= LegacyMods.Hidden;
+ break;
+
+ case ModHardRock _:
+ value |= LegacyMods.HardRock;
+ break;
+
+ case ModSuddenDeath _:
+ value |= LegacyMods.SuddenDeath;
+ break;
+
+ case ModDoubleTime _:
+ value |= LegacyMods.DoubleTime;
+ break;
+
+ case ModRelax _:
+ value |= LegacyMods.Relax;
+ break;
+
+ case ModHalfTime _:
+ value |= LegacyMods.HalfTime;
+ break;
+
+ case ModFlashlight _:
+ value |= LegacyMods.Flashlight;
+ break;
+ }
+ }
+
+ return value;
+ }
public ModAutoplay GetAutoplayMod() => GetAllMods().OfType().First();
diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs
index e624fb80fa..5062c92afe 100644
--- a/osu.Game/Rulesets/UI/DrawableRuleset.cs
+++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs
@@ -72,9 +72,9 @@ namespace osu.Game.Rulesets.UI
///
public override Playfield Playfield => playfield.Value;
- private Container overlays;
+ public override Container Overlays { get; } = new Container { RelativeSizeAxes = Axes.Both };
- public override Container Overlays => overlays;
+ public override Container FrameStableComponents { get; } = new Container { RelativeSizeAxes = Axes.Both };
public override GameplayClock FrameStableClock => frameStabilityContainer.GameplayClock;
@@ -158,6 +158,7 @@ namespace osu.Game.Rulesets.UI
dependencies.Cache(textureStore);
localSampleStore = dependencies.Get().GetSampleStore(new NamespacedResourceStore(resources, "Samples"));
+ localSampleStore.PlaybackConcurrency = OsuGameBase.SAMPLE_CONCURRENCY;
dependencies.CacheAs(new FallbackSampleStore(localSampleStore, dependencies.Get()));
}
@@ -186,11 +187,12 @@ namespace osu.Game.Rulesets.UI
FrameStablePlayback = FrameStablePlayback,
Children = new Drawable[]
{
+ FrameStableComponents,
KeyBindingInputManager
.WithChild(CreatePlayfieldAdjustmentContainer()
.WithChild(Playfield)
),
- overlays = new Container { RelativeSizeAxes = Axes.Both }
+ Overlays,
}
},
};
@@ -261,6 +263,21 @@ namespace osu.Game.Rulesets.UI
Playfield.Add(drawableObject);
}
+ public override void SetRecordTarget(Replay recordingReplay)
+ {
+ if (!(KeyBindingInputManager is IHasRecordingHandler recordingInputManager))
+ throw new InvalidOperationException($"A {nameof(KeyBindingInputManager)} which supports recording is not available");
+
+ var recorder = CreateReplayRecorder(recordingReplay);
+
+ if (recorder == null)
+ return;
+
+ recorder.ScreenSpaceToGamefield = Playfield.ScreenSpaceToGamefield;
+
+ recordingInputManager.Recorder = recorder;
+ }
+
public override void SetReplayScore(Score replayScore)
{
if (!(KeyBindingInputManager is IHasReplayHandler replayInputManager))
@@ -301,6 +318,8 @@ namespace osu.Game.Rulesets.UI
protected virtual ReplayInputHandler CreateReplayInputHandler(Replay replay) => null;
+ protected virtual ReplayRecorder CreateReplayRecorder(Replay replay) => null;
+
///
/// Creates a Playfield.
///
@@ -388,10 +407,15 @@ namespace osu.Game.Rulesets.UI
public abstract Playfield Playfield { get; }
///
- /// Place to put drawables above hit objects but below UI.
+ /// Content to be placed above hitobjects. Will be affected by frame stability.
///
public abstract Container Overlays { get; }
+ ///
+ /// Components to be run potentially multiple times in line with frame-stable gameplay.
+ ///
+ public abstract Container FrameStableComponents { get; }
+
///
/// The frame-stable clock which is being used for playfield display.
///
@@ -469,6 +493,12 @@ namespace osu.Game.Rulesets.UI
/// The replay, null for local input.
public abstract void SetReplayScore(Score replayScore);
+ ///
+ /// Sets a replay to be used to record gameplay.
+ ///
+ /// The target to be recorded to.
+ public abstract void SetRecordTarget(Replay recordingReplay);
+
///
/// Invoked when the interactive user requests resuming from a paused state.
/// Allows potentially delaying the resume process until an interaction is performed.
diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs
index e569bb8459..3ba28aad45 100644
--- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs
+++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs
@@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.UI
{
///
/// A container which consumes a parent gameplay clock and standardises frame counts for children.
- /// Will ensure a minimum of 40 frames per clock second is maintained, regardless of any system lag or seeks.
+ /// Will ensure a minimum of 50 frames per clock second is maintained, regardless of any system lag or seeks.
///
public class FrameStabilityContainer : Container, IHasReplayHandler
{
diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs
index 3edab0745d..8ea6c74349 100644
--- a/osu.Game/Rulesets/UI/ModIcon.cs
+++ b/osu.Game/Rulesets/UI/ModIcon.cs
@@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.UI
private readonly ModType type;
- public virtual string TooltipText { get; }
+ public virtual string TooltipText => mod.IconTooltip;
private Mod mod;
@@ -48,8 +48,6 @@ namespace osu.Game.Rulesets.UI
type = mod.Type;
- TooltipText = mod.Name;
-
Size = new Vector2(size);
Children = new Drawable[]
diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs
index 047047ccfd..8141108aef 100644
--- a/osu.Game/Rulesets/UI/Playfield.cs
+++ b/osu.Game/Rulesets/UI/Playfield.cs
@@ -30,6 +30,11 @@ namespace osu.Game.Rulesets.UI
///
public Func GamefieldToScreenSpace => HitObjectContainer.ToScreenSpace;
+ ///
+ /// A function that converts screen space coordinates to gamefield.
+ ///
+ public Func ScreenSpaceToGamefield => HitObjectContainer.ToLocalSpace;
+
///
/// All the s contained in this and all .
///
diff --git a/osu.Game/Rulesets/UI/ReplayRecorder.cs b/osu.Game/Rulesets/UI/ReplayRecorder.cs
new file mode 100644
index 0000000000..c977639584
--- /dev/null
+++ b/osu.Game/Rulesets/UI/ReplayRecorder.cs
@@ -0,0 +1,85 @@
+// 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.Generic;
+using System.Linq;
+using osu.Framework.Graphics;
+using osu.Framework.Input;
+using osu.Framework.Input.Bindings;
+using osu.Framework.Input.Events;
+using osu.Game.Replays;
+using osu.Game.Rulesets.Replays;
+using osuTK;
+
+namespace osu.Game.Rulesets.UI
+{
+ public abstract class ReplayRecorder : ReplayRecorder, IKeyBindingHandler
+ where T : struct
+ {
+ private readonly Replay target;
+
+ private readonly List pressedActions = new List();
+
+ private InputManager inputManager;
+
+ public int RecordFrameRate = 60;
+
+ protected ReplayRecorder(Replay target)
+ {
+ this.target = target;
+
+ RelativeSizeAxes = Axes.Both;
+
+ Depth = float.MinValue;
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ inputManager = GetContainingInputManager();
+ }
+
+ protected override bool OnMouseMove(MouseMoveEvent e)
+ {
+ recordFrame(false);
+ return base.OnMouseMove(e);
+ }
+
+ public bool OnPressed(T action)
+ {
+ pressedActions.Add(action);
+ recordFrame(true);
+ return false;
+ }
+
+ public void OnReleased(T action)
+ {
+ pressedActions.Remove(action);
+ recordFrame(true);
+ }
+
+ private void recordFrame(bool important)
+ {
+ var last = target.Frames.LastOrDefault();
+
+ if (!important && last != null && Time.Current - last.Time < (1000d / RecordFrameRate))
+ return;
+
+ var position = ScreenSpaceToGamefield?.Invoke(inputManager.CurrentState.Mouse.Position) ?? inputManager.CurrentState.Mouse.Position;
+
+ var frame = HandleFrame(position, pressedActions, last);
+
+ if (frame != null)
+ target.Frames.Add(frame);
+ }
+
+ protected abstract ReplayFrame HandleFrame(Vector2 mousePosition, List actions, ReplayFrame previousFrame);
+ }
+
+ public abstract class ReplayRecorder : Component
+ {
+ public Func ScreenSpaceToGamefield;
+ }
+}
diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs
index 41b2739fc5..ba30fe28d5 100644
--- a/osu.Game/Rulesets/UI/RulesetInputManager.cs
+++ b/osu.Game/Rulesets/UI/RulesetInputManager.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 osu.Framework.Allocation;
using osu.Framework.Bindables;
@@ -23,9 +24,24 @@ using MouseState = osu.Framework.Input.States.MouseState;
namespace osu.Game.Rulesets.UI
{
- public abstract class RulesetInputManager : PassThroughInputManager, ICanAttachKeyCounter, IHasReplayHandler
+ public abstract class RulesetInputManager : PassThroughInputManager, ICanAttachKeyCounter, IHasReplayHandler, IHasRecordingHandler
where T : struct
{
+ private ReplayRecorder recorder;
+
+ public ReplayRecorder Recorder
+ {
+ set
+ {
+ if (recorder != null)
+ throw new InvalidOperationException("Cannot attach more than one recorder");
+
+ recorder = value;
+
+ KeyBindingContainer.Add(recorder);
+ }
+ }
+
protected override InputState CreateInitialState()
{
var state = base.CreateInitialState();
@@ -148,7 +164,7 @@ namespace osu.Game.Rulesets.UI
#endregion
- protected virtual RulesetKeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
+ protected virtual KeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
=> new RulesetKeyBindingContainer(ruleset, variant, unique);
public class RulesetKeyBindingContainer : DatabasedKeyBindingContainer
@@ -168,6 +184,11 @@ namespace osu.Game.Rulesets.UI
ReplayInputHandler ReplayInputHandler { get; set; }
}
+ public interface IHasRecordingHandler
+ {
+ public ReplayRecorder Recorder { set; }
+ }
+
///
/// Supports attaching a .
/// Keys will be populated automatically and a receptor will be injected inside.
diff --git a/osu.Game/Scoring/Legacy/DatabasedLegacyScoreParser.cs b/osu.Game/Scoring/Legacy/DatabasedLegacyScoreDecoder.cs
similarity index 74%
rename from osu.Game/Scoring/Legacy/DatabasedLegacyScoreParser.cs
rename to osu.Game/Scoring/Legacy/DatabasedLegacyScoreDecoder.cs
index 2115d784a0..9b590f56dd 100644
--- a/osu.Game/Scoring/Legacy/DatabasedLegacyScoreParser.cs
+++ b/osu.Game/Scoring/Legacy/DatabasedLegacyScoreDecoder.cs
@@ -7,15 +7,15 @@ using osu.Game.Rulesets;
namespace osu.Game.Scoring.Legacy
{
///
- /// A which retrieves the applicable and
+ /// A which retrieves the applicable and
/// for the score from the database.
///
- public class DatabasedLegacyScoreParser : LegacyScoreParser
+ public class DatabasedLegacyScoreDecoder : LegacyScoreDecoder
{
private readonly RulesetStore rulesets;
private readonly BeatmapManager beatmaps;
- public DatabasedLegacyScoreParser(RulesetStore rulesets, BeatmapManager beatmaps)
+ public DatabasedLegacyScoreDecoder(RulesetStore rulesets, BeatmapManager beatmaps)
{
this.rulesets = rulesets;
this.beatmaps = beatmaps;
diff --git a/osu.Game/Scoring/Legacy/LegacyScoreParser.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs
similarity index 97%
rename from osu.Game/Scoring/Legacy/LegacyScoreParser.cs
rename to osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs
index 19d8410cc2..c356dd246d 100644
--- a/osu.Game/Scoring/Legacy/LegacyScoreParser.cs
+++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs
@@ -19,7 +19,7 @@ using SharpCompress.Compressors.LZMA;
namespace osu.Game.Scoring.Legacy
{
- public abstract class LegacyScoreParser
+ public abstract class LegacyScoreDecoder
{
private IBeatmap currentBeatmap;
private Ruleset currentRuleset;
@@ -45,9 +45,6 @@ namespace osu.Game.Scoring.Legacy
if (workingBeatmap is DummyWorkingBeatmap)
throw new BeatmapNotFoundException();
- currentBeatmap = workingBeatmap.Beatmap;
- scoreInfo.Beatmap = currentBeatmap.BeatmapInfo;
-
scoreInfo.User = new User { Username = sr.ReadString() };
// MD5Hash
@@ -66,7 +63,10 @@ namespace osu.Game.Scoring.Legacy
/* score.Perfect = */
sr.ReadBoolean();
- scoreInfo.Mods = currentRuleset.ConvertLegacyMods((LegacyMods)sr.ReadInt32()).ToArray();
+ scoreInfo.Mods = currentRuleset.ConvertFromLegacyMods((LegacyMods)sr.ReadInt32()).ToArray();
+
+ currentBeatmap = workingBeatmap.GetPlayableBeatmap(currentRuleset.RulesetInfo, scoreInfo.Mods);
+ scoreInfo.Beatmap = currentBeatmap.BeatmapInfo;
/* score.HpGraphString = */
sr.ReadString();
@@ -264,7 +264,7 @@ namespace osu.Game.Scoring.Legacy
if (convertible == null)
throw new InvalidOperationException($"Legacy replay cannot be converted for the ruleset: {currentRuleset.Description}");
- convertible.ConvertFrom(currentFrame, currentBeatmap, lastFrame);
+ convertible.FromLegacy(currentFrame, currentBeatmap, lastFrame);
var frame = (ReplayFrame)convertible;
frame.Time = currentFrame.Time;
diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs
new file mode 100644
index 0000000000..db7e51e833
--- /dev/null
+++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs
@@ -0,0 +1,114 @@
+// 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.IO;
+using System.Linq;
+using System.Text;
+using osu.Framework.Extensions;
+using osu.Game.Beatmaps;
+using osu.Game.IO.Legacy;
+using osu.Game.Replays.Legacy;
+using osu.Game.Rulesets.Replays.Types;
+using SharpCompress.Compressors.LZMA;
+
+namespace osu.Game.Scoring.Legacy
+{
+ public class LegacyScoreEncoder
+ {
+ public const int LATEST_VERSION = 128;
+
+ private readonly Score score;
+ private readonly IBeatmap beatmap;
+
+ public LegacyScoreEncoder(Score score, IBeatmap beatmap)
+ {
+ this.score = score;
+ this.beatmap = beatmap;
+
+ if (score.ScoreInfo.Beatmap.RulesetID < 0 || score.ScoreInfo.Beatmap.RulesetID > 3)
+ throw new ArgumentException("Only scores in the osu, taiko, catch, or mania rulesets can be encoded to the legacy score format.", nameof(score));
+ }
+
+ public void Encode(Stream stream)
+ {
+ using (SerializationWriter sw = new SerializationWriter(stream))
+ {
+ sw.Write((byte)(score.ScoreInfo.Ruleset.ID ?? 0));
+ sw.Write(LATEST_VERSION);
+ sw.Write(score.ScoreInfo.Beatmap.MD5Hash);
+ sw.Write(score.ScoreInfo.UserString);
+ sw.Write($"lazer-{score.ScoreInfo.UserString}-{score.ScoreInfo.Date}".ComputeMD5Hash());
+ sw.Write((ushort)(score.ScoreInfo.GetCount300() ?? 0));
+ sw.Write((ushort)(score.ScoreInfo.GetCount100() ?? 0));
+ sw.Write((ushort)(score.ScoreInfo.GetCount50() ?? 0));
+ sw.Write((ushort)(score.ScoreInfo.GetCountGeki() ?? 0));
+ sw.Write((ushort)(score.ScoreInfo.GetCountKatu() ?? 0));
+ sw.Write((ushort)(score.ScoreInfo.GetCountMiss() ?? 0));
+ sw.Write((int)(score.ScoreInfo.TotalScore));
+ sw.Write((ushort)score.ScoreInfo.MaxCombo);
+ sw.Write(score.ScoreInfo.Combo == score.ScoreInfo.MaxCombo);
+ sw.Write((int)score.ScoreInfo.Ruleset.CreateInstance().ConvertToLegacyMods(score.ScoreInfo.Mods));
+
+ sw.Write(getHpGraphFormatted());
+ sw.Write(score.ScoreInfo.Date.DateTime);
+ sw.WriteByteArray(createReplayData());
+ sw.Write((long)0);
+ writeModSpecificData(score.ScoreInfo, sw);
+ }
+ }
+
+ private void writeModSpecificData(ScoreInfo score, SerializationWriter sw)
+ {
+ }
+
+ private byte[] createReplayData()
+ {
+ var content = new ASCIIEncoding().GetBytes(replayStringContent);
+
+ using (var outStream = new MemoryStream())
+ {
+ using (var lzma = new LzmaStream(new LzmaEncoderProperties(false, 1 << 21, 255), false, outStream))
+ {
+ outStream.Write(lzma.Properties);
+
+ long fileSize = content.Length;
+ for (int i = 0; i < 8; i++)
+ outStream.WriteByte((byte)(fileSize >> (8 * i)));
+
+ lzma.Write(content);
+ }
+
+ return outStream.ToArray();
+ }
+ }
+
+ private string replayStringContent
+ {
+ get
+ {
+ StringBuilder replayData = new StringBuilder();
+
+ if (score.Replay != null)
+ {
+ LegacyReplayFrame lastF = new LegacyReplayFrame(0, 0, 0, ReplayButtonState.None);
+
+ foreach (var f in score.Replay.Frames.OfType().Select(f => f.ToLegacy(beatmap)))
+ {
+ replayData.Append(FormattableString.Invariant($"{f.Time - lastF.Time}|{f.MouseX ?? 0}|{f.MouseY ?? 0}|{(int)f.ButtonState},"));
+ lastF = f;
+ }
+ }
+
+ replayData.AppendFormat(@"{0}|{1}|{2}|{3},", -12345, 0, 0, 0);
+ return replayData.ToString();
+ }
+ }
+
+ private string getHpGraphFormatted()
+ {
+ // todo: implement, maybe?
+ return string.Empty;
+ }
+ }
+}
diff --git a/osu.Game/Scoring/LegacyDatabasedScore.cs b/osu.Game/Scoring/LegacyDatabasedScore.cs
index 172e08e2d0..bd673eaa29 100644
--- a/osu.Game/Scoring/LegacyDatabasedScore.cs
+++ b/osu.Game/Scoring/LegacyDatabasedScore.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Scoring
var replayFilename = score.Files.First(f => f.Filename.EndsWith(".osr", StringComparison.InvariantCultureIgnoreCase)).FileInfo.StoragePath;
using (var stream = store.GetStream(replayFilename))
- Replay = new DatabasedLegacyScoreParser(rulesets, beatmaps).Parse(stream).Replay;
+ Replay = new DatabasedLegacyScoreDecoder(rulesets, beatmaps).Parse(stream).Replay;
}
}
}
diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs
index 249f0a932b..d5bd486e43 100644
--- a/osu.Game/Scoring/ScoreManager.cs
+++ b/osu.Game/Scoring/ScoreManager.cs
@@ -46,9 +46,9 @@ namespace osu.Game.Scoring
{
try
{
- return new DatabasedLegacyScoreParser(rulesets, beatmaps()).Parse(stream).ScoreInfo;
+ return new DatabasedLegacyScoreDecoder(rulesets, beatmaps()).Parse(stream).ScoreInfo;
}
- catch (LegacyScoreParser.BeatmapNotFoundException e)
+ catch (LegacyScoreDecoder.BeatmapNotFoundException e)
{
Logger.Log(e.Message, LoggingTarget.Information, LogLevel.Error);
return null;
diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs
index 50fd127093..b08455be95 100644
--- a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs
+++ b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs
@@ -166,7 +166,7 @@ namespace osu.Game.Screens.Backgrounds
BlurAmount.ValueChanged += _ => UpdateVisuals();
}
- protected override bool ShowDimContent => !ShowStoryboard.Value || !StoryboardReplacesBackground.Value || !ShowVideo.Value; // The background needs to be hidden in the case of it being replaced by the storyboard
+ protected override bool ShowDimContent => !ShowStoryboard.Value || !StoryboardReplacesBackground.Value; // The background needs to be hidden in the case of it being replaced by the storyboard
protected override void UpdateVisuals()
{
diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs
index 8201ec2710..2dec3fd22e 100644
--- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs
@@ -279,6 +279,11 @@ namespace osu.Game.Screens.Edit.Compose.Components
handleMouseInput(e.ScreenSpaceMousePosition);
}
+ protected override void OnDragEnd(DragEndEvent e)
+ {
+ handleMouseInput(e.ScreenSpaceMousePosition);
+ }
+
private void handleMouseInput(Vector2 screenSpaceMousePosition)
{
// copied from SliderBar so we can do custom spacing logic.
diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs
index 127270f521..dcee5e83b7 100644
--- a/osu.Game/Screens/Menu/MainMenu.cs
+++ b/osu.Game/Screens/Menu/MainMenu.cs
@@ -140,7 +140,7 @@ namespace osu.Game.Screens.Menu
preloadSongSelect();
}
- [Resolved]
+ [Resolved(canBeNull: true)]
private OsuGame game { get; set; }
private void confirmAndExit()
@@ -148,7 +148,7 @@ namespace osu.Game.Screens.Menu
if (exitConfirmed) return;
exitConfirmed = true;
- game.PerformFromScreen(menu => menu.Exit());
+ game?.PerformFromScreen(menu => menu.Exit());
}
private void preloadSongSelect()
diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs
index ee8be87352..c978f4e96d 100644
--- a/osu.Game/Screens/Play/BreakOverlay.cs
+++ b/osu.Game/Screens/Play/BreakOverlay.cs
@@ -2,8 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
-using osu.Framework.Allocation;
-using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
@@ -16,8 +14,6 @@ namespace osu.Game.Screens.Play
{
public class BreakOverlay : Container
{
- private readonly ScoreProcessor scoreProcessor;
-
///
/// The duration of the break overlay fading.
///
@@ -37,10 +33,6 @@ namespace osu.Game.Screens.Play
{
breaks = value;
- // reset index in case the new breaks list is smaller than last one
- isBreakTime.Value = false;
- CurrentBreakIndex = 0;
-
if (IsLoaded)
initializeBreaks();
}
@@ -48,27 +40,17 @@ namespace osu.Game.Screens.Play
public override bool RemoveCompletedTransforms => false;
- ///
- /// Whether the gameplay is currently in a break.
- ///
- public IBindable IsBreakTime => isBreakTime;
-
- protected int CurrentBreakIndex;
-
- private readonly BindableBool isBreakTime = new BindableBool();
-
private readonly Container remainingTimeAdjustmentBox;
private readonly Container remainingTimeBox;
private readonly RemainingTimeCounter remainingTimeCounter;
- private readonly BreakInfo info;
private readonly BreakArrows breakArrows;
- private readonly double gameplayStartTime;
- public BreakOverlay(bool letterboxing, double gameplayStartTime = 0, ScoreProcessor scoreProcessor = null)
+ public BreakOverlay(bool letterboxing, ScoreProcessor scoreProcessor)
{
- this.gameplayStartTime = gameplayStartTime;
- this.scoreProcessor = scoreProcessor;
RelativeSizeAxes = Axes.Both;
+
+ BreakInfo info;
+
Child = fadeContainer = new Container
{
Alpha = 0,
@@ -119,13 +101,11 @@ namespace osu.Game.Screens.Play
}
};
- if (scoreProcessor != null) bindProcessor(scoreProcessor);
- }
-
- [BackgroundDependencyLoader(true)]
- private void load(GameplayClock clock)
- {
- if (clock != null) Clock = clock;
+ if (scoreProcessor != null)
+ {
+ info.AccuracyDisplay.Current.BindTo(scoreProcessor.Accuracy);
+ info.GradeDisplay.Current.BindTo(scoreProcessor.Rank);
+ }
}
protected override void LoadComplete()
@@ -134,42 +114,6 @@ namespace osu.Game.Screens.Play
initializeBreaks();
}
- protected override void Update()
- {
- base.Update();
- updateBreakTimeBindable();
- }
-
- private void updateBreakTimeBindable() =>
- isBreakTime.Value = getCurrentBreak()?.HasEffect == true
- || Clock.CurrentTime < gameplayStartTime
- || scoreProcessor?.HasCompleted == true;
-
- private BreakPeriod getCurrentBreak()
- {
- if (breaks?.Count > 0)
- {
- var time = Clock.CurrentTime;
-
- if (time > breaks[CurrentBreakIndex].EndTime)
- {
- while (time > breaks[CurrentBreakIndex].EndTime && CurrentBreakIndex < breaks.Count - 1)
- CurrentBreakIndex++;
- }
- else
- {
- while (time < breaks[CurrentBreakIndex].StartTime && CurrentBreakIndex > 0)
- CurrentBreakIndex--;
- }
-
- var closest = breaks[CurrentBreakIndex];
-
- return closest.Contains(time) ? closest : null;
- }
-
- return null;
- }
-
private void initializeBreaks()
{
FinishTransforms(true);
@@ -207,11 +151,5 @@ namespace osu.Game.Screens.Play
}
}
}
-
- private void bindProcessor(ScoreProcessor processor)
- {
- info.AccuracyDisplay.Current.BindTo(processor.Accuracy);
- info.GradeDisplay.Current.BindTo(processor.Rank);
- }
}
}
diff --git a/osu.Game/Screens/Play/BreakTracker.cs b/osu.Game/Screens/Play/BreakTracker.cs
new file mode 100644
index 0000000000..64262d52b5
--- /dev/null
+++ b/osu.Game/Screens/Play/BreakTracker.cs
@@ -0,0 +1,82 @@
+// 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 osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Game.Beatmaps.Timing;
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Screens.Play
+{
+ public class BreakTracker : Component
+ {
+ private readonly ScoreProcessor scoreProcessor;
+
+ private readonly double gameplayStartTime;
+
+ ///
+ /// Whether the gameplay is currently in a break.
+ ///
+ public IBindable IsBreakTime => isBreakTime;
+
+ protected int CurrentBreakIndex;
+
+ private readonly BindableBool isBreakTime = new BindableBool();
+
+ private IReadOnlyList breaks;
+
+ public IReadOnlyList Breaks
+ {
+ get => breaks;
+ set
+ {
+ breaks = value;
+
+ // reset index in case the new breaks list is smaller than last one
+ isBreakTime.Value = false;
+ CurrentBreakIndex = 0;
+ }
+ }
+
+ public BreakTracker(double gameplayStartTime = 0, ScoreProcessor scoreProcessor = null)
+ {
+ this.gameplayStartTime = gameplayStartTime;
+ this.scoreProcessor = scoreProcessor;
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ isBreakTime.Value = getCurrentBreak()?.HasEffect == true
+ || Clock.CurrentTime < gameplayStartTime
+ || scoreProcessor?.HasCompleted == true;
+ }
+
+ private BreakPeriod getCurrentBreak()
+ {
+ if (breaks?.Count > 0)
+ {
+ var time = Clock.CurrentTime;
+
+ if (time > breaks[CurrentBreakIndex].EndTime)
+ {
+ while (time > breaks[CurrentBreakIndex].EndTime && CurrentBreakIndex < breaks.Count - 1)
+ CurrentBreakIndex++;
+ }
+ else
+ {
+ while (time < breaks[CurrentBreakIndex].StartTime && CurrentBreakIndex > 0)
+ CurrentBreakIndex--;
+ }
+
+ var closest = breaks[CurrentBreakIndex];
+
+ return closest.Contains(time) ? closest : null;
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/osu.Game/Screens/Play/DimmableStoryboard.cs b/osu.Game/Screens/Play/DimmableStoryboard.cs
index 0fe315fbab..eabdee95fb 100644
--- a/osu.Game/Screens/Play/DimmableStoryboard.cs
+++ b/osu.Game/Screens/Play/DimmableStoryboard.cs
@@ -44,7 +44,6 @@ namespace osu.Game.Screens.Play
return;
drawableStoryboard = storyboard.CreateDrawable();
- drawableStoryboard.Masking = true;
if (async)
LoadComponentAsync(drawableStoryboard, Add);
diff --git a/osu.Game/Screens/Play/DimmableVideo.cs b/osu.Game/Screens/Play/DimmableVideo.cs
deleted file mode 100644
index 1a01cace17..0000000000
--- a/osu.Game/Screens/Play/DimmableVideo.cs
+++ /dev/null
@@ -1,88 +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 osu.Framework.Allocation;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
-using osu.Framework.Graphics.Video;
-using osu.Game.Graphics.Containers;
-using osuTK.Graphics;
-
-namespace osu.Game.Screens.Play
-{
- public class DimmableVideo : UserDimContainer
- {
- private readonly VideoSprite video;
- private DrawableVideo drawableVideo;
-
- public DimmableVideo(VideoSprite video)
- {
- this.video = video;
- }
-
- [BackgroundDependencyLoader]
- private void load()
- {
- initializeVideo(false);
- }
-
- protected override void LoadComplete()
- {
- ShowVideo.BindValueChanged(_ => initializeVideo(true), true);
- base.LoadComplete();
- }
-
- protected override bool ShowDimContent => IgnoreUserSettings.Value || (ShowVideo.Value && DimLevel < 1);
-
- private void initializeVideo(bool async)
- {
- if (video == null)
- return;
-
- if (drawableVideo != null)
- return;
-
- if (!ShowVideo.Value && !IgnoreUserSettings.Value)
- return;
-
- drawableVideo = new DrawableVideo(video);
-
- if (async)
- LoadComponentAsync(drawableVideo, Add);
- else
- Add(drawableVideo);
- }
-
- private class DrawableVideo : Container
- {
- public DrawableVideo(VideoSprite video)
- {
- RelativeSizeAxes = Axes.Both;
- Masking = true;
-
- video.RelativeSizeAxes = Axes.Both;
- video.FillMode = FillMode.Fit;
- video.Anchor = Anchor.Centre;
- video.Origin = Anchor.Centre;
-
- AddRangeInternal(new Drawable[]
- {
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = Color4.Black,
- },
- video,
- });
- }
-
- [BackgroundDependencyLoader]
- private void load(GameplayClock clock)
- {
- if (clock != null)
- Clock = clock;
- }
- }
- }
-}
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index 32261efd4e..5da53ad2c9 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Audio;
@@ -17,13 +18,16 @@ using osu.Framework.Threading;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Graphics.Containers;
+using osu.Game.IO.Archives;
using osu.Game.Online.API;
using osu.Game.Overlays;
+using osu.Game.Replays;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Scoring;
+using osu.Game.Scoring.Legacy;
using osu.Game.Screens.Ranking;
using osu.Game.Skinning;
using osu.Game.Users;
@@ -72,6 +76,8 @@ namespace osu.Game.Screens.Play
public BreakOverlay BreakOverlay;
+ private BreakTracker breakTracker;
+
protected ScoreProcessor ScoreProcessor { get; private set; }
protected HealthProcessor HealthProcessor { get; private set; }
@@ -85,7 +91,6 @@ namespace osu.Game.Screens.Play
protected GameplayClockContainer GameplayClockContainer { get; private set; }
public DimmableStoryboard DimmableStoryboard { get; private set; }
- public DimmableVideo DimmableVideo { get; private set; }
[Cached]
[Cached(Type = typeof(IBindable>))]
@@ -118,6 +123,23 @@ namespace osu.Game.Screens.Play
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
=> dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ PrepareReplay();
+ }
+
+ private Replay recordingReplay;
+
+ ///
+ /// Run any recording / playback setup for replays.
+ ///
+ protected virtual void PrepareReplay()
+ {
+ DrawableRuleset.SetRecordTarget(recordingReplay = new Replay());
+ }
+
[BackgroundDependencyLoader]
private void load(AudioManager audio, OsuConfigManager config)
{
@@ -184,12 +206,11 @@ namespace osu.Game.Screens.Play
foreach (var mod in Mods.Value.OfType())
mod.ApplyToHealthProcessor(HealthProcessor);
- BreakOverlay.IsBreakTime.BindValueChanged(onBreakTimeChanged, true);
+ breakTracker.IsBreakTime.BindValueChanged(onBreakTimeChanged, true);
}
private void addUnderlayComponents(Container target)
{
- target.Add(DimmableVideo = new DimmableVideo(Beatmap.Value.Video) { RelativeSizeAxes = Axes.Both });
target.Add(DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard) { RelativeSizeAxes = Axes.Both });
}
@@ -212,12 +233,30 @@ namespace osu.Game.Screens.Play
DrawableRuleset,
new ComboEffects(ScoreProcessor)
});
+
+ DrawableRuleset.FrameStableComponents.AddRange(new Drawable[]
+ {
+ ScoreProcessor,
+ HealthProcessor,
+ breakTracker = new BreakTracker(DrawableRuleset.GameplayStartTime, ScoreProcessor)
+ {
+ Breaks = working.Beatmap.Breaks
+ }
+ });
+
+ HealthProcessor.IsBreakTime.BindTo(breakTracker.IsBreakTime);
}
private void addOverlayComponents(Container target, WorkingBeatmap working)
{
target.AddRange(new[]
{
+ BreakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor)
+ {
+ Clock = DrawableRuleset.FrameStableClock,
+ ProcessCustomClock = false,
+ Breaks = working.Beatmap.Breaks
+ },
// display the cursor above some HUD elements.
DrawableRuleset.Cursor?.CreateProxy() ?? new Container(),
DrawableRuleset.ResumeOverlay?.CreateProxy() ?? new Container(),
@@ -274,20 +313,8 @@ namespace osu.Game.Screens.Play
performImmediateExit();
},
},
- failAnimation = new FailAnimation(DrawableRuleset) { OnComplete = onFailComplete, }
+ failAnimation = new FailAnimation(DrawableRuleset) { OnComplete = onFailComplete, },
});
-
- DrawableRuleset.Overlays.Add(BreakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, DrawableRuleset.GameplayStartTime, ScoreProcessor)
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Breaks = working.Beatmap.Breaks
- });
-
- DrawableRuleset.Overlays.Add(ScoreProcessor);
- DrawableRuleset.Overlays.Add(HealthProcessor);
-
- HealthProcessor.IsBreakTime.BindTo(BreakOverlay.IsBreakTime);
}
private void onBreakTimeChanged(ValueChangedEvent isBreakTime)
@@ -299,7 +326,7 @@ namespace osu.Game.Screens.Play
private void updatePauseOnFocusLostState() =>
HUDOverlay.HoldToQuit.PauseOnFocusLost = PauseOnFocusLost
&& !DrawableRuleset.HasReplayLoaded.Value
- && !BreakOverlay.IsBreakTime.Value;
+ && !breakTracker.IsBreakTime.Value;
private IBeatmap loadPlayableBeatmap()
{
@@ -387,6 +414,10 @@ namespace osu.Game.Screens.Play
private void onCompletion()
{
+ // screen may be in the exiting transition phase.
+ if (!this.IsCurrentScreen())
+ return;
+
// Only show the completion screen if the player hasn't failed
if (HealthProcessor.HasFailed || completionProgressDelegate != null)
return;
@@ -517,7 +548,7 @@ namespace osu.Game.Screens.Play
PauseOverlay.Hide();
// breaks and time-based conditions may allow instant resume.
- if (BreakOverlay.IsBreakTime.Value)
+ if (breakTracker.IsBreakTime.Value)
completeResume();
else
DrawableRuleset.RequestResume(completeResume);
@@ -551,9 +582,8 @@ namespace osu.Game.Screens.Play
Background.BlurAmount.Value = 0;
// bind component bindables.
- Background.IsBreakTime.BindTo(BreakOverlay.IsBreakTime);
- DimmableStoryboard.IsBreakTime.BindTo(BreakOverlay.IsBreakTime);
- DimmableVideo.IsBreakTime.BindTo(BreakOverlay.IsBreakTime);
+ Background.IsBreakTime.BindTo(breakTracker.IsBreakTime);
+ DimmableStoryboard.IsBreakTime.BindTo(breakTracker.IsBreakTime);
Background.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground);
DimmableStoryboard.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground);
@@ -581,7 +611,7 @@ namespace osu.Game.Screens.Play
if (completionProgressDelegate != null && !completionProgressDelegate.Cancelled && !completionProgressDelegate.Completed)
{
// proceed to result screen if beatmap already finished playing
- scheduleGotoRanking();
+ completionProgressDelegate.RunTask();
return true;
}
@@ -607,6 +637,39 @@ namespace osu.Game.Screens.Play
return base.OnExiting(next);
}
+ protected virtual void GotoRanking()
+ {
+ if (DrawableRuleset.ReplayScore != null)
+ {
+ // if a replay is present, we likely don't want to import into the local database.
+ this.Push(CreateResults(CreateScore()));
+ return;
+ }
+
+ LegacyByteArrayReader replayReader = null;
+
+ var score = new Score { ScoreInfo = CreateScore() };
+
+ if (recordingReplay?.Frames.Count > 0)
+ {
+ score.Replay = recordingReplay;
+
+ using (var stream = new MemoryStream())
+ {
+ new LegacyScoreEncoder(score, gameplayBeatmap.PlayableBeatmap).Encode(stream);
+ replayReader = new LegacyByteArrayReader(stream.ToArray(), "replay.osr");
+ }
+ }
+
+ scoreManager.Import(score.ScoreInfo, replayReader)
+ .ContinueWith(imported => Schedule(() =>
+ {
+ // screen may be in the exiting transition phase.
+ if (this.IsCurrentScreen())
+ this.Push(CreateResults(imported.Result));
+ }));
+ }
+
private void fadeOut(bool instant = false)
{
float fadeOutDuration = instant ? 0 : 250;
@@ -619,14 +682,7 @@ namespace osu.Game.Screens.Play
private void scheduleGotoRanking()
{
completionProgressDelegate?.Cancel();
- completionProgressDelegate = Schedule(delegate
- {
- var score = CreateScore();
- if (DrawableRuleset.ReplayScore == null)
- scoreManager.Import(score).ContinueWith(_ => Schedule(() => this.Push(CreateResults(score))));
- else
- this.Push(CreateResults(score));
- });
+ completionProgressDelegate = Schedule(GotoRanking);
}
#endregion
diff --git a/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs b/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs
index 9db3a587fa..d6c66d0751 100644
--- a/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs
+++ b/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs
@@ -15,7 +15,6 @@ namespace osu.Game.Screens.Play.PlayerSettings
private readonly PlayerSliderBar dimSliderBar;
private readonly PlayerSliderBar blurSliderBar;
private readonly PlayerCheckbox showStoryboardToggle;
- private readonly PlayerCheckbox showVideoToggle;
private readonly PlayerCheckbox beatmapSkinsToggle;
private readonly PlayerCheckbox beatmapHitsoundsToggle;
@@ -43,8 +42,7 @@ namespace osu.Game.Screens.Play.PlayerSettings
{
Text = "Toggles:"
},
- showStoryboardToggle = new PlayerCheckbox { LabelText = "Storyboards" },
- showVideoToggle = new PlayerCheckbox { LabelText = "Video" },
+ showStoryboardToggle = new PlayerCheckbox { LabelText = "Storyboard / Video" },
beatmapSkinsToggle = new PlayerCheckbox { LabelText = "Beatmap skins" },
beatmapHitsoundsToggle = new PlayerCheckbox { LabelText = "Beatmap hitsounds" }
};
@@ -56,7 +54,6 @@ namespace osu.Game.Screens.Play.PlayerSettings
dimSliderBar.Bindable = config.GetBindable(OsuSetting.DimLevel);
blurSliderBar.Bindable = config.GetBindable(OsuSetting.BlurLevel);
showStoryboardToggle.Current = config.GetBindable(OsuSetting.ShowStoryboard);
- showVideoToggle.Current = config.GetBindable(OsuSetting.ShowVideoBackground);
beatmapSkinsToggle.Current = config.GetBindable(OsuSetting.BeatmapSkins);
beatmapHitsoundsToggle.Current = config.GetBindable(OsuSetting.BeatmapHitsounds);
}
diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs
index b040549efc..74c853340d 100644
--- a/osu.Game/Screens/Play/ReplayPlayer.cs
+++ b/osu.Game/Screens/Play/ReplayPlayer.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd