mirror of
https://github.com/ppy/osu.git
synced 2025-01-15 07:22:55 +08:00
Merge remote-tracking branch 'upstream/master' into fix-control-point-ordering
This commit is contained in:
commit
aca77a7ae2
@ -57,33 +57,20 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
|
||||
CatchHitObject lastObject = null;
|
||||
|
||||
foreach (var hitObject in beatmap.HitObjects.OfType<CatchHitObject>())
|
||||
// In 2B beatmaps, it is possible that a normal Fruit is placed in the middle of a JuiceStream.
|
||||
foreach (var hitObject in beatmap.HitObjects
|
||||
.SelectMany(obj => obj is JuiceStream stream ? stream.NestedHitObjects : new[] { obj })
|
||||
.Cast<CatchHitObject>()
|
||||
.OrderBy(x => x.StartTime))
|
||||
{
|
||||
if (lastObject == null)
|
||||
{
|
||||
lastObject = hitObject;
|
||||
// We want to only consider fruits that contribute to the combo.
|
||||
if (hitObject is BananaShower || hitObject is TinyDroplet)
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (hitObject)
|
||||
{
|
||||
// We want to only consider fruits that contribute to the combo. Droplets are addressed as accuracy and spinners are not relevant for "skill" calculations.
|
||||
case Fruit fruit:
|
||||
yield return new CatchDifficultyHitObject(fruit, lastObject, clockRate, halfCatchWidth);
|
||||
if (lastObject != null)
|
||||
yield return new CatchDifficultyHitObject(hitObject, lastObject, clockRate, halfCatchWidth);
|
||||
|
||||
lastObject = hitObject;
|
||||
break;
|
||||
|
||||
case JuiceStream _:
|
||||
foreach (var nested in hitObject.NestedHitObjects.OfType<CatchHitObject>().Where(o => !(o is TinyDroplet)))
|
||||
{
|
||||
yield return new CatchDifficultyHitObject(nested, lastObject, clockRate, halfCatchWidth);
|
||||
|
||||
lastObject = nested;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
lastObject = hitObject;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -353,6 +353,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
|
||||
|
||||
protected override bool PauseOnFocusLost => false;
|
||||
|
||||
public ScoreAccessibleReplayPlayer(Score score)
|
||||
: base(score, false, false)
|
||||
{
|
||||
|
@ -160,9 +160,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
base.SkinChanged(skin, allowFallback);
|
||||
|
||||
Body.AccentColour = skin.GetValue<SkinConfiguration, Color4?>(s => s.CustomColours.ContainsKey("SliderTrackOverride") ? s.CustomColours["SliderTrackOverride"] : (Color4?)null) ?? Body.AccentColour;
|
||||
Body.BorderColour = skin.GetValue<SkinConfiguration, Color4?>(s => s.CustomColours.ContainsKey("SliderBorder") ? s.CustomColours["SliderBorder"] : (Color4?)null) ?? Body.BorderColour;
|
||||
Ball.AccentColour = skin.GetValue<SkinConfiguration, Color4?>(s => s.CustomColours.ContainsKey("SliderBall") ? s.CustomColours["SliderBall"] : (Color4?)null) ?? Ball.AccentColour;
|
||||
Body.BorderSize = skin.GetValue<SkinConfiguration, float?>(s => s.SliderBorderSize) ?? SliderBody.DEFAULT_BORDER_SIZE;
|
||||
Body.AccentColour = skin.GetValue<SkinConfiguration, Color4?>(s => s.CustomColours.ContainsKey("SliderTrackOverride") ? s.CustomColours["SliderTrackOverride"] : (Color4?)null) ?? AccentColour;
|
||||
Body.BorderColour = skin.GetValue<SkinConfiguration, Color4?>(s => s.CustomColours.ContainsKey("SliderBorder") ? s.CustomColours["SliderBorder"] : (Color4?)null) ?? Color4.White;
|
||||
Ball.AccentColour = skin.GetValue<SkinConfiguration, Color4?>(s => s.CustomColours.ContainsKey("SliderBall") ? s.CustomColours["SliderBall"] : (Color4?)null) ?? AccentColour;
|
||||
}
|
||||
|
||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||
|
@ -14,6 +14,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
{
|
||||
public abstract class SliderBody : CompositeDrawable
|
||||
{
|
||||
public const float DEFAULT_BORDER_SIZE = 1;
|
||||
|
||||
private readonly SliderPath path;
|
||||
protected Path Path => path;
|
||||
|
||||
@ -64,6 +66,23 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to size the path border.
|
||||
/// </summary>
|
||||
public float BorderSize
|
||||
{
|
||||
get => path.BorderSize;
|
||||
set
|
||||
{
|
||||
if (path.BorderSize == value)
|
||||
return;
|
||||
|
||||
path.BorderSize = value;
|
||||
|
||||
container.ForceRedraw();
|
||||
}
|
||||
}
|
||||
|
||||
public Quad PathDrawQuad => container.ScreenSpaceDrawQuad;
|
||||
|
||||
protected SliderBody()
|
||||
@ -92,6 +111,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
|
||||
private class SliderPath : SmoothPath
|
||||
{
|
||||
private const float border_max_size = 8f;
|
||||
private const float border_min_size = 0f;
|
||||
|
||||
private const float border_portion = 0.128f;
|
||||
private const float gradient_portion = 1 - border_portion;
|
||||
|
||||
@ -130,12 +152,33 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
}
|
||||
}
|
||||
|
||||
private float borderSize = DEFAULT_BORDER_SIZE;
|
||||
|
||||
public float BorderSize
|
||||
{
|
||||
get => borderSize;
|
||||
set
|
||||
{
|
||||
if (borderSize == value)
|
||||
return;
|
||||
|
||||
if (value < border_min_size || value > border_max_size)
|
||||
return;
|
||||
|
||||
borderSize = value;
|
||||
|
||||
InvalidateTexture();
|
||||
}
|
||||
}
|
||||
|
||||
private float calculatedBorderPortion => BorderSize * border_portion;
|
||||
|
||||
protected override Color4 ColourAt(float position)
|
||||
{
|
||||
if (position <= border_portion)
|
||||
if (calculatedBorderPortion != 0f && position <= calculatedBorderPortion)
|
||||
return BorderColour;
|
||||
|
||||
position -= border_portion;
|
||||
position -= calculatedBorderPortion;
|
||||
return new Color4(AccentColour.R, AccentColour.G, AccentColour.B, (opacity_at_edge - (opacity_at_edge - opacity_at_centre) * position / gradient_portion) * AccentColour.A);
|
||||
}
|
||||
}
|
||||
|
@ -328,7 +328,7 @@ namespace osu.Game.Tests.Visual.Background
|
||||
public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR);
|
||||
}
|
||||
|
||||
private class TestPlayer : Player
|
||||
private class TestPlayer : Visual.TestPlayer
|
||||
{
|
||||
protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value);
|
||||
|
||||
|
@ -24,7 +24,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddUntilStep("key counter counted keys", () => ((ScoreAccessiblePlayer)Player).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 0));
|
||||
}
|
||||
|
||||
private class ScoreAccessiblePlayer : Player
|
||||
private class ScoreAccessiblePlayer : TestPlayer
|
||||
{
|
||||
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
|
||||
public new HUDOverlay HUDOverlay => base.HUDOverlay;
|
||||
|
@ -73,6 +73,26 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
checkFrameCount(2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestInitialSeekWithGameplayStart()
|
||||
{
|
||||
seekManualTo(1000);
|
||||
createStabilityContainer(30000);
|
||||
|
||||
confirmSeek(1000);
|
||||
checkFrameCount(0);
|
||||
|
||||
seekManualTo(10000);
|
||||
confirmSeek(10000);
|
||||
|
||||
checkFrameCount(1);
|
||||
|
||||
seekManualTo(130000);
|
||||
confirmSeek(130000);
|
||||
|
||||
checkFrameCount(6002);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestInitialSeek()
|
||||
{
|
||||
@ -83,7 +103,11 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
checkFrameCount(0);
|
||||
}
|
||||
|
||||
private void createStabilityContainer() => AddStep("create container", () => mainContainer.Child = new FrameStabilityContainer().WithChild(consumer = new ClockConsumingChild()));
|
||||
private const int max_frames_catchup = 50;
|
||||
|
||||
private void createStabilityContainer(double gameplayStartTime = double.MinValue) => AddStep("create container", () =>
|
||||
mainContainer.Child = new FrameStabilityContainer(gameplayStartTime) { MaxCatchUpFrames = max_frames_catchup }
|
||||
.WithChild(consumer = new ClockConsumingChild()));
|
||||
|
||||
private void seekManualTo(double time) => AddStep($"seek manual clock to {time}", () => manualClock.CurrentTime = time);
|
||||
|
||||
|
@ -6,6 +6,7 @@ using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Rulesets;
|
||||
@ -31,6 +32,14 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
base.Content.Add(content = new MenuCursorContainer { RelativeSizeAxes = Axes.Both });
|
||||
}
|
||||
|
||||
[SetUpSteps]
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
base.SetUpSteps();
|
||||
AddStep("resume player", () => Player.GameplayClockContainer.Start());
|
||||
confirmClockRunning(true);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPauseResume()
|
||||
{
|
||||
@ -186,7 +195,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
protected override Player CreatePlayer(Ruleset ruleset) => new PausePlayer();
|
||||
|
||||
protected class PausePlayer : Player
|
||||
protected class PausePlayer : TestPlayer
|
||||
{
|
||||
public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer;
|
||||
|
||||
@ -198,10 +207,10 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
public bool PauseOverlayVisible => PauseOverlay.State == Visibility.Visible;
|
||||
|
||||
protected override void LoadComplete()
|
||||
public override void OnEntering(IScreen last)
|
||||
{
|
||||
base.LoadComplete();
|
||||
HUDOverlay.HoldToQuit.PauseOnFocusLost = false;
|
||||
base.OnEntering(last);
|
||||
GameplayClockContainer.Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
@ -34,20 +35,20 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Test]
|
||||
public void TestLoadContinuation()
|
||||
{
|
||||
AddStep("load dummy beatmap", () => stack.Push(loader = new PlayerLoader(() => new Player(false, false))));
|
||||
Player player = null;
|
||||
SlowLoadPlayer slowPlayer = null;
|
||||
|
||||
AddStep("load dummy beatmap", () => stack.Push(loader = new PlayerLoader(() => player = new TestPlayer(false, false))));
|
||||
AddUntilStep("wait for current", () => loader.IsCurrentScreen());
|
||||
AddStep("mouse in centre", () => InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre));
|
||||
AddUntilStep("wait for no longer current", () => !loader.IsCurrentScreen());
|
||||
AddUntilStep("wait for player to be current", () => player.IsCurrentScreen());
|
||||
AddStep("load slow dummy beatmap", () =>
|
||||
{
|
||||
SlowLoadPlayer slow = null;
|
||||
|
||||
stack.Push(loader = new PlayerLoader(() => slow = new SlowLoadPlayer(false, false)));
|
||||
|
||||
Scheduler.AddDelayed(() => slow.Ready = true, 5000);
|
||||
stack.Push(loader = new PlayerLoader(() => slowPlayer = new SlowLoadPlayer(false, false)));
|
||||
Scheduler.AddDelayed(() => slowPlayer.AllowLoad.Set(), 5000);
|
||||
});
|
||||
|
||||
AddUntilStep("wait for no longer current", () => !loader.IsCurrentScreen());
|
||||
AddUntilStep("wait for player to be current", () => slowPlayer.IsCurrentScreen());
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -101,19 +102,19 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank;
|
||||
}
|
||||
|
||||
private class TestPlayer : Player
|
||||
private class TestPlayer : Visual.TestPlayer
|
||||
{
|
||||
public new Bindable<IReadOnlyList<Mod>> Mods => base.Mods;
|
||||
|
||||
public TestPlayer()
|
||||
: base(false, false)
|
||||
public TestPlayer(bool allowPause = true, bool showResults = true)
|
||||
: base(allowPause, showResults)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
protected class SlowLoadPlayer : Player
|
||||
protected class SlowLoadPlayer : Visual.TestPlayer
|
||||
{
|
||||
public bool Ready;
|
||||
public readonly ManualResetEventSlim AllowLoad = new ManualResetEventSlim(false);
|
||||
|
||||
public SlowLoadPlayer(bool allowPause = true, bool showResults = true)
|
||||
: base(allowPause, showResults)
|
||||
@ -123,8 +124,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
while (!Ready)
|
||||
Thread.Sleep(1);
|
||||
if (!AllowLoad.Wait(TimeSpan.FromSeconds(10)))
|
||||
throw new TimeoutException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
|
||||
public new HUDOverlay HUDOverlay => base.HUDOverlay;
|
||||
|
||||
protected override bool PauseOnFocusLost => false;
|
||||
|
||||
public ScoreAccessibleReplayPlayer(Score score)
|
||||
: base(score)
|
||||
{
|
||||
|
@ -93,7 +93,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
AddUntilStep("remove all channels", () =>
|
||||
{
|
||||
var first = channelTabControl.Items.First();
|
||||
if (first.Name == "+")
|
||||
if (first is ChannelSelectorTabItem.ChannelSelectorTabChannel)
|
||||
return true;
|
||||
|
||||
channelTabControl.RemoveChannel(first);
|
||||
|
@ -1,16 +0,0 @@
|
||||
// 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 osu.Game.Graphics.Sprites;
|
||||
|
||||
namespace osu.Game.Tests.Visual
|
||||
{
|
||||
public class TestCaseCharLookup : OsuTestCase
|
||||
{
|
||||
public TestCaseCharLookup()
|
||||
{
|
||||
AddStep("null", () => { });
|
||||
AddStep("display acharacter", () => Add(new OsuSpriteText { Text = "振込申請" }));
|
||||
}
|
||||
}
|
||||
}
|
@ -65,7 +65,6 @@ namespace osu.Game.Beatmaps
|
||||
protected override IQueryable<BeatmapSetInfo> AddIncludesForDeletion(IQueryable<BeatmapSetInfo> query) =>
|
||||
base.AddIncludesForDeletion(query)
|
||||
.Include(s => s.Metadata)
|
||||
.Include(s => s.Beatmaps).ThenInclude(b => b.Scores)
|
||||
.Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty)
|
||||
.Include(s => s.Beatmaps).ThenInclude(b => b.Metadata);
|
||||
|
||||
|
@ -29,5 +29,12 @@ namespace osu.Game.Beatmaps.Timing
|
||||
/// Whether the break has any effect. Breaks that are too short are culled before they are added to the beatmap.
|
||||
/// </summary>
|
||||
public bool HasEffect => Duration >= MIN_BREAK_DURATION;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this break contains a specified time.
|
||||
/// </summary>
|
||||
/// <param name="time">The time to check in milliseconds.</param>
|
||||
/// <returns>Whether the time falls within this <see cref="BreakPeriod"/>.</returns>
|
||||
public bool Contains(double time) => time >= StartTime && time <= EndTime;
|
||||
}
|
||||
}
|
||||
|
@ -385,7 +385,7 @@ namespace osu.Game.Database
|
||||
/// Delete multiple items.
|
||||
/// This will post notifications tracking progress.
|
||||
/// </summary>
|
||||
public void Delete(List<TModel> items)
|
||||
public void Delete(List<TModel> items, bool silent = false)
|
||||
{
|
||||
if (items.Count == 0) return;
|
||||
|
||||
@ -396,7 +396,8 @@ namespace osu.Game.Database
|
||||
State = ProgressNotificationState.Active,
|
||||
};
|
||||
|
||||
PostNotification?.Invoke(notification);
|
||||
if (!silent)
|
||||
PostNotification?.Invoke(notification);
|
||||
|
||||
int i = 0;
|
||||
|
||||
@ -423,7 +424,7 @@ namespace osu.Game.Database
|
||||
/// Restore multiple items that were previously deleted.
|
||||
/// This will post notifications tracking progress.
|
||||
/// </summary>
|
||||
public void Undelete(List<TModel> items)
|
||||
public void Undelete(List<TModel> items, bool silent = false)
|
||||
{
|
||||
if (!items.Any()) return;
|
||||
|
||||
@ -434,7 +435,8 @@ namespace osu.Game.Database
|
||||
State = ProgressNotificationState.Active,
|
||||
};
|
||||
|
||||
PostNotification?.Invoke(notification);
|
||||
if (!silent)
|
||||
PostNotification?.Invoke(notification);
|
||||
|
||||
int i = 0;
|
||||
|
||||
|
@ -10,6 +10,7 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Overlays.Chat.Tabs;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Online.Chat
|
||||
@ -84,7 +85,11 @@ namespace osu.Game.Online.Chat
|
||||
?? new Channel(user);
|
||||
}
|
||||
|
||||
private void currentChannelChanged(ValueChangedEvent<Channel> e) => JoinChannel(e.NewValue);
|
||||
private void currentChannelChanged(ValueChangedEvent<Channel> e)
|
||||
{
|
||||
if (!(e.NewValue is ChannelSelectorTabItem.ChannelSelectorTabChannel))
|
||||
JoinChannel(e.NewValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensure we run post actions in sequence, once at a time.
|
||||
|
@ -155,8 +155,23 @@ namespace osu.Game
|
||||
|
||||
dependencies.Cache(RulesetStore = new RulesetStore(contextFactory));
|
||||
dependencies.Cache(FileStore = new FileStore(contextFactory, Host.Storage));
|
||||
|
||||
// ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup()
|
||||
dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Host.Storage, contextFactory, Host));
|
||||
dependencies.Cache(BeatmapManager = new BeatmapManager(Host.Storage, contextFactory, RulesetStore, API, Audio, Host, defaultBeatmap));
|
||||
dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, BeatmapManager, Host.Storage, contextFactory, Host));
|
||||
|
||||
// this should likely be moved to ArchiveModelManager when another case appers where it is necessary
|
||||
// to have inter-dependent model managers. this could be obtained with an IHasForeign<T> interface to
|
||||
// allow lookups to be done on the child (ScoreManager in this case) to perform the cascading delete.
|
||||
List<ScoreInfo> getBeatmapScores(BeatmapSetInfo set)
|
||||
{
|
||||
var beatmapIds = BeatmapManager.QueryBeatmaps(b => b.BeatmapSetInfoID == set.ID).Select(b => b.ID).ToList();
|
||||
return ScoreManager.QueryScores(s => beatmapIds.Contains(s.Beatmap.ID)).ToList();
|
||||
}
|
||||
|
||||
BeatmapManager.ItemRemoved += i => ScoreManager.Delete(getBeatmapScores(i), true);
|
||||
BeatmapManager.ItemAdded += (i, existing) => ScoreManager.Undelete(getBeatmapScores(i), true);
|
||||
|
||||
dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore));
|
||||
dependencies.Cache(SettingsStore = new SettingsStore(contextFactory));
|
||||
dependencies.Cache(RulesetConfigCache = new RulesetConfigCache(SettingsStore));
|
||||
|
@ -13,8 +13,8 @@ namespace osu.Game.Overlays.Chat.Tabs
|
||||
|
||||
public override bool IsSwitchable => false;
|
||||
|
||||
public ChannelSelectorTabItem(Channel value)
|
||||
: base(value)
|
||||
public ChannelSelectorTabItem()
|
||||
: base(new ChannelSelectorTabChannel())
|
||||
{
|
||||
Depth = float.MaxValue;
|
||||
Width = 45;
|
||||
@ -31,5 +31,13 @@ namespace osu.Game.Overlays.Chat.Tabs
|
||||
BackgroundInactive = colour.Gray2;
|
||||
BackgroundActive = colour.Gray3;
|
||||
}
|
||||
|
||||
public class ChannelSelectorTabChannel : Channel
|
||||
{
|
||||
public ChannelSelectorTabChannel()
|
||||
{
|
||||
Name = "+";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ namespace osu.Game.Overlays.Chat.Tabs
|
||||
Margin = new MarginPadding(10),
|
||||
});
|
||||
|
||||
AddTabItem(selectorTab = new ChannelSelectorTabItem(new Channel { Name = "+" }));
|
||||
AddTabItem(selectorTab = new ChannelSelectorTabItem());
|
||||
|
||||
ChannelSelectorActive.BindTo(selectorTab.Active);
|
||||
}
|
||||
|
@ -199,6 +199,9 @@ namespace osu.Game.Overlays
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.NewValue is ChannelSelectorTabItem.ChannelSelectorTabChannel)
|
||||
return;
|
||||
|
||||
textbox.Current.Disabled = e.NewValue.ReadOnly;
|
||||
|
||||
if (channelTabControl.Current.Value != e.NewValue)
|
||||
@ -268,7 +271,7 @@ namespace osu.Game.Overlays
|
||||
private void selectTab(int index)
|
||||
{
|
||||
var channel = channelTabControl.Items.Skip(index).FirstOrDefault();
|
||||
if (channel != null && channel.Name != "+")
|
||||
if (channel != null && !(channel is ChannelSelectorTabItem.ChannelSelectorTabChannel))
|
||||
channelTabControl.Current.Value = channel;
|
||||
}
|
||||
|
||||
|
@ -87,14 +87,6 @@ namespace osu.Game.Overlays.Profile.Header
|
||||
|
||||
addSpacer(topLinkContainer);
|
||||
|
||||
if (user.PlayStyles?.Length > 0)
|
||||
{
|
||||
topLinkContainer.AddText("Plays with ");
|
||||
topLinkContainer.AddText(string.Join(", ", user.PlayStyles.Select(style => style.GetDescription())), embolden);
|
||||
|
||||
addSpacer(topLinkContainer);
|
||||
}
|
||||
|
||||
if (user.LastVisit.HasValue)
|
||||
{
|
||||
topLinkContainer.AddText("Last seen ");
|
||||
@ -103,6 +95,14 @@ namespace osu.Game.Overlays.Profile.Header
|
||||
addSpacer(topLinkContainer);
|
||||
}
|
||||
|
||||
if (user.PlayStyles?.Length > 0)
|
||||
{
|
||||
topLinkContainer.AddText("Plays with ");
|
||||
topLinkContainer.AddText(string.Join(", ", user.PlayStyles.Select(style => style.GetDescription())), embolden);
|
||||
|
||||
addSpacer(topLinkContainer);
|
||||
}
|
||||
|
||||
topLinkContainer.AddText("Contributed ");
|
||||
topLinkContainer.AddLink($@"{user.PostCount:#,##0} forum posts", $"https://osu.ppy.sh/users/{user.Id}/posts", creationParameters: embolden);
|
||||
|
||||
|
@ -4,21 +4,20 @@
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Overlays.Volume
|
||||
{
|
||||
public class MuteButton : Container, IHasCurrentValue<bool>
|
||||
public class MuteButton : OsuButton, IHasCurrentValue<bool>
|
||||
{
|
||||
private readonly Bindable<bool> current = new Bindable<bool>();
|
||||
|
||||
@ -36,63 +35,57 @@ namespace osu.Game.Overlays.Volume
|
||||
}
|
||||
|
||||
private Color4 hoveredColour, unhoveredColour;
|
||||
|
||||
private const float width = 100;
|
||||
public const float HEIGHT = 35;
|
||||
|
||||
public MuteButton()
|
||||
{
|
||||
Masking = true;
|
||||
BorderThickness = 3;
|
||||
CornerRadius = HEIGHT / 2;
|
||||
Content.BorderThickness = 3;
|
||||
Content.CornerRadius = HEIGHT / 2;
|
||||
|
||||
Size = new Vector2(width, HEIGHT);
|
||||
|
||||
Action = () => Current.Value = !Current.Value;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
hoveredColour = colours.YellowDark;
|
||||
BorderColour = unhoveredColour = colours.Gray1.Opacity(0.9f);
|
||||
|
||||
Content.BorderColour = unhoveredColour = colours.Gray1;
|
||||
BackgroundColour = colours.Gray1;
|
||||
|
||||
SpriteIcon icon;
|
||||
|
||||
AddRange(new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colours.Gray1,
|
||||
Alpha = 0.9f,
|
||||
},
|
||||
icon = new SpriteIcon
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(20),
|
||||
}
|
||||
});
|
||||
|
||||
Current.ValueChanged += muted =>
|
||||
{
|
||||
icon.Icon = muted.NewValue ? FontAwesome.Solid.VolumeOff : FontAwesome.Solid.VolumeUp;
|
||||
icon.Margin = new MarginPadding { Left = muted.NewValue ? width / 2 - 15 : width / 2 - 10 }; //Magic numbers to line up both icons because they're different widths
|
||||
icon.Icon = muted.NewValue ? FontAwesome.Solid.VolumeMute : FontAwesome.Solid.VolumeUp;
|
||||
};
|
||||
|
||||
Current.TriggerChange();
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
this.TransformTo<MuteButton, SRGBColour>("BorderColour", hoveredColour, 500, Easing.OutQuint);
|
||||
Content.TransformTo<Container<Drawable>, SRGBColour>("BorderColour", hoveredColour, 500, Easing.OutQuint);
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
this.TransformTo<MuteButton, SRGBColour>("BorderColour", unhoveredColour, 500, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
Current.Value = !Current.Value;
|
||||
return true;
|
||||
Content.TransformTo<Container<Drawable>, SRGBColour>("BorderColour", unhoveredColour, 500, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.TypeExtensions;
|
||||
using osu.Framework.MathUtils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@ -99,7 +100,7 @@ namespace osu.Game.Rulesets.Scoring
|
||||
/// <summary>
|
||||
/// The default conditions for failing.
|
||||
/// </summary>
|
||||
protected virtual bool DefaultFailCondition => Health.Value == Health.MinValue;
|
||||
protected virtual bool DefaultFailCondition => Precision.AlmostBigger(Health.MinValue, Health.Value);
|
||||
|
||||
protected ScoreProcessor()
|
||||
{
|
||||
|
@ -12,6 +12,7 @@ using osu.Game.Rulesets.Objects.Drawables;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Input;
|
||||
@ -59,6 +60,8 @@ namespace osu.Game.Rulesets.UI
|
||||
/// </summary>
|
||||
public Container Overlays { get; private set; }
|
||||
|
||||
public override GameplayClock FrameStableClock => frameStabilityContainer.GameplayClock;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when a <see cref="JudgementResult"/> has been applied by a <see cref="DrawableHitObject"/>.
|
||||
/// </summary>
|
||||
@ -140,11 +143,11 @@ namespace osu.Game.Rulesets.UI
|
||||
public virtual PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new PlayfieldAdjustmentContainer();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuConfigManager config)
|
||||
private void load(OsuConfigManager config, CancellationToken? cancellationToken)
|
||||
{
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
frameStabilityContainer = new FrameStabilityContainer
|
||||
frameStabilityContainer = new FrameStabilityContainer(GameplayStartTime)
|
||||
{
|
||||
Child = KeyBindingInputManager
|
||||
.WithChild(CreatePlayfieldAdjustmentContainer()
|
||||
@ -163,16 +166,21 @@ namespace osu.Game.Rulesets.UI
|
||||
|
||||
applyRulesetMods(mods, config);
|
||||
|
||||
loadObjects();
|
||||
loadObjects(cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates and adds drawable representations of hit objects to the play field.
|
||||
/// </summary>
|
||||
private void loadObjects()
|
||||
private void loadObjects(CancellationToken? cancellationToken)
|
||||
{
|
||||
foreach (TObject h in Beatmap.HitObjects)
|
||||
{
|
||||
cancellationToken?.ThrowIfCancellationRequested();
|
||||
addHitObject(h);
|
||||
}
|
||||
|
||||
cancellationToken?.ThrowIfCancellationRequested();
|
||||
|
||||
Playfield.PostProcess();
|
||||
|
||||
@ -334,6 +342,11 @@ namespace osu.Game.Rulesets.UI
|
||||
/// </summary>
|
||||
public readonly BindableBool IsPaused = new BindableBool();
|
||||
|
||||
/// <summary>
|
||||
/// The frame-stable clock which is being used for playfield display.
|
||||
/// </summary>
|
||||
public abstract GameplayClock FrameStableClock { get; }
|
||||
|
||||
/// <summary>~
|
||||
/// The associated ruleset.
|
||||
/// </summary>
|
||||
|
@ -17,19 +17,29 @@ namespace osu.Game.Rulesets.UI
|
||||
/// </summary>
|
||||
public class FrameStabilityContainer : Container, IHasReplayHandler
|
||||
{
|
||||
public FrameStabilityContainer()
|
||||
private readonly double gameplayStartTime;
|
||||
|
||||
/// <summary>
|
||||
/// The number of frames (per parent frame) which can be run in an attempt to catch-up to real-time.
|
||||
/// </summary>
|
||||
public int MaxCatchUpFrames { get; set; } = 5;
|
||||
|
||||
[Cached]
|
||||
public GameplayClock GameplayClock { get; }
|
||||
|
||||
public FrameStabilityContainer(double gameplayStartTime = double.MinValue)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
gameplayClock = new GameplayClock(framedClock = new FramedClock(manualClock = new ManualClock()));
|
||||
|
||||
GameplayClock = new GameplayClock(framedClock = new FramedClock(manualClock = new ManualClock()));
|
||||
|
||||
this.gameplayStartTime = gameplayStartTime;
|
||||
}
|
||||
|
||||
private readonly ManualClock manualClock;
|
||||
|
||||
private readonly FramedClock framedClock;
|
||||
|
||||
[Cached]
|
||||
private GameplayClock gameplayClock;
|
||||
|
||||
private IFrameBasedClock parentGameplayClock;
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
@ -38,7 +48,7 @@ namespace osu.Game.Rulesets.UI
|
||||
if (clock != null)
|
||||
{
|
||||
parentGameplayClock = clock;
|
||||
gameplayClock.IsPaused.BindTo(clock.IsPaused);
|
||||
GameplayClock.IsPaused.BindTo(clock.IsPaused);
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,8 +74,6 @@ namespace osu.Game.Rulesets.UI
|
||||
|
||||
private bool isAttached => ReplayInputHandler != null;
|
||||
|
||||
private const int max_catch_up_updates_per_frame = 50;
|
||||
|
||||
private const double sixty_frame_time = 1000.0 / 60;
|
||||
|
||||
private bool firstConsumption = true;
|
||||
@ -73,11 +81,11 @@ namespace osu.Game.Rulesets.UI
|
||||
public override bool UpdateSubTree()
|
||||
{
|
||||
requireMoreUpdateLoops = true;
|
||||
validState = !gameplayClock.IsPaused.Value;
|
||||
validState = !GameplayClock.IsPaused.Value;
|
||||
|
||||
int loops = 0;
|
||||
|
||||
while (validState && requireMoreUpdateLoops && loops++ < max_catch_up_updates_per_frame)
|
||||
while (validState && requireMoreUpdateLoops && loops++ < MaxCatchUpFrames)
|
||||
{
|
||||
updateClock();
|
||||
|
||||
@ -116,6 +124,8 @@ namespace osu.Game.Rulesets.UI
|
||||
|
||||
firstConsumption = false;
|
||||
}
|
||||
else if (manualClock.CurrentTime < gameplayStartTime)
|
||||
manualClock.CurrentTime = newProposedTime = Math.Min(gameplayStartTime, newProposedTime);
|
||||
else if (Math.Abs(manualClock.CurrentTime - newProposedTime) > sixty_frame_time * 1.2f)
|
||||
{
|
||||
newProposedTime = newProposedTime > manualClock.CurrentTime
|
||||
@ -160,7 +170,7 @@ namespace osu.Game.Rulesets.UI
|
||||
if (parentGameplayClock == null)
|
||||
parentGameplayClock = Clock;
|
||||
|
||||
Clock = gameplayClock;
|
||||
Clock = GameplayClock;
|
||||
ProcessCustomClock = false;
|
||||
}
|
||||
|
||||
|
@ -25,9 +25,9 @@ namespace osu.Game.Scoring
|
||||
protected override string ImportFromStablePath => "Replays";
|
||||
|
||||
private readonly RulesetStore rulesets;
|
||||
private readonly BeatmapManager beatmaps;
|
||||
private readonly Func<BeatmapManager> beatmaps;
|
||||
|
||||
public ScoreManager(RulesetStore rulesets, BeatmapManager beatmaps, Storage storage, IDatabaseContextFactory contextFactory, IIpcHost importHost = null)
|
||||
public ScoreManager(RulesetStore rulesets, Func<BeatmapManager> beatmaps, Storage storage, IDatabaseContextFactory contextFactory, IIpcHost importHost = null)
|
||||
: base(storage, contextFactory, new ScoreStore(contextFactory, storage), importHost)
|
||||
{
|
||||
this.rulesets = rulesets;
|
||||
@ -43,7 +43,7 @@ namespace osu.Game.Scoring
|
||||
{
|
||||
try
|
||||
{
|
||||
return new DatabasedLegacyScoreParser(rulesets, beatmaps).Parse(stream).ScoreInfo;
|
||||
return new DatabasedLegacyScoreParser(rulesets, beatmaps()).Parse(stream).ScoreInfo;
|
||||
}
|
||||
catch (LegacyScoreParser.BeatmapNotFoundException e)
|
||||
{
|
||||
@ -53,7 +53,7 @@ namespace osu.Game.Scoring
|
||||
}
|
||||
}
|
||||
|
||||
public Score GetScore(ScoreInfo score) => new LegacyDatabasedScore(score, rulesets, beatmaps, Files.Store);
|
||||
public Score GetScore(ScoreInfo score) => new LegacyDatabasedScore(score, rulesets, beatmaps(), Files.Store);
|
||||
|
||||
public List<ScoreInfo> GetAllUsableScores() => ModelStore.ConsumableItems.Where(s => !s.DeletePending).ToList();
|
||||
|
||||
|
@ -25,6 +25,8 @@ namespace osu.Game.Screens.Play.HUD
|
||||
{
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
|
||||
|
||||
public readonly Bindable<bool> IsPaused = new Bindable<bool>();
|
||||
|
||||
private readonly Button button;
|
||||
|
||||
public Action Action
|
||||
@ -51,7 +53,8 @@ namespace osu.Game.Screens.Play.HUD
|
||||
button = new Button
|
||||
{
|
||||
HoverGained = () => text.FadeIn(500, Easing.OutQuint),
|
||||
HoverLost = () => text.FadeOut(500, Easing.OutQuint)
|
||||
HoverLost = () => text.FadeOut(500, Easing.OutQuint),
|
||||
IsPaused = { BindTarget = IsPaused }
|
||||
}
|
||||
};
|
||||
AutoSizeAxes = Axes.Both;
|
||||
@ -94,6 +97,8 @@ namespace osu.Game.Screens.Play.HUD
|
||||
private CircularProgress circularProgress;
|
||||
private Circle overlayCircle;
|
||||
|
||||
public readonly Bindable<bool> IsPaused = new Bindable<bool>();
|
||||
|
||||
protected override bool AllowMultipleFires => true;
|
||||
|
||||
public Action HoverGained;
|
||||
@ -217,7 +222,7 @@ namespace osu.Game.Screens.Play.HUD
|
||||
|
||||
private void updateActive()
|
||||
{
|
||||
if (!pauseOnFocusLost) return;
|
||||
if (!pauseOnFocusLost || IsPaused.Value) return;
|
||||
|
||||
if (gameActive.Value)
|
||||
AbortConfirm();
|
||||
|
@ -35,6 +35,10 @@ namespace osu.Game.Screens.Play
|
||||
public readonly HoldForMenuButton HoldToQuit;
|
||||
public readonly PlayerSettingsOverlay PlayerSettingsOverlay;
|
||||
|
||||
private readonly ScoreProcessor scoreProcessor;
|
||||
private readonly DrawableRuleset drawableRuleset;
|
||||
private readonly IReadOnlyList<Mod> mods;
|
||||
|
||||
private Bindable<bool> showHud;
|
||||
private readonly Container visibilityContainer;
|
||||
private readonly BindableBool replayLoaded = new BindableBool();
|
||||
@ -45,6 +49,10 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
public HUDOverlay(ScoreProcessor scoreProcessor, DrawableRuleset drawableRuleset, IReadOnlyList<Mod> mods)
|
||||
{
|
||||
this.scoreProcessor = scoreProcessor;
|
||||
this.drawableRuleset = drawableRuleset;
|
||||
this.mods = mods;
|
||||
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
Children = new Drawable[]
|
||||
@ -89,20 +97,21 @@ namespace osu.Game.Screens.Play
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(OsuConfigManager config, NotificationOverlay notificationOverlay)
|
||||
{
|
||||
BindProcessor(scoreProcessor);
|
||||
BindDrawableRuleset(drawableRuleset);
|
||||
|
||||
Progress.Objects = drawableRuleset.Objects;
|
||||
Progress.AllowSeeking = drawableRuleset.HasReplayLoaded.Value;
|
||||
Progress.RequestSeek = time => RequestSeek(time);
|
||||
Progress.ReferenceClock = drawableRuleset.FrameStableClock;
|
||||
|
||||
ModDisplay.Current.Value = mods;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(OsuConfigManager config, NotificationOverlay notificationOverlay)
|
||||
{
|
||||
showHud = config.GetBindable<bool>(OsuSetting.ShowInterface);
|
||||
showHud.ValueChanged += visible => visibilityContainer.FadeTo(visible.NewValue ? 1 : 0, duration);
|
||||
showHud.TriggerChange();
|
||||
@ -122,14 +131,12 @@ namespace osu.Game.Screens.Play
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
replayLoaded.ValueChanged += replayLoadedValueChanged;
|
||||
replayLoaded.TriggerChange();
|
||||
replayLoaded.BindValueChanged(replayLoadedValueChanged, true);
|
||||
}
|
||||
|
||||
private void replayLoadedValueChanged(ValueChangedEvent<bool> e)
|
||||
{
|
||||
PlayerSettingsOverlay.ReplayLoaded = e.NewValue;
|
||||
HoldToQuit.PauseOnFocusLost = !e.NewValue;
|
||||
|
||||
if (e.NewValue)
|
||||
{
|
||||
|
@ -40,6 +40,11 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
public override OverlayActivation InitialOverlayActivationMode => OverlayActivation.UserTriggered;
|
||||
|
||||
/// <summary>
|
||||
/// Whether gameplay should pause when the game window focus is lost.
|
||||
/// </summary>
|
||||
protected virtual bool PauseOnFocusLost => true;
|
||||
|
||||
public Action RestartRequested;
|
||||
|
||||
public bool HasFailed { get; private set; }
|
||||
@ -132,7 +137,11 @@ namespace osu.Game.Screens.Play
|
||||
DrawableRuleset.Cursor?.CreateProxy() ?? new Container(),
|
||||
HUDOverlay = new HUDOverlay(ScoreProcessor, DrawableRuleset, Mods.Value)
|
||||
{
|
||||
HoldToQuit = { Action = performUserRequestedExit },
|
||||
HoldToQuit =
|
||||
{
|
||||
Action = performUserRequestedExit,
|
||||
IsPaused = { BindTarget = GameplayClockContainer.IsPaused }
|
||||
},
|
||||
PlayerSettingsOverlay = { PlaybackSettings = { UserPlaybackRate = { BindTarget = GameplayClockContainer.UserPlaybackRate } } },
|
||||
KeyCounter = { Visible = { BindTarget = DrawableRuleset.HasReplayLoaded } },
|
||||
RequestSeek = GameplayClockContainer.Seek,
|
||||
@ -167,6 +176,8 @@ namespace osu.Game.Screens.Play
|
||||
}
|
||||
};
|
||||
|
||||
DrawableRuleset.HasReplayLoaded.BindValueChanged(e => HUDOverlay.HoldToQuit.PauseOnFocusLost = !e.NewValue && PauseOnFocusLost, true);
|
||||
|
||||
// bind clock into components that require it
|
||||
DrawableRuleset.IsPaused.BindTo(GameplayClockContainer.IsPaused);
|
||||
|
||||
@ -403,8 +414,9 @@ namespace osu.Game.Screens.Play
|
||||
IsResuming = true;
|
||||
PauseOverlay.Hide();
|
||||
|
||||
// time-based conditions may allow instant resume.
|
||||
if (GameplayClockContainer.GameplayClock.CurrentTime < Beatmap.Value.Beatmap.HitObjects.First().StartTime)
|
||||
// breaks and time-based conditions may allow instant resume.
|
||||
double time = GameplayClockContainer.GameplayClock.CurrentTime;
|
||||
if (Beatmap.Value.Beatmap.Breaks.Any(b => b.Contains(time)) || time < Beatmap.Value.Beatmap.HitObjects.First().StartTime)
|
||||
completeResume();
|
||||
else
|
||||
DrawableRuleset.RequestResume(completeResume);
|
||||
|
@ -10,6 +10,7 @@ using osu.Game.Graphics;
|
||||
using osu.Framework.Allocation;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.UI;
|
||||
@ -55,7 +56,9 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
private readonly BindableBool replayLoaded = new BindableBool();
|
||||
|
||||
private GameplayClock gameplayClock;
|
||||
public IClock ReferenceClock;
|
||||
|
||||
private IClock gameplayClock;
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(OsuColour colours, GameplayClock clock)
|
||||
@ -154,10 +157,12 @@ namespace osu.Game.Screens.Play
|
||||
if (objects == null)
|
||||
return;
|
||||
|
||||
double position = gameplayClock?.CurrentTime ?? Time.Current;
|
||||
double progress = Math.Min(1, (position - firstHitTime) / (lastHitTime - firstHitTime));
|
||||
double gameplayTime = gameplayClock?.CurrentTime ?? Time.Current;
|
||||
double frameStableTime = ReferenceClock?.CurrentTime ?? gameplayTime;
|
||||
|
||||
bar.CurrentTime = position;
|
||||
double progress = Math.Min(1, (frameStableTime - firstHitTime) / (lastHitTime - firstHitTime));
|
||||
|
||||
bar.CurrentTime = gameplayTime;
|
||||
graph.Progress = (int)(graph.ColumnCount * progress);
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.MathUtils;
|
||||
|
||||
namespace osu.Game.Screens.Play
|
||||
{
|
||||
@ -107,9 +108,17 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
protected override void UpdateValue(float value)
|
||||
{
|
||||
var xFill = value * UsableWidth;
|
||||
fill.Width = xFill;
|
||||
handleBase.X = xFill;
|
||||
// handled in update
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
float newX = (float)Interpolation.Lerp(handleBase.X, NormalizedValue * UsableWidth, MathHelper.Clamp(Time.Elapsed / 40, 0, 1));
|
||||
|
||||
fill.Width = newX;
|
||||
handleBase.X = newX;
|
||||
}
|
||||
|
||||
protected override void OnUserChange(double value) => OnSeek?.Invoke(value);
|
||||
|
@ -39,6 +39,7 @@ namespace osu.Game.Screens.Select
|
||||
public abstract class SongSelect : OsuScreen
|
||||
{
|
||||
private static readonly Vector2 wedged_container_size = new Vector2(0.5f, 245);
|
||||
|
||||
protected const float BACKGROUND_BLUR = 20;
|
||||
private const float left_area_padding = 20;
|
||||
|
||||
@ -89,8 +90,6 @@ namespace osu.Game.Screens.Select
|
||||
|
||||
protected SongSelect()
|
||||
{
|
||||
const float carousel_width = 640;
|
||||
|
||||
AddRangeInternal(new Drawable[]
|
||||
{
|
||||
new ParallaxContainer
|
||||
@ -103,7 +102,8 @@ namespace osu.Game.Screens.Select
|
||||
new WedgeBackground
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Right = carousel_width * 0.76f },
|
||||
Padding = new MarginPadding { Right = -150 },
|
||||
Size = new Vector2(wedged_container_size.X, 1),
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -144,8 +144,8 @@ namespace osu.Game.Screens.Select
|
||||
Carousel = new BeatmapCarousel
|
||||
{
|
||||
Masking = false,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Size = new Vector2(carousel_width, 1),
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Size = new Vector2(1 - wedged_container_size.X, 1),
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
SelectionChanged = updateSelectedBeatmap,
|
||||
@ -505,6 +505,8 @@ namespace osu.Game.Screens.Select
|
||||
{
|
||||
ModSelect.Hide();
|
||||
|
||||
BeatmapOptions.Hide();
|
||||
|
||||
this.ScaleTo(1.1f, 250, Easing.InSine);
|
||||
|
||||
this.FadeOut(250);
|
||||
|
@ -34,6 +34,10 @@ namespace osu.Game.Skinning
|
||||
case @"CursorExpand":
|
||||
skin.CursorExpand = pair.Value != "0";
|
||||
break;
|
||||
|
||||
case @"SliderBorderSize":
|
||||
skin.SliderBorderSize = Parsing.ParseFloat(pair.Value);
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
|
@ -25,6 +25,8 @@ namespace osu.Game.Skinning
|
||||
|
||||
public int HitCircleOverlap { get; set; }
|
||||
|
||||
public float? SliderBorderSize { get; set; }
|
||||
|
||||
public bool? CursorExpand { get; set; } = true;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
// 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.Threading;
|
||||
using osuTK;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
@ -57,7 +58,7 @@ namespace osu.Game.Storyboards.Drawables
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(FileStore fileStore, GameplayClock clock)
|
||||
private void load(FileStore fileStore, GameplayClock clock, CancellationToken? cancellationToken)
|
||||
{
|
||||
if (clock != null)
|
||||
Clock = clock;
|
||||
@ -65,7 +66,11 @@ namespace osu.Game.Storyboards.Drawables
|
||||
dependencies.Cache(new TextureStore(new TextureLoaderStore(fileStore.Store), false, scaleAdjust: 1));
|
||||
|
||||
foreach (var layer in Storyboard.Layers)
|
||||
{
|
||||
cancellationToken?.ThrowIfCancellationRequested();
|
||||
|
||||
Add(layer.CreateDrawable());
|
||||
}
|
||||
}
|
||||
|
||||
private void updateLayerVisibility()
|
||||
|
@ -1,6 +1,7 @@
|
||||
// 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.Threading;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@ -24,10 +25,12 @@ namespace osu.Game.Storyboards.Drawables
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
private void load(CancellationToken? cancellationToken)
|
||||
{
|
||||
foreach (var element in Layer.Elements)
|
||||
{
|
||||
cancellationToken?.ThrowIfCancellationRequested();
|
||||
|
||||
if (element.IsDrawable)
|
||||
AddInternal(element.CreateDrawable());
|
||||
}
|
||||
|
@ -5,11 +5,10 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Storyboards.Drawables;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System;
|
||||
|
||||
namespace osu.Game.Storyboards
|
||||
{
|
||||
public class Storyboard : IDisposable
|
||||
public class Storyboard
|
||||
{
|
||||
private readonly Dictionary<string, StoryboardLayer> layers = new Dictionary<string, StoryboardLayer>();
|
||||
public IEnumerable<StoryboardLayer> Layers => layers.Values;
|
||||
@ -56,30 +55,5 @@ namespace osu.Game.Storyboards
|
||||
drawable.Width = drawable.Height * (BeatmapInfo.WidescreenStoryboard ? 16 / 9f : 4 / 3f);
|
||||
return drawable;
|
||||
}
|
||||
|
||||
#region Disposal
|
||||
|
||||
~Storyboard()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private bool isDisposed;
|
||||
|
||||
protected virtual void Dispose(bool isDisposing)
|
||||
{
|
||||
if (isDisposed)
|
||||
return;
|
||||
|
||||
isDisposed = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
@ -76,6 +76,6 @@ namespace osu.Game.Tests.Visual
|
||||
return Player;
|
||||
}
|
||||
|
||||
protected virtual Player CreatePlayer(Ruleset ruleset) => new Player(false, false);
|
||||
protected virtual Player CreatePlayer(Ruleset ruleset) => new TestPlayer(false, false);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// 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.Linq;
|
||||
@ -33,7 +33,7 @@ namespace osu.Game.Tests.Visual
|
||||
}
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
public virtual void SetUpSteps()
|
||||
{
|
||||
AddStep(ruleset.RulesetInfo.Name, loadPlayer);
|
||||
AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1);
|
||||
@ -56,6 +56,6 @@ namespace osu.Game.Tests.Visual
|
||||
LoadScreen(Player);
|
||||
}
|
||||
|
||||
protected virtual Player CreatePlayer(Ruleset ruleset) => new Player(false, false);
|
||||
protected virtual Player CreatePlayer(Ruleset ruleset) => new TestPlayer(false, false);
|
||||
}
|
||||
}
|
||||
|
17
osu.Game/Tests/Visual/TestPlayer.cs
Normal file
17
osu.Game/Tests/Visual/TestPlayer.cs
Normal file
@ -0,0 +1,17 @@
|
||||
// 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 osu.Game.Screens.Play;
|
||||
|
||||
namespace osu.Game.Tests.Visual
|
||||
{
|
||||
public class TestPlayer : Player
|
||||
{
|
||||
protected override bool PauseOnFocusLost => false;
|
||||
|
||||
public TestPlayer(bool allowPause = true, bool showResults = true)
|
||||
: base(allowPause, showResults)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user