mirror of
https://github.com/ppy/osu.git
synced 2024-12-15 08:22:56 +08:00
Merge branch 'master' into skin-bindables
This commit is contained in:
commit
3cac837acf
@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
|||||||
Entry = null;
|
Entry = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onEntryInvalidated() => refreshPoints();
|
private void onEntryInvalidated() => Scheduler.AddOnce(refreshPoints);
|
||||||
|
|
||||||
private void refreshPoints()
|
private void refreshPoints()
|
||||||
{
|
{
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Game.Replays;
|
using osu.Game.Replays;
|
||||||
using osu.Game.Rulesets.Replays;
|
using osu.Game.Rulesets.Replays;
|
||||||
@ -278,6 +279,54 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
setTime(-100, -100);
|
setTime(-100, -100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestReplayFramesSortStability()
|
||||||
|
{
|
||||||
|
const double repeating_time = 5000;
|
||||||
|
|
||||||
|
// add a collection of frames in shuffled order time-wise; each frame also stores its original index to check stability later.
|
||||||
|
// data is hand-picked and breaks if the unstable List<T>.Sort() is used.
|
||||||
|
// in theory this can still return a false-positive with another unstable algorithm if extremely unlucky,
|
||||||
|
// but there is no conceivable fool-proof way to prevent that anyways.
|
||||||
|
replay.Frames.AddRange(new[]
|
||||||
|
{
|
||||||
|
repeating_time,
|
||||||
|
0,
|
||||||
|
3000,
|
||||||
|
repeating_time,
|
||||||
|
repeating_time,
|
||||||
|
6000,
|
||||||
|
9000,
|
||||||
|
repeating_time,
|
||||||
|
repeating_time,
|
||||||
|
1000,
|
||||||
|
11000,
|
||||||
|
21000,
|
||||||
|
4000,
|
||||||
|
repeating_time,
|
||||||
|
repeating_time,
|
||||||
|
8000,
|
||||||
|
2000,
|
||||||
|
7000,
|
||||||
|
repeating_time,
|
||||||
|
repeating_time,
|
||||||
|
10000
|
||||||
|
}.Select((time, index) => new TestReplayFrame(time, true, index)));
|
||||||
|
|
||||||
|
replay.HasReceivedAllFrames = true;
|
||||||
|
|
||||||
|
// create a new handler with the replay for the sort to be performed.
|
||||||
|
handler = new TestInputHandler(replay);
|
||||||
|
|
||||||
|
// ensure sort stability by checking that the frames with time == repeating_time are sorted in ascending frame index order themselves.
|
||||||
|
var repeatingTimeFramesData = replay.Frames
|
||||||
|
.Cast<TestReplayFrame>()
|
||||||
|
.Where(f => f.Time == repeating_time)
|
||||||
|
.Select(f => f.FrameIndex);
|
||||||
|
|
||||||
|
Assert.That(repeatingTimeFramesData, Is.Ordered.Ascending);
|
||||||
|
}
|
||||||
|
|
||||||
private void setReplayFrames()
|
private void setReplayFrames()
|
||||||
{
|
{
|
||||||
replay.Frames = new List<ReplayFrame>
|
replay.Frames = new List<ReplayFrame>
|
||||||
@ -324,11 +373,13 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
private class TestReplayFrame : ReplayFrame
|
private class TestReplayFrame : ReplayFrame
|
||||||
{
|
{
|
||||||
public readonly bool IsImportant;
|
public readonly bool IsImportant;
|
||||||
|
public readonly int FrameIndex;
|
||||||
|
|
||||||
public TestReplayFrame(double time, bool isImportant = false)
|
public TestReplayFrame(double time, bool isImportant = false, int frameIndex = 0)
|
||||||
: base(time)
|
: base(time)
|
||||||
{
|
{
|
||||||
IsImportant = isImportant;
|
IsImportant = isImportant;
|
||||||
|
FrameIndex = frameIndex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
191
osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs
Normal file
191
osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Screens;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Judgements;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Screens.Play;
|
||||||
|
using osu.Game.Screens.Ranking;
|
||||||
|
using osu.Game.Storyboards;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Gameplay
|
||||||
|
{
|
||||||
|
public class TestSceneStoryboardWithOutro : PlayerTestScene
|
||||||
|
{
|
||||||
|
protected override bool HasCustomSteps => true;
|
||||||
|
|
||||||
|
protected new OutroPlayer Player => (OutroPlayer)base.Player;
|
||||||
|
|
||||||
|
private double currentStoryboardDuration;
|
||||||
|
|
||||||
|
private bool showResults = true;
|
||||||
|
|
||||||
|
private event Func<HealthProcessor, JudgementResult, bool> currentFailConditions;
|
||||||
|
|
||||||
|
[SetUpSteps]
|
||||||
|
public override void SetUpSteps()
|
||||||
|
{
|
||||||
|
base.SetUpSteps();
|
||||||
|
AddStep("enable storyboard", () => LocalConfig.SetValue(OsuSetting.ShowStoryboard, true));
|
||||||
|
AddStep("set dim level to 0", () => LocalConfig.SetValue<double>(OsuSetting.DimLevel, 0));
|
||||||
|
AddStep("reset fail conditions", () => currentFailConditions = (_, __) => false);
|
||||||
|
AddStep("set storyboard duration to 2s", () => currentStoryboardDuration = 2000);
|
||||||
|
AddStep("set ShowResults = true", () => showResults = true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestStoryboardSkipOutro()
|
||||||
|
{
|
||||||
|
CreateTest(null);
|
||||||
|
AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value);
|
||||||
|
AddStep("skip outro", () => InputManager.Key(osuTK.Input.Key.Space));
|
||||||
|
AddAssert("score shown", () => Player.IsScoreShown);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestStoryboardNoSkipOutro()
|
||||||
|
{
|
||||||
|
CreateTest(null);
|
||||||
|
AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration);
|
||||||
|
AddUntilStep("wait for score shown", () => Player.IsScoreShown);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestStoryboardExitToSkipOutro()
|
||||||
|
{
|
||||||
|
CreateTest(null);
|
||||||
|
AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value);
|
||||||
|
AddStep("exit via pause", () => Player.ExitViaPause());
|
||||||
|
AddAssert("score shown", () => Player.IsScoreShown);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase(false)]
|
||||||
|
[TestCase(true)]
|
||||||
|
public void TestStoryboardToggle(bool enabledAtBeginning)
|
||||||
|
{
|
||||||
|
CreateTest(null);
|
||||||
|
AddStep($"{(enabledAtBeginning ? "enable" : "disable")} storyboard", () => LocalConfig.SetValue(OsuSetting.ShowStoryboard, enabledAtBeginning));
|
||||||
|
AddStep("toggle storyboard", () => LocalConfig.SetValue(OsuSetting.ShowStoryboard, !enabledAtBeginning));
|
||||||
|
AddUntilStep("wait for score shown", () => Player.IsScoreShown);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestOutroEndsDuringFailAnimation()
|
||||||
|
{
|
||||||
|
CreateTest(() =>
|
||||||
|
{
|
||||||
|
AddStep("fail on first judgement", () => currentFailConditions = (_, __) => true);
|
||||||
|
AddStep("set storyboard duration to 1.3s", () => currentStoryboardDuration = 1300);
|
||||||
|
});
|
||||||
|
AddUntilStep("wait for fail", () => Player.HasFailed);
|
||||||
|
AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration);
|
||||||
|
AddUntilStep("wait for fail overlay", () => Player.FailOverlay.State.Value == Visibility.Visible);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestShowResultsFalse()
|
||||||
|
{
|
||||||
|
CreateTest(() =>
|
||||||
|
{
|
||||||
|
AddStep("set ShowResults = false", () => showResults = false);
|
||||||
|
});
|
||||||
|
AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration);
|
||||||
|
AddWaitStep("wait", 10);
|
||||||
|
AddAssert("no score shown", () => !Player.IsScoreShown);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestStoryboardEndsBeforeCompletion()
|
||||||
|
{
|
||||||
|
CreateTest(() => AddStep("set storyboard duration to .1s", () => currentStoryboardDuration = 100));
|
||||||
|
AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration);
|
||||||
|
AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value);
|
||||||
|
AddUntilStep("wait for score shown", () => Player.IsScoreShown);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestStoryboardRewind()
|
||||||
|
{
|
||||||
|
SkipOverlay.FadeContainer fadeContainer() => Player.ChildrenOfType<SkipOverlay.FadeContainer>().First();
|
||||||
|
|
||||||
|
CreateTest(null);
|
||||||
|
AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value);
|
||||||
|
AddUntilStep("skip overlay content becomes visible", () => fadeContainer().State == Visibility.Visible);
|
||||||
|
|
||||||
|
AddStep("rewind", () => Player.GameplayClockContainer.Seek(-1000));
|
||||||
|
AddUntilStep("skip overlay content not visible", () => fadeContainer().State == Visibility.Hidden);
|
||||||
|
|
||||||
|
AddUntilStep("skip overlay content becomes visible", () => fadeContainer().State == Visibility.Visible);
|
||||||
|
AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool AllowFail => true;
|
||||||
|
|
||||||
|
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
|
||||||
|
|
||||||
|
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new OutroPlayer(currentFailConditions, showResults);
|
||||||
|
|
||||||
|
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
|
||||||
|
{
|
||||||
|
var beatmap = new Beatmap();
|
||||||
|
beatmap.HitObjects.Add(new HitCircle());
|
||||||
|
return beatmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
|
||||||
|
{
|
||||||
|
return base.CreateWorkingBeatmap(beatmap, createStoryboard(currentStoryboardDuration));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Storyboard createStoryboard(double duration)
|
||||||
|
{
|
||||||
|
var storyboard = new Storyboard();
|
||||||
|
var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero);
|
||||||
|
sprite.TimelineGroup.Alpha.Add(Easing.None, 0, duration, 1, 0);
|
||||||
|
storyboard.GetLayer("Background").Add(sprite);
|
||||||
|
return storyboard;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected class OutroPlayer : TestPlayer
|
||||||
|
{
|
||||||
|
public void ExitViaPause() => PerformExit(true);
|
||||||
|
|
||||||
|
public new FailOverlay FailOverlay => base.FailOverlay;
|
||||||
|
|
||||||
|
public bool IsScoreShown => !this.IsCurrentScreen() && this.GetChildScreen() is ResultsScreen;
|
||||||
|
|
||||||
|
private event Func<HealthProcessor, JudgementResult, bool> failConditions;
|
||||||
|
|
||||||
|
public OutroPlayer(Func<HealthProcessor, JudgementResult, bool> failConditions, bool showResults = true)
|
||||||
|
: base(false, showResults)
|
||||||
|
{
|
||||||
|
this.failConditions = failConditions;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
HealthProcessor.FailConditions += failConditions;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Task ImportScore(Score score)
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,7 @@ using JetBrains.Annotations;
|
|||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
@ -112,8 +113,8 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
private void testInfoLabels(int expectedCount)
|
private void testInfoLabels(int expectedCount)
|
||||||
{
|
{
|
||||||
AddAssert("check info labels exists", () => infoWedge.Info.ChildrenOfType<BeatmapInfoWedge.BufferedWedgeInfo.InfoLabel>().Any());
|
AddAssert("check info labels exists", () => infoWedge.Info.ChildrenOfType<BeatmapInfoWedge.WedgeInfoText.InfoLabel>().Any());
|
||||||
AddAssert("check info labels count", () => infoWedge.Info.ChildrenOfType<BeatmapInfoWedge.BufferedWedgeInfo.InfoLabel>().Count() == expectedCount);
|
AddAssert("check info labels count", () => infoWedge.Info.ChildrenOfType<BeatmapInfoWedge.WedgeInfoText.InfoLabel>().Count() == expectedCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -124,7 +125,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
AddAssert("check default title", () => infoWedge.Info.TitleLabel.Current.Value == Beatmap.Default.BeatmapInfo.Metadata.Title);
|
AddAssert("check default title", () => infoWedge.Info.TitleLabel.Current.Value == Beatmap.Default.BeatmapInfo.Metadata.Title);
|
||||||
AddAssert("check default artist", () => infoWedge.Info.ArtistLabel.Current.Value == Beatmap.Default.BeatmapInfo.Metadata.Artist);
|
AddAssert("check default artist", () => infoWedge.Info.ArtistLabel.Current.Value == Beatmap.Default.BeatmapInfo.Metadata.Artist);
|
||||||
AddAssert("check empty author", () => !infoWedge.Info.MapperContainer.Children.Any());
|
AddAssert("check empty author", () => !infoWedge.Info.MapperContainer.Children.Any());
|
||||||
AddAssert("check no info labels", () => !infoWedge.Info.ChildrenOfType<BeatmapInfoWedge.BufferedWedgeInfo.InfoLabel>().Any());
|
AddAssert("check no info labels", () => !infoWedge.Info.ChildrenOfType<BeatmapInfoWedge.WedgeInfoText.InfoLabel>().Any());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -135,15 +136,15 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
private void selectBeatmap([CanBeNull] IBeatmap b)
|
private void selectBeatmap([CanBeNull] IBeatmap b)
|
||||||
{
|
{
|
||||||
BeatmapInfoWedge.BufferedWedgeInfo infoBefore = null;
|
Container containerBefore = null;
|
||||||
|
|
||||||
AddStep($"select {b?.Metadata.Title ?? "null"} beatmap", () =>
|
AddStep($"select {b?.Metadata.Title ?? "null"} beatmap", () =>
|
||||||
{
|
{
|
||||||
infoBefore = infoWedge.Info;
|
containerBefore = infoWedge.DisplayedContent;
|
||||||
infoWedge.Beatmap = Beatmap.Value = b == null ? Beatmap.Default : CreateWorkingBeatmap(b);
|
infoWedge.Beatmap = Beatmap.Value = b == null ? Beatmap.Default : CreateWorkingBeatmap(b);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("wait for async load", () => infoWedge.Info != infoBefore);
|
AddUntilStep("wait for async load", () => infoWedge.DisplayedContent != containerBefore);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IBeatmap createTestBeatmap(RulesetInfo ruleset)
|
private IBeatmap createTestBeatmap(RulesetInfo ruleset)
|
||||||
@ -193,7 +194,9 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
private class TestBeatmapInfoWedge : BeatmapInfoWedge
|
private class TestBeatmapInfoWedge : BeatmapInfoWedge
|
||||||
{
|
{
|
||||||
public new BufferedWedgeInfo Info => base.Info;
|
public new Container DisplayedContent => base.DisplayedContent;
|
||||||
|
|
||||||
|
public new WedgeInfoText Info => base.Info;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestHitObject : ConvertHitObject, IHasPosition
|
private class TestHitObject : ConvertHitObject, IHasPosition
|
||||||
|
@ -28,7 +28,7 @@ namespace osu.Game.Graphics.Containers
|
|||||||
protected override bool BlockNonPositionalInput => true;
|
protected override bool BlockNonPositionalInput => true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Temporary to allow for overlays in the main screen content to not dim theirselves.
|
/// Temporary to allow for overlays in the main screen content to not dim themselves.
|
||||||
/// Should be eventually replaced by dimming which is aware of the target dim container (traverse parent for certain interface type?).
|
/// Should be eventually replaced by dimming which is aware of the target dim container (traverse parent for certain interface type?).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected virtual bool DimMainContent => true;
|
protected virtual bool DimMainContent => true;
|
||||||
|
@ -141,7 +141,14 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
|||||||
|
|
||||||
scalingSettings.ForEach(s => bindPreviewEvent(s.Current));
|
scalingSettings.ForEach(s => bindPreviewEvent(s.Current));
|
||||||
|
|
||||||
windowModeDropdown.Current.ValueChanged += _ => updateResolutionDropdown();
|
windowModeDropdown.Current.BindValueChanged(mode =>
|
||||||
|
{
|
||||||
|
updateResolutionDropdown();
|
||||||
|
|
||||||
|
const string not_fullscreen_note = "Running without fullscreen mode will increase your input latency!";
|
||||||
|
|
||||||
|
windowModeDropdown.WarningText = mode.NewValue != WindowMode.Fullscreen ? not_fullscreen_note : string.Empty;
|
||||||
|
}, true);
|
||||||
|
|
||||||
windowModes.BindCollectionChanged((sender, args) =>
|
windowModes.BindCollectionChanged((sender, args) =>
|
||||||
{
|
{
|
||||||
|
@ -13,6 +13,8 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
|||||||
{
|
{
|
||||||
protected override string Header => "Renderer";
|
protected override string Header => "Renderer";
|
||||||
|
|
||||||
|
private SettingsEnumDropdown<FrameSync> frameLimiterDropdown;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(FrameworkConfigManager config, OsuConfigManager osuConfig)
|
private void load(FrameworkConfigManager config, OsuConfigManager osuConfig)
|
||||||
{
|
{
|
||||||
@ -20,7 +22,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
|||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
// TODO: this needs to be a custom dropdown at some point
|
// TODO: this needs to be a custom dropdown at some point
|
||||||
new SettingsEnumDropdown<FrameSync>
|
frameLimiterDropdown = new SettingsEnumDropdown<FrameSync>
|
||||||
{
|
{
|
||||||
LabelText = "Frame limiter",
|
LabelText = "Frame limiter",
|
||||||
Current = config.GetBindable<FrameSync>(FrameworkSetting.FrameSync)
|
Current = config.GetBindable<FrameSync>(FrameworkSetting.FrameSync)
|
||||||
@ -37,5 +39,17 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
frameLimiterDropdown.Current.BindValueChanged(limit =>
|
||||||
|
{
|
||||||
|
const string unlimited_frames_note = "Using unlimited frame limiter can lead to stutters, bad performance and overheating. It will not improve perceived latency. \"2x refresh rate\" is recommended.";
|
||||||
|
|
||||||
|
frameLimiterDropdown.WarningText = limit.NewValue == FrameSync.Unlimited ? unlimited_frames_note : string.Empty;
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,11 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Online.API;
|
||||||
|
using osu.Game.Users;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Settings.Sections.UserInterface
|
namespace osu.Game.Overlays.Settings.Sections.UserInterface
|
||||||
{
|
{
|
||||||
@ -11,9 +14,15 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
|
|||||||
{
|
{
|
||||||
protected override string Header => "Main Menu";
|
protected override string Header => "Main Menu";
|
||||||
|
|
||||||
|
private IBindable<User> user;
|
||||||
|
|
||||||
|
private SettingsEnumDropdown<BackgroundSource> backgroundSourceDropdown;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuConfigManager config)
|
private void load(OsuConfigManager config, IAPIProvider api)
|
||||||
{
|
{
|
||||||
|
user = api.LocalUser.GetBoundCopy();
|
||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new SettingsCheckbox
|
new SettingsCheckbox
|
||||||
@ -31,7 +40,7 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
|
|||||||
LabelText = "Intro sequence",
|
LabelText = "Intro sequence",
|
||||||
Current = config.GetBindable<IntroSequence>(OsuSetting.IntroSequence),
|
Current = config.GetBindable<IntroSequence>(OsuSetting.IntroSequence),
|
||||||
},
|
},
|
||||||
new SettingsEnumDropdown<BackgroundSource>
|
backgroundSourceDropdown = new SettingsEnumDropdown<BackgroundSource>
|
||||||
{
|
{
|
||||||
LabelText = "Background source",
|
LabelText = "Background source",
|
||||||
Current = config.GetBindable<BackgroundSource>(OsuSetting.MenuBackgroundSource),
|
Current = config.GetBindable<BackgroundSource>(OsuSetting.MenuBackgroundSource),
|
||||||
@ -43,5 +52,17 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
backgroundSourceDropdown.Current.BindValueChanged(source =>
|
||||||
|
{
|
||||||
|
const string not_supporter_note = "Changes to this setting will only apply with an active osu!supporter tag.";
|
||||||
|
|
||||||
|
backgroundSourceDropdown.WarningText = user.Value?.IsSupporter != true ? not_supporter_note : string.Empty;
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ using osu.Framework.Input.Events;
|
|||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osuTK;
|
using osu.Game.Graphics.Containers;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Settings
|
namespace osu.Game.Overlays.Settings
|
||||||
{
|
{
|
||||||
@ -36,10 +36,15 @@ namespace osu.Game.Overlays.Settings
|
|||||||
|
|
||||||
private SpriteText labelText;
|
private SpriteText labelText;
|
||||||
|
|
||||||
|
private OsuTextFlowContainer warningText;
|
||||||
|
|
||||||
public bool ShowsDefaultIndicator = true;
|
public bool ShowsDefaultIndicator = true;
|
||||||
|
|
||||||
public string TooltipText { get; set; }
|
public string TooltipText { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OsuColour colours { get; set; }
|
||||||
|
|
||||||
public virtual LocalisableString LabelText
|
public virtual LocalisableString LabelText
|
||||||
{
|
{
|
||||||
get => labelText?.Text ?? string.Empty;
|
get => labelText?.Text ?? string.Empty;
|
||||||
@ -57,6 +62,31 @@ namespace osu.Game.Overlays.Settings
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Text to be displayed at the bottom of this <see cref="SettingsItem{T}"/>.
|
||||||
|
/// Generally used to recommend the user change their setting as the current one is considered sub-optimal.
|
||||||
|
/// </summary>
|
||||||
|
public string WarningText
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (warningText == null)
|
||||||
|
{
|
||||||
|
// construct lazily for cases where the label is not needed (may be provided by the Control).
|
||||||
|
FlowContent.Add(warningText = new OsuTextFlowContainer
|
||||||
|
{
|
||||||
|
Colour = colours.Yellow,
|
||||||
|
Margin = new MarginPadding { Bottom = 5 },
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
warningText.Alpha = string.IsNullOrWhiteSpace(value) ? 0 : 1;
|
||||||
|
warningText.Text = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public virtual Bindable<T> Current
|
public virtual Bindable<T> Current
|
||||||
{
|
{
|
||||||
get => controlWithCurrent.Current;
|
get => controlWithCurrent.Current;
|
||||||
@ -92,7 +122,10 @@ namespace osu.Game.Overlays.Settings
|
|||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS },
|
Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS },
|
||||||
Child = Control = CreateControl()
|
Children = new[]
|
||||||
|
{
|
||||||
|
Control = CreateControl(),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -141,6 +174,7 @@ namespace osu.Game.Overlays.Settings
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Y;
|
RelativeSizeAxes = Axes.Y;
|
||||||
Width = SettingsPanel.CONTENT_MARGINS;
|
Width = SettingsPanel.CONTENT_MARGINS;
|
||||||
|
Padding = new MarginPadding { Vertical = 1.5f };
|
||||||
Alpha = 0f;
|
Alpha = 0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,7 +197,7 @@ namespace osu.Game.Overlays.Settings
|
|||||||
Type = EdgeEffectType.Glow,
|
Type = EdgeEffectType.Glow,
|
||||||
Radius = 2,
|
Radius = 2,
|
||||||
},
|
},
|
||||||
Size = new Vector2(0.33f, 0.8f),
|
Width = 0.33f,
|
||||||
Child = new Box { RelativeSizeAxes = Axes.Both },
|
Child = new Box { RelativeSizeAxes = Axes.Both },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -196,12 +230,6 @@ namespace osu.Game.Overlays.Settings
|
|||||||
UpdateState();
|
UpdateState();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetButtonColour(Color4 buttonColour)
|
|
||||||
{
|
|
||||||
this.buttonColour = buttonColour;
|
|
||||||
UpdateState();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UpdateState() => Scheduler.AddOnce(updateState);
|
public void UpdateState() => Scheduler.AddOnce(updateState);
|
||||||
|
|
||||||
private void updateState()
|
private void updateState()
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using osu.Game.Input.Handlers;
|
using osu.Game.Input.Handlers;
|
||||||
using osu.Game.Replays;
|
using osu.Game.Replays;
|
||||||
@ -97,7 +98,7 @@ namespace osu.Game.Rulesets.Replays
|
|||||||
{
|
{
|
||||||
// TODO: This replay frame ordering should be enforced on the Replay type.
|
// TODO: This replay frame ordering should be enforced on the Replay type.
|
||||||
// Currently, the ordering can be broken if the frames are added after this construction.
|
// Currently, the ordering can be broken if the frames are added after this construction.
|
||||||
replay.Frames.Sort((x, y) => x.Time.CompareTo(y.Time));
|
replay.Frames = replay.Frames.OrderBy(f => f.Time).ToList();
|
||||||
|
|
||||||
this.replay = replay;
|
this.replay = replay;
|
||||||
currentFrameIndex = -1;
|
currentFrameIndex = -1;
|
||||||
|
@ -34,13 +34,6 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
// For non-pooled rulesets, hitobjects are already present in the playfield which allows the blueprints to be loaded in the async context.
|
|
||||||
if (Composer != null)
|
|
||||||
{
|
|
||||||
foreach (var obj in Composer.HitObjects)
|
|
||||||
AddBlueprintFor(obj.HitObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
selectedHitObjects.BindTo(Beatmap.SelectedHitObjects);
|
selectedHitObjects.BindTo(Beatmap.SelectedHitObjects);
|
||||||
selectedHitObjects.CollectionChanged += (selectedObjects, args) =>
|
selectedHitObjects.CollectionChanged += (selectedObjects, args) =>
|
||||||
{
|
{
|
||||||
@ -69,7 +62,6 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
|
|
||||||
if (Composer != null)
|
if (Composer != null)
|
||||||
{
|
{
|
||||||
// For pooled rulesets, blueprints must be added for hitobjects already "current" as they would've not been "current" during the async load addition process above.
|
|
||||||
foreach (var obj in Composer.HitObjects)
|
foreach (var obj in Composer.HitObjects)
|
||||||
AddBlueprintFor(obj.HitObject);
|
AddBlueprintFor(obj.HitObject);
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
{
|
{
|
||||||
AllowPause = false,
|
AllowPause = false,
|
||||||
AllowRestart = false,
|
AllowRestart = false,
|
||||||
AllowSkippingIntro = false,
|
AllowSkipping = false,
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
this.userIds = userIds;
|
this.userIds = userIds;
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Storyboards;
|
using osu.Game.Storyboards;
|
||||||
@ -19,6 +20,14 @@ namespace osu.Game.Screens.Play
|
|||||||
private readonly Storyboard storyboard;
|
private readonly Storyboard storyboard;
|
||||||
private DrawableStoryboard drawableStoryboard;
|
private DrawableStoryboard drawableStoryboard;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the storyboard is considered finished.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This is true by default in here, until an actual drawable storyboard is loaded, in which case it'll bind to it.
|
||||||
|
/// </remarks>
|
||||||
|
public IBindable<bool> HasStoryboardEnded = new BindableBool(true);
|
||||||
|
|
||||||
public DimmableStoryboard(Storyboard storyboard)
|
public DimmableStoryboard(Storyboard storyboard)
|
||||||
{
|
{
|
||||||
this.storyboard = storyboard;
|
this.storyboard = storyboard;
|
||||||
@ -49,6 +58,7 @@ namespace osu.Game.Screens.Play
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
drawableStoryboard = storyboard.CreateDrawable();
|
drawableStoryboard = storyboard.CreateDrawable();
|
||||||
|
HasStoryboardEnded.BindTo(drawableStoryboard.HasStoryboardEnded);
|
||||||
|
|
||||||
if (async)
|
if (async)
|
||||||
LoadComponentAsync(drawableStoryboard, onStoryboardCreated);
|
LoadComponentAsync(drawableStoryboard, onStoryboardCreated);
|
||||||
|
@ -104,7 +104,8 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
private BreakTracker breakTracker;
|
private BreakTracker breakTracker;
|
||||||
|
|
||||||
private SkipOverlay skipOverlay;
|
private SkipOverlay skipIntroOverlay;
|
||||||
|
private SkipOverlay skipOutroOverlay;
|
||||||
|
|
||||||
protected ScoreProcessor ScoreProcessor { get; private set; }
|
protected ScoreProcessor ScoreProcessor { get; private set; }
|
||||||
|
|
||||||
@ -244,7 +245,6 @@ namespace osu.Game.Screens.Play
|
|||||||
HUDOverlay.ShowHud.Value = false;
|
HUDOverlay.ShowHud.Value = false;
|
||||||
HUDOverlay.ShowHud.Disabled = true;
|
HUDOverlay.ShowHud.Disabled = true;
|
||||||
BreakOverlay.Hide();
|
BreakOverlay.Hide();
|
||||||
skipOverlay.Hide();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DrawableRuleset.FrameStableClock.WaitingOnFrames.BindValueChanged(waiting =>
|
DrawableRuleset.FrameStableClock.WaitingOnFrames.BindValueChanged(waiting =>
|
||||||
@ -281,8 +281,14 @@ namespace osu.Game.Screens.Play
|
|||||||
ScoreProcessor.RevertResult(r);
|
ScoreProcessor.RevertResult(r);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
DimmableStoryboard.HasStoryboardEnded.ValueChanged += storyboardEnded =>
|
||||||
|
{
|
||||||
|
if (storyboardEnded.NewValue && completionProgressDelegate == null)
|
||||||
|
updateCompletionState();
|
||||||
|
};
|
||||||
|
|
||||||
// Bind the judgement processors to ourselves
|
// Bind the judgement processors to ourselves
|
||||||
ScoreProcessor.HasCompleted.ValueChanged += updateCompletionState;
|
ScoreProcessor.HasCompleted.BindValueChanged(_ => updateCompletionState());
|
||||||
HealthProcessor.Failed += onFail;
|
HealthProcessor.Failed += onFail;
|
||||||
|
|
||||||
foreach (var mod in Mods.Value.OfType<IApplicableToScoreProcessor>())
|
foreach (var mod in Mods.Value.OfType<IApplicableToScoreProcessor>())
|
||||||
@ -355,10 +361,15 @@ namespace osu.Game.Screens.Play
|
|||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre
|
Origin = Anchor.Centre
|
||||||
},
|
},
|
||||||
skipOverlay = new SkipOverlay(DrawableRuleset.GameplayStartTime)
|
skipIntroOverlay = new SkipOverlay(DrawableRuleset.GameplayStartTime)
|
||||||
{
|
{
|
||||||
RequestSkip = performUserRequestedSkip
|
RequestSkip = performUserRequestedSkip
|
||||||
},
|
},
|
||||||
|
skipOutroOverlay = new SkipOverlay(Beatmap.Value.Storyboard.LatestEventTime ?? 0)
|
||||||
|
{
|
||||||
|
RequestSkip = () => updateCompletionState(true),
|
||||||
|
Alpha = 0
|
||||||
|
},
|
||||||
FailOverlay = new FailOverlay
|
FailOverlay = new FailOverlay
|
||||||
{
|
{
|
||||||
OnRetry = Restart,
|
OnRetry = Restart,
|
||||||
@ -385,12 +396,15 @@ namespace osu.Game.Screens.Play
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!Configuration.AllowSkipping || !DrawableRuleset.AllowGameplayOverlays)
|
||||||
|
{
|
||||||
|
skipIntroOverlay.Expire();
|
||||||
|
skipOutroOverlay.Expire();
|
||||||
|
}
|
||||||
|
|
||||||
if (GameplayClockContainer is MasterGameplayClockContainer master)
|
if (GameplayClockContainer is MasterGameplayClockContainer master)
|
||||||
HUDOverlay.PlayerSettingsOverlay.PlaybackSettings.UserPlaybackRate.BindTarget = master.UserPlaybackRate;
|
HUDOverlay.PlayerSettingsOverlay.PlaybackSettings.UserPlaybackRate.BindTarget = master.UserPlaybackRate;
|
||||||
|
|
||||||
if (!Configuration.AllowSkippingIntro)
|
|
||||||
skipOverlay.Expire();
|
|
||||||
|
|
||||||
if (Configuration.AllowRestart)
|
if (Configuration.AllowRestart)
|
||||||
{
|
{
|
||||||
container.Add(new HotkeyRetryOverlay
|
container.Add(new HotkeyRetryOverlay
|
||||||
@ -525,6 +539,10 @@ namespace osu.Game.Screens.Play
|
|||||||
Pause();
|
Pause();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if the score is ready for display but results screen has not been pushed yet (e.g. storyboard is still playing beyond gameplay), then transition to results screen instead of exiting.
|
||||||
|
if (prepareScoreForDisplayTask != null)
|
||||||
|
updateCompletionState(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.Exit();
|
this.Exit();
|
||||||
@ -564,17 +582,23 @@ namespace osu.Game.Screens.Play
|
|||||||
private ScheduledDelegate completionProgressDelegate;
|
private ScheduledDelegate completionProgressDelegate;
|
||||||
private Task<ScoreInfo> prepareScoreForDisplayTask;
|
private Task<ScoreInfo> prepareScoreForDisplayTask;
|
||||||
|
|
||||||
private void updateCompletionState(ValueChangedEvent<bool> completionState)
|
/// <summary>
|
||||||
|
/// Handles changes in player state which may progress the completion of gameplay / this screen's lifetime.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="skipStoryboardOutro">If in a state where a storyboard outro is to be played, offers the choice of skipping beyond it.</param>
|
||||||
|
/// <exception cref="InvalidOperationException">Thrown if this method is called more than once without changing state.</exception>
|
||||||
|
private void updateCompletionState(bool skipStoryboardOutro = false)
|
||||||
{
|
{
|
||||||
// screen may be in the exiting transition phase.
|
// screen may be in the exiting transition phase.
|
||||||
if (!this.IsCurrentScreen())
|
if (!this.IsCurrentScreen())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!completionState.NewValue)
|
if (!ScoreProcessor.HasCompleted.Value)
|
||||||
{
|
{
|
||||||
completionProgressDelegate?.Cancel();
|
completionProgressDelegate?.Cancel();
|
||||||
completionProgressDelegate = null;
|
completionProgressDelegate = null;
|
||||||
ValidForResume = true;
|
ValidForResume = true;
|
||||||
|
skipOutroOverlay.Hide();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -614,6 +638,20 @@ namespace osu.Game.Screens.Play
|
|||||||
return score.ScoreInfo;
|
return score.ScoreInfo;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (skipStoryboardOutro)
|
||||||
|
{
|
||||||
|
scheduleCompletion();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool storyboardHasOutro = DimmableStoryboard.ContentDisplayed && !DimmableStoryboard.HasStoryboardEnded.Value;
|
||||||
|
|
||||||
|
if (storyboardHasOutro)
|
||||||
|
{
|
||||||
|
skipOutroOverlay.Show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
using (BeginDelayedSequence(RESULTS_DISPLAY_DELAY))
|
using (BeginDelayedSequence(RESULTS_DISPLAY_DELAY))
|
||||||
scheduleCompletion();
|
scheduleCompletion();
|
||||||
}
|
}
|
||||||
|
@ -21,8 +21,8 @@ namespace osu.Game.Screens.Play
|
|||||||
public bool AllowRestart { get; set; } = true;
|
public bool AllowRestart { get; set; } = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether the player should be allowed to skip the intro, advancing to the start of gameplay.
|
/// Whether the player should be allowed to skip intros/outros, advancing to the start of gameplay or the end of a storyboard.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool AllowSkippingIntro { get; set; } = true;
|
public bool AllowSkipping { get; set; } = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,19 +8,19 @@ using osu.Framework.Audio;
|
|||||||
using osu.Framework.Audio.Sample;
|
using osu.Framework.Audio.Sample;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Input.Bindings;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Input.Bindings;
|
||||||
using osu.Game.Screens.Ranking;
|
using osu.Game.Screens.Ranking;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
using osu.Framework.Graphics.Shapes;
|
|
||||||
using osu.Framework.Graphics.Sprites;
|
|
||||||
using osu.Game.Graphics.Containers;
|
|
||||||
using osu.Framework.Input.Bindings;
|
|
||||||
using osu.Framework.Input.Events;
|
|
||||||
using osu.Framework.Utils;
|
|
||||||
using osu.Game.Input.Bindings;
|
|
||||||
|
|
||||||
namespace osu.Game.Screens.Play
|
namespace osu.Game.Screens.Play
|
||||||
{
|
{
|
||||||
@ -92,6 +92,18 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
private double fadeOutBeginTime => startTime - MasterGameplayClockContainer.MINIMUM_SKIP_TIME;
|
private double fadeOutBeginTime => startTime - MasterGameplayClockContainer.MINIMUM_SKIP_TIME;
|
||||||
|
|
||||||
|
public override void Hide()
|
||||||
|
{
|
||||||
|
base.Hide();
|
||||||
|
fadeContainer.Hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Show()
|
||||||
|
{
|
||||||
|
base.Show();
|
||||||
|
fadeContainer.Show();
|
||||||
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
@ -147,7 +159,7 @@ namespace osu.Game.Screens.Play
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private class FadeContainer : Container, IStateful<Visibility>
|
public class FadeContainer : Container, IStateful<Visibility>
|
||||||
{
|
{
|
||||||
public event Action<Visibility> StateChanged;
|
public event Action<Visibility> StateChanged;
|
||||||
|
|
||||||
@ -170,7 +182,7 @@ namespace osu.Game.Screens.Play
|
|||||||
switch (state)
|
switch (state)
|
||||||
{
|
{
|
||||||
case Visibility.Visible:
|
case Visibility.Visible:
|
||||||
// we may be triggered to become visible mnultiple times but we only want to transform once.
|
// we may be triggered to become visible multiple times but we only want to transform once.
|
||||||
if (stateChanged)
|
if (stateChanged)
|
||||||
this.FadeIn(500, Easing.OutExpo);
|
this.FadeIn(500, Easing.OutExpo);
|
||||||
|
|
||||||
|
@ -11,7 +11,6 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Colour;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
@ -49,7 +48,9 @@ namespace osu.Game.Screens.Select
|
|||||||
|
|
||||||
private IBindable<StarDifficulty?> beatmapDifficulty;
|
private IBindable<StarDifficulty?> beatmapDifficulty;
|
||||||
|
|
||||||
protected BufferedWedgeInfo Info;
|
protected Container DisplayedContent { get; private set; }
|
||||||
|
|
||||||
|
protected WedgeInfoText Info { get; private set; }
|
||||||
|
|
||||||
public BeatmapInfoWedge()
|
public BeatmapInfoWedge()
|
||||||
{
|
{
|
||||||
@ -110,9 +111,9 @@ namespace osu.Game.Screens.Select
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool IsPresent => base.IsPresent || Info == null; // Visibility is updated in the LoadComponentAsync callback
|
public override bool IsPresent => base.IsPresent || DisplayedContent == null; // Visibility is updated in the LoadComponentAsync callback
|
||||||
|
|
||||||
private BufferedWedgeInfo loadingInfo;
|
private Container loadingInfo;
|
||||||
|
|
||||||
private void updateDisplay()
|
private void updateDisplay()
|
||||||
{
|
{
|
||||||
@ -124,9 +125,9 @@ namespace osu.Game.Screens.Select
|
|||||||
{
|
{
|
||||||
State.Value = beatmap == null ? Visibility.Hidden : Visibility.Visible;
|
State.Value = beatmap == null ? Visibility.Hidden : Visibility.Visible;
|
||||||
|
|
||||||
Info?.FadeOut(250);
|
DisplayedContent?.FadeOut(250);
|
||||||
Info?.Expire();
|
DisplayedContent?.Expire();
|
||||||
Info = null;
|
DisplayedContent = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (beatmap == null)
|
if (beatmap == null)
|
||||||
@ -135,17 +136,23 @@ namespace osu.Game.Screens.Select
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
LoadComponentAsync(loadingInfo = new BufferedWedgeInfo(beatmap, ruleset.Value, mods.Value, beatmapDifficulty.Value ?? new StarDifficulty())
|
LoadComponentAsync(loadingInfo = new Container
|
||||||
{
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
Shear = -Shear,
|
Shear = -Shear,
|
||||||
Depth = Info?.Depth + 1 ?? 0
|
Depth = DisplayedContent?.Depth + 1 ?? 0,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new BeatmapInfoWedgeBackground(beatmap),
|
||||||
|
Info = new WedgeInfoText(beatmap, ruleset.Value, mods.Value, beatmapDifficulty.Value ?? new StarDifficulty()),
|
||||||
|
}
|
||||||
}, loaded =>
|
}, loaded =>
|
||||||
{
|
{
|
||||||
// ensure we are the most recent loaded wedge.
|
// ensure we are the most recent loaded wedge.
|
||||||
if (loaded != loadingInfo) return;
|
if (loaded != loadingInfo) return;
|
||||||
|
|
||||||
removeOldInfo();
|
removeOldInfo();
|
||||||
Add(Info = loaded);
|
Add(DisplayedContent = loaded);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -156,7 +163,7 @@ namespace osu.Game.Screens.Select
|
|||||||
cancellationSource?.Cancel();
|
cancellationSource?.Cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class BufferedWedgeInfo : BufferedContainer
|
public class WedgeInfoText : Container
|
||||||
{
|
{
|
||||||
public OsuSpriteText VersionLabel { get; private set; }
|
public OsuSpriteText VersionLabel { get; private set; }
|
||||||
public OsuSpriteText TitleLabel { get; private set; }
|
public OsuSpriteText TitleLabel { get; private set; }
|
||||||
@ -176,8 +183,7 @@ namespace osu.Game.Screens.Select
|
|||||||
|
|
||||||
private ModSettingChangeTracker settingChangeTracker;
|
private ModSettingChangeTracker settingChangeTracker;
|
||||||
|
|
||||||
public BufferedWedgeInfo(WorkingBeatmap beatmap, RulesetInfo userRuleset, IReadOnlyList<Mod> mods, StarDifficulty difficulty)
|
public WedgeInfoText(WorkingBeatmap beatmap, RulesetInfo userRuleset, IReadOnlyList<Mod> mods, StarDifficulty difficulty)
|
||||||
: base(pixelSnapping: true)
|
|
||||||
{
|
{
|
||||||
this.beatmap = beatmap;
|
this.beatmap = beatmap;
|
||||||
ruleset = userRuleset ?? beatmap.BeatmapInfo.Ruleset;
|
ruleset = userRuleset ?? beatmap.BeatmapInfo.Ruleset;
|
||||||
@ -191,7 +197,6 @@ namespace osu.Game.Screens.Select
|
|||||||
var beatmapInfo = beatmap.BeatmapInfo;
|
var beatmapInfo = beatmap.BeatmapInfo;
|
||||||
var metadata = beatmapInfo.Metadata ?? beatmap.BeatmapSetInfo?.Metadata ?? new BeatmapMetadata();
|
var metadata = beatmapInfo.Metadata ?? beatmap.BeatmapSetInfo?.Metadata ?? new BeatmapMetadata();
|
||||||
|
|
||||||
CacheDrawnFrameBuffer = true;
|
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
titleBinding = localisation.GetLocalisedString(new RomanisableString(metadata.TitleUnicode, metadata.Title));
|
titleBinding = localisation.GetLocalisedString(new RomanisableString(metadata.TitleUnicode, metadata.Title));
|
||||||
@ -199,32 +204,6 @@ namespace osu.Game.Screens.Select
|
|||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
// We will create the white-to-black gradient by modulating transparency and having
|
|
||||||
// a black backdrop. This results in an sRGB-space gradient and not linear space,
|
|
||||||
// transitioning from white to black more perceptually uniformly.
|
|
||||||
new Box
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Colour = Color4.Black,
|
|
||||||
},
|
|
||||||
// We use a container, such that we can set the colour gradient to go across the
|
|
||||||
// vertices of the masked container instead of the vertices of the (larger) sprite.
|
|
||||||
new Container
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Colour = ColourInfo.GradientVertical(Color4.White, Color4.White.Opacity(0.3f)),
|
|
||||||
Children = new[]
|
|
||||||
{
|
|
||||||
// Zoomed-in and cropped beatmap background
|
|
||||||
new BeatmapBackgroundSprite(beatmap)
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
FillMode = FillMode.Fill,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
new DifficultyColourBar(starDifficulty)
|
new DifficultyColourBar(starDifficulty)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Y,
|
RelativeSizeAxes = Axes.Y,
|
||||||
@ -340,7 +319,6 @@ namespace osu.Game.Screens.Select
|
|||||||
{
|
{
|
||||||
ArtistLabel.Text = artistBinding.Value;
|
ArtistLabel.Text = artistBinding.Value;
|
||||||
TitleLabel.Text = string.IsNullOrEmpty(source) ? titleBinding.Value : source + " — " + titleBinding.Value;
|
TitleLabel.Text = string.IsNullOrEmpty(source) ? titleBinding.Value : source + " — " + titleBinding.Value;
|
||||||
ForceRedraw();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addInfoLabels()
|
private void addInfoLabels()
|
||||||
@ -426,8 +404,6 @@ namespace osu.Game.Screens.Select
|
|||||||
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Bpm),
|
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Bpm),
|
||||||
Content = labelText
|
Content = labelText
|
||||||
});
|
});
|
||||||
|
|
||||||
ForceRedraw();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private OsuSpriteText[] getMapper(BeatmapMetadata metadata)
|
private OsuSpriteText[] getMapper(BeatmapMetadata metadata)
|
||||||
|
66
osu.Game/Screens/Select/BeatmapInfoWedgeBackground.cs
Normal file
66
osu.Game/Screens/Select/BeatmapInfoWedgeBackground.cs
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osuTK.Graphics;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Colour;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.Drawables;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Select
|
||||||
|
{
|
||||||
|
internal class BeatmapInfoWedgeBackground : CompositeDrawable
|
||||||
|
{
|
||||||
|
private readonly WorkingBeatmap beatmap;
|
||||||
|
|
||||||
|
public BeatmapInfoWedgeBackground(WorkingBeatmap beatmap)
|
||||||
|
{
|
||||||
|
this.beatmap = beatmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
InternalChild = new BufferedContainer
|
||||||
|
{
|
||||||
|
CacheDrawnFrameBuffer = true,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
// We will create the white-to-black gradient by modulating transparency and having
|
||||||
|
// a black backdrop. This results in an sRGB-space gradient and not linear space,
|
||||||
|
// transitioning from white to black more perceptually uniformly.
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = Color4.Black,
|
||||||
|
},
|
||||||
|
// We use a container, such that we can set the colour gradient to go across the
|
||||||
|
// vertices of the masked container instead of the vertices of the (larger) sprite.
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = ColourInfo.GradientVertical(Color4.White, Color4.White.Opacity(0.3f)),
|
||||||
|
Children = new[]
|
||||||
|
{
|
||||||
|
// Zoomed-in and cropped beatmap background
|
||||||
|
new BeatmapBackgroundSprite(beatmap)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
FillMode = FillMode.Fill,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,7 @@ using System.Linq;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
@ -19,6 +20,13 @@ namespace osu.Game.Storyboards.Drawables
|
|||||||
[Cached]
|
[Cached]
|
||||||
public Storyboard Storyboard { get; }
|
public Storyboard Storyboard { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the storyboard is considered finished.
|
||||||
|
/// </summary>
|
||||||
|
public IBindable<bool> HasStoryboardEnded => hasStoryboardEnded;
|
||||||
|
|
||||||
|
private readonly BindableBool hasStoryboardEnded = new BindableBool();
|
||||||
|
|
||||||
protected override Container<DrawableStoryboardLayer> Content { get; }
|
protected override Container<DrawableStoryboardLayer> Content { get; }
|
||||||
|
|
||||||
protected override Vector2 DrawScale => new Vector2(Parent.DrawHeight / 480);
|
protected override Vector2 DrawScale => new Vector2(Parent.DrawHeight / 480);
|
||||||
@ -39,6 +47,8 @@ namespace osu.Game.Storyboards.Drawables
|
|||||||
|
|
||||||
public override bool RemoveCompletedTransforms => false;
|
public override bool RemoveCompletedTransforms => false;
|
||||||
|
|
||||||
|
private double? lastEventEndTime;
|
||||||
|
|
||||||
private DependencyContainer dependencies;
|
private DependencyContainer dependencies;
|
||||||
|
|
||||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) =>
|
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) =>
|
||||||
@ -73,6 +83,14 @@ namespace osu.Game.Storyboards.Drawables
|
|||||||
|
|
||||||
Add(layer.CreateDrawable());
|
Add(layer.CreateDrawable());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lastEventEndTime = Storyboard.LatestEventTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
hasStoryboardEnded.Value = lastEventEndTime == null || Time.Current >= lastEventEndTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DrawableStoryboardLayer OverlayLayer => Children.Single(layer => layer.Name == "Overlay");
|
public DrawableStoryboardLayer OverlayLayer => Children.Single(layer => layer.Name == "Overlay");
|
||||||
|
@ -14,4 +14,17 @@ namespace osu.Game.Storyboards
|
|||||||
|
|
||||||
Drawable CreateDrawable();
|
Drawable CreateDrawable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class StoryboardElementExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the end time of this storyboard element.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This returns the <see cref="IStoryboardElementWithDuration.EndTime"/> where available, falling back to <see cref="IStoryboardElement.StartTime"/> otherwise.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="element">The storyboard element.</param>
|
||||||
|
/// <returns>The end time of this element.</returns>
|
||||||
|
public static double GetEndTime(this IStoryboardElement element) => (element as IStoryboardElementWithDuration)?.EndTime ?? element.StartTime;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
21
osu.Game/Storyboards/IStoryboardElementWithDuration.cs
Normal file
21
osu.Game/Storyboards/IStoryboardElementWithDuration.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
namespace osu.Game.Storyboards
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A <see cref="IStoryboardElement"/> that ends at a different time than its start time.
|
||||||
|
/// </summary>
|
||||||
|
public interface IStoryboardElementWithDuration : IStoryboardElement
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The time at which the <see cref="IStoryboardElement"/> ends.
|
||||||
|
/// </summary>
|
||||||
|
double EndTime { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The duration of the StoryboardElement.
|
||||||
|
/// </summary>
|
||||||
|
double Duration => EndTime - StartTime;
|
||||||
|
}
|
||||||
|
}
|
@ -36,6 +36,16 @@ namespace osu.Game.Storyboards
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
public double? EarliestEventTime => Layers.SelectMany(l => l.Elements).OrderBy(e => e.StartTime).FirstOrDefault()?.StartTime;
|
public double? EarliestEventTime => Layers.SelectMany(l => l.Elements).OrderBy(e => e.StartTime).FirstOrDefault()?.StartTime;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Across all layers, find the latest point in time that a storyboard element ends at.
|
||||||
|
/// Will return null if there are no elements.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This iterates all elements and as such should be used sparingly or stored locally.
|
||||||
|
/// Videos and samples return StartTime as their EndTIme.
|
||||||
|
/// </remarks>
|
||||||
|
public double? LatestEventTime => Layers.SelectMany(l => l.Elements).OrderBy(e => e.GetEndTime()).LastOrDefault()?.GetEndTime();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Depth of the currently front-most storyboard layer, excluding the overlay layer.
|
/// Depth of the currently front-most storyboard layer, excluding the overlay layer.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -11,7 +11,7 @@ using JetBrains.Annotations;
|
|||||||
|
|
||||||
namespace osu.Game.Storyboards
|
namespace osu.Game.Storyboards
|
||||||
{
|
{
|
||||||
public class StoryboardSprite : IStoryboardElement
|
public class StoryboardSprite : IStoryboardElementWithDuration
|
||||||
{
|
{
|
||||||
private readonly List<CommandLoop> loops = new List<CommandLoop>();
|
private readonly List<CommandLoop> loops = new List<CommandLoop>();
|
||||||
private readonly List<CommandTrigger> triggers = new List<CommandTrigger>();
|
private readonly List<CommandTrigger> triggers = new List<CommandTrigger>();
|
||||||
|
Loading…
Reference in New Issue
Block a user