1
0
mirror of https://github.com/ppy/osu.git synced 2026-06-03 15:04:26 +08:00

Compare commits

..

3 Commits

46 changed files with 292 additions and 496 deletions
@@ -1,11 +1,9 @@
// 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 NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Screens.Play;
namespace osu.Game.Rulesets.Catch.Tests
{
@@ -19,30 +17,13 @@ namespace osu.Game.Rulesets.Catch.Tests
protected override IBeatmap CreateBeatmap(Ruleset ruleset)
{
var beatmap = new Beatmap
{
BeatmapInfo =
{
Ruleset = ruleset.RulesetInfo,
BaseDifficulty = new BeatmapDifficulty { CircleSize = 3.6f }
}
};
// Should produce a hperdash
beatmap.HitObjects.Add(new Fruit { StartTime = 816, X = 308 / 512f, NewCombo = true });
beatmap.HitObjects.Add(new Fruit { StartTime = 1008, X = 56 / 512f, });
var beatmap = new Beatmap { BeatmapInfo = { Ruleset = ruleset.RulesetInfo } };
for (int i = 0; i < 512; i++)
if (i % 5 < 3)
beatmap.HitObjects.Add(new Fruit { X = i % 10 < 5 ? 0.02f : 0.98f, StartTime = 2000 + i * 100, NewCombo = i % 8 == 0 });
beatmap.HitObjects.Add(new Fruit { X = i % 10 < 5 ? 0.02f : 0.98f, StartTime = i * 100, NewCombo = i % 8 == 0 });
return beatmap;
}
protected override void AddCheckSteps(Func<Player> player)
{
base.AddCheckSteps(player);
AddAssert("First note is hyperdash", () => Beatmap.Value.Beatmap.HitObjects[0] is Fruit f && f.HyperDash);
}
}
}
@@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
CatchHitObject nextObject = objectWithDroplets[i + 1];
int thisDirection = nextObject.X > currentObject.X ? 1 : -1;
double timeToNext = nextObject.StartTime - currentObject.StartTime - 1000f / 60f / 4; // 1/4th of a frame of grace time, taken from osu-stable
double timeToNext = nextObject.StartTime - currentObject.StartTime;
double distanceToNext = Math.Abs(nextObject.X - currentObject.X) - (lastDirection == thisDirection ? lastExcess : halfCatcherWidth);
float distanceToHyper = (float)(timeToNext * CatcherArea.Catcher.BASE_SPEED - distanceToNext);
if (distanceToHyper < 0)
@@ -25,8 +25,8 @@ namespace osu.Game.Rulesets.Mania.Tests
[BackgroundDependencyLoader]
private void load(RulesetConfigCache configCache)
{
var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance());
config.BindWith(ManiaRulesetSetting.ScrollDirection, direction);
var config = (ManiaConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance());
config.BindWith(ManiaSetting.ScrollDirection, direction);
}
}
}
@@ -8,9 +8,9 @@ using osu.Game.Rulesets.Mania.UI;
namespace osu.Game.Rulesets.Mania.Configuration
{
public class ManiaRulesetConfigManager : RulesetConfigManager<ManiaRulesetSetting>
public class ManiaConfigManager : RulesetConfigManager<ManiaSetting>
{
public ManiaRulesetConfigManager(SettingsStore settings, RulesetInfo ruleset, int? variant = null)
public ManiaConfigManager(SettingsStore settings, RulesetInfo ruleset, int? variant = null)
: base(settings, ruleset, variant)
{
}
@@ -19,17 +19,17 @@ namespace osu.Game.Rulesets.Mania.Configuration
{
base.InitialiseDefaults();
Set(ManiaRulesetSetting.ScrollTime, 2250.0, 50.0, 10000.0, 50.0);
Set(ManiaRulesetSetting.ScrollDirection, ManiaScrollingDirection.Down);
Set(ManiaSetting.ScrollTime, 2250.0, 50.0, 10000.0, 50.0);
Set(ManiaSetting.ScrollDirection, ManiaScrollingDirection.Down);
}
public override TrackedSettings CreateTrackedSettings() => new TrackedSettings
{
new TrackedSetting<double>(ManiaRulesetSetting.ScrollTime, v => new SettingDescription(v, "Scroll Time", $"{v}ms"))
new TrackedSetting<double>(ManiaSetting.ScrollTime, v => new SettingDescription(v, "Scroll Time", $"{v}ms"))
};
}
public enum ManiaRulesetSetting
public enum ManiaSetting
{
ScrollTime,
ScrollDirection
+1 -1
View File
@@ -162,7 +162,7 @@ namespace osu.Game.Rulesets.Mania
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new ManiaReplayFrame();
public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new ManiaRulesetConfigManager(settings, RulesetInfo);
public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new ManiaConfigManager(settings, RulesetInfo);
public override RulesetSettingsSubsection CreateSettings() => new ManiaSettingsSubsection(this);
@@ -22,19 +22,19 @@ namespace osu.Game.Rulesets.Mania
[BackgroundDependencyLoader]
private void load()
{
var config = (ManiaRulesetConfigManager)Config;
var config = (ManiaConfigManager)Config;
Children = new Drawable[]
{
new SettingsEnumDropdown<ManiaScrollingDirection>
{
LabelText = "Scrolling direction",
Bindable = config.GetBindable<ManiaScrollingDirection>(ManiaRulesetSetting.ScrollDirection)
Bindable = config.GetBindable<ManiaScrollingDirection>(ManiaSetting.ScrollDirection)
},
new SettingsSlider<double, TimeSlider>
{
LabelText = "Scroll speed",
Bindable = config.GetBindable<double>(ManiaRulesetSetting.ScrollTime)
Bindable = config.GetBindable<double>(ManiaSetting.ScrollTime)
},
};
}
@@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Mania.UI
public IEnumerable<BarLine> BarLines;
protected new ManiaRulesetConfigManager Config => (ManiaRulesetConfigManager)base.Config;
protected new ManiaConfigManager Config => (ManiaConfigManager)base.Config;
private readonly Bindable<ManiaScrollingDirection> configDirection = new Bindable<ManiaScrollingDirection>();
@@ -74,10 +74,10 @@ namespace osu.Game.Rulesets.Mania.UI
{
BarLines.ForEach(Playfield.Add);
Config.BindWith(ManiaRulesetSetting.ScrollDirection, configDirection);
Config.BindWith(ManiaSetting.ScrollDirection, configDirection);
configDirection.BindValueChanged(direction => Direction.Value = (ScrollingDirection)direction.NewValue, true);
Config.BindWith(ManiaRulesetSetting.ScrollTime, TimeRange);
Config.BindWith(ManiaSetting.ScrollTime, TimeRange);
}
/// <summary>
@@ -1,30 +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.Configuration;
using osu.Game.Rulesets.Configuration;
namespace osu.Game.Rulesets.Osu.Configuration
{
public class OsuRulesetConfigManager : RulesetConfigManager<OsuRulesetSetting>
{
public OsuRulesetConfigManager(SettingsStore settings, RulesetInfo ruleset, int? variant = null)
: base(settings, ruleset, variant)
{
}
protected override void InitialiseDefaults()
{
base.InitialiseDefaults();
Set(OsuRulesetSetting.SnakingInSliders, true);
Set(OsuRulesetSetting.SnakingOutSliders, true);
}
}
public enum OsuRulesetSetting
{
SnakingInSliders,
SnakingOutSliders
}
}
@@ -10,8 +10,8 @@ using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers;
using osu.Game.Configuration;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Configuration;
using osu.Game.Rulesets.Scoring;
using osuTK.Graphics;
using osu.Game.Skinning;
@@ -33,9 +33,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private readonly IBindable<float> scaleBindable = new Bindable<float>();
private readonly IBindable<SliderPath> pathBindable = new Bindable<SliderPath>();
[Resolved(CanBeNull = true)]
private OsuRulesetConfigManager config { get; set; }
public DrawableSlider(Slider s)
: base(s)
{
@@ -97,10 +94,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
}
[BackgroundDependencyLoader]
private void load()
private void load(OsuConfigManager config)
{
config?.BindWith(OsuRulesetSetting.SnakingInSliders, Body.SnakingIn);
config?.BindWith(OsuRulesetSetting.SnakingOutSliders, Body.SnakingOut);
config.BindWith(OsuSetting.SnakingInSliders, Body.SnakingIn);
config.BindWith(OsuSetting.SnakingOutSliders, Body.SnakingOut);
positionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
scaleBindable.BindValueChanged(scale =>
+1 -6
View File
@@ -16,11 +16,8 @@ using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Replays;
using osu.Game.Rulesets.Replays.Types;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Configuration;
using osu.Game.Rulesets.Configuration;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Configuration;
using osu.Game.Rulesets.Osu.Difficulty;
using osu.Game.Scoring;
@@ -147,14 +144,12 @@ namespace osu.Game.Rulesets.Osu
public override string ShortName => "osu";
public override RulesetSettingsSubsection CreateSettings() => new OsuSettingsSubsection(this);
public override RulesetSettingsSubsection CreateSettings() => new OsuSettings(this);
public override int? LegacyID => 0;
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new OsuReplayFrame();
public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new OsuRulesetConfigManager(settings, RulesetInfo);
public OsuRuleset(RulesetInfo rulesetInfo = null)
: base(rulesetInfo)
{
@@ -8,7 +8,6 @@ using osu.Game.Beatmaps;
using osu.Game.Input.Handlers;
using osu.Game.Replays;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Configuration;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.Replays;
@@ -21,8 +20,6 @@ namespace osu.Game.Rulesets.Osu.UI
{
public class OsuRulesetContainer : RulesetContainer<OsuPlayfield, OsuHitObject>
{
protected new OsuRulesetConfigManager Config => (OsuRulesetConfigManager)base.Config;
public OsuRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap)
: base(ruleset, beatmap)
{
@@ -3,36 +3,34 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Configuration;
using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Osu.Configuration;
namespace osu.Game.Rulesets.Osu.UI
{
public class OsuSettingsSubsection : RulesetSettingsSubsection
public class OsuSettings : RulesetSettingsSubsection
{
protected override string Header => "osu!";
public OsuSettingsSubsection(Ruleset ruleset)
public OsuSettings(Ruleset ruleset)
: base(ruleset)
{
}
[BackgroundDependencyLoader]
private void load()
private void load(OsuConfigManager config)
{
var config = (OsuRulesetConfigManager)Config;
Children = new Drawable[]
{
new SettingsCheckbox
{
LabelText = "Snaking in sliders",
Bindable = config.GetBindable<bool>(OsuRulesetSetting.SnakingInSliders)
Bindable = config.GetBindable<bool>(OsuSetting.SnakingInSliders)
},
new SettingsCheckbox
{
LabelText = "Snaking out sliders",
Bindable = config.GetBindable<bool>(OsuRulesetSetting.SnakingOutSliders)
Bindable = config.GetBindable<bool>(OsuSetting.SnakingOutSliders)
},
};
}
@@ -333,7 +333,6 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual("hit_2.wav", getTestableSampleInfo(hitObjects[1]).LookupNames.First());
Assert.AreEqual("normal-hitnormal2", getTestableSampleInfo(hitObjects[2]).LookupNames.First());
Assert.AreEqual("hit_1.wav", getTestableSampleInfo(hitObjects[3]).LookupNames.First());
Assert.AreEqual(70, getTestableSampleInfo(hitObjects[3]).Volume);
}
SampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]);
@@ -101,7 +101,7 @@ namespace osu.Game.Tests.Beatmaps.IO
int fireCount = 0;
// ReSharper disable once AccessToModifiedClosure
manager.ItemAdded += (_, __) => fireCount++;
manager.ItemAdded += (_, __, ___) => fireCount++;
manager.ItemRemoved += _ => fireCount++;
var imported = LoadOszIntoOsu(osu);
@@ -13,4 +13,4 @@ SampleSet: Normal
255,193,2170,1,0,0:0:0:0:hit_1.wav
256,191,2638,5,0,0:0:0:0:hit_2.wav
255,193,3107,1,0,0:0:0:0:
256,191,3576,1,0,0:0:0:70:hit_1.wav
256,191,3576,1,0,0:0:0:0:hit_1.wav
@@ -188,10 +188,10 @@ namespace osu.Game.Tests.Visual
public void PauseTest()
{
performFullSetup(true);
AddStep("Pause", () => player.CurrentPausableGameplayContainer.Pause());
AddStep("Pause", () => player.CurrentPauseContainer.Pause());
waitForDim();
AddAssert("Screen is dimmed", () => songSelect.IsBackgroundDimmed());
AddStep("Unpause", () => player.CurrentPausableGameplayContainer.Resume());
AddStep("Unpause", () => player.CurrentPauseContainer.Resume());
waitForDim();
AddAssert("Screen is dimmed", () => songSelect.IsBackgroundDimmed());
}
@@ -328,7 +328,7 @@ namespace osu.Game.Tests.Visual
};
}
public PausableGameplayContainer CurrentPausableGameplayContainer => PausableGameplayContainer;
public PauseContainer CurrentPauseContainer => PauseContainer;
public UserDimContainer CurrentStoryboardContainer => StoryboardContainer;
@@ -1,47 +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 System;
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Overlays.Direct;
using osu.Game.Rulesets.Osu;
using osu.Game.Tests.Beatmaps;
using osuTK;
namespace osu.Game.Tests.Visual
{
public class TestCaseDirectPanel : OsuTestCase
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(DirectGridPanel),
typeof(DirectListPanel),
typeof(IconPill)
};
[BackgroundDependencyLoader]
private void load()
{
var beatmap = new TestWorkingBeatmap(new OsuRuleset().RulesetInfo, null);
beatmap.BeatmapSetInfo.OnlineInfo.HasVideo = true;
beatmap.BeatmapSetInfo.OnlineInfo.HasStoryboard = true;
Child = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Padding = new MarginPadding(20),
Spacing = new Vector2(0, 20),
Children = new Drawable[]
{
new DirectGridPanel(beatmap.BeatmapSetInfo),
new DirectListPanel(beatmap.BeatmapSetInfo)
}
};
}
}
}
@@ -17,15 +17,15 @@ namespace osu.Game.Tests.Visual
[Description("player pause/fail screens")]
public class TestCaseGameplayMenuOverlay : ManualInputManagerTestCase
{
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(FailOverlay), typeof(PausableGameplayContainer) };
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(FailOverlay), typeof(PauseContainer) };
private FailOverlay failOverlay;
private PausableGameplayContainer.PauseOverlay pauseOverlay;
private PauseContainer.PauseOverlay pauseOverlay;
[BackgroundDependencyLoader]
private void load()
{
Add(pauseOverlay = new PausableGameplayContainer.PauseOverlay
Add(pauseOverlay = new PauseContainer.PauseOverlay
{
OnResume = () => Logger.Log(@"Resume"),
OnRetry = () => Logger.Log(@"Retry"),
+25 -2
View File
@@ -11,6 +11,7 @@ using Microsoft.EntityFrameworkCore;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Extensions;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics.Textures;
using osu.Framework.Logging;
using osu.Framework.Platform;
@@ -49,6 +50,11 @@ namespace osu.Game.Beatmaps
/// </summary>
public event Action<DownloadBeatmapSetRequest> BeatmapDownloadFailed;
/// <summary>
/// Fired when a beatmap load is requested (into the interactive game UI).
/// </summary>
public Action<BeatmapSetInfo> PresentBeatmap;
/// <summary>
/// A default representation of a WorkingBeatmap to use when no beatmap is available.
/// </summary>
@@ -145,7 +151,8 @@ namespace osu.Game.Beatmaps
var downloadNotification = new DownloadNotification
{
Text = $"Downloading {beatmapSetInfo}",
CompletionText = $"Imported {beatmapSetInfo.Metadata.Artist} - {beatmapSetInfo.Metadata.Title}!",
Text = $"Downloading {beatmapSetInfo.Metadata.Artist} - {beatmapSetInfo.Metadata.Title}",
};
var request = new DownloadBeatmapSetRequest(beatmapSetInfo, noVideo);
@@ -158,10 +165,20 @@ namespace osu.Game.Beatmaps
request.Success += filename =>
{
downloadNotification.Text = $"Importing {beatmapSetInfo.Metadata.Artist} - {beatmapSetInfo.Metadata.Title}";
Task.Factory.StartNew(() =>
{
// This gets scheduled back to the update thread, but we want the import to run in the background.
Import(downloadNotification, filename);
var importedBeatmap = Import(filename);
downloadNotification.CompletionClickAction = () =>
{
PresentCompletedImport(importedBeatmap.Yield());
return true;
};
downloadNotification.State = ProgressNotificationState.Completed;
currentDownloads.Remove(request);
}, TaskCreationOptions.LongRunning);
};
@@ -204,6 +221,12 @@ namespace osu.Game.Beatmaps
return true;
}
protected override void PresentCompletedImport(IEnumerable<BeatmapSetInfo> imported)
{
base.PresentCompletedImport(imported);
PresentBeatmap?.Invoke(imported.LastOrDefault());
}
/// <summary>
/// Get an existing download request if it exists.
/// </summary>
@@ -72,6 +72,9 @@ namespace osu.Game.Configuration
Set(OsuSetting.MenuParallax, true);
Set(OsuSetting.SnakingInSliders, true);
Set(OsuSetting.SnakingOutSliders, true);
// Gameplay
Set(OsuSetting.DimLevel, 0.3, 0, 1, 0.01);
Set(OsuSetting.BlurLevel, 0, 0, 1, 0.01);
@@ -147,6 +150,8 @@ namespace osu.Game.Configuration
DisplayStarsMinimum,
DisplayStarsMaximum,
RandomSelectAlgorithm,
SnakingInSliders,
SnakingOutSliders,
ShowFpsDisplay,
ChatDisplayHeight,
Version,
+23 -45
View File
@@ -33,7 +33,7 @@ namespace osu.Game.Database
where TModel : class, IHasFiles<TFileModel>, IHasPrimaryKey, ISoftDelete
where TFileModel : INamedFileInfo, new()
{
public delegate void ItemAddedDelegate(TModel model, bool existing);
public delegate void ItemAddedDelegate(TModel model, bool existing, bool silent);
/// <summary>
/// Set an endpoint for notifications to be posted to.
@@ -113,7 +113,7 @@ namespace osu.Game.Database
ContextFactory = contextFactory;
ModelStore = modelStore;
ModelStore.ItemAdded += item => handleEvent(() => ItemAdded?.Invoke(item, false));
ModelStore.ItemAdded += (item, silent) => handleEvent(() => ItemAdded?.Invoke(item, false, silent));
ModelStore.ItemRemoved += s => handleEvent(() => ItemRemoved?.Invoke(s));
Files = new FileStore(contextFactory, storage);
@@ -131,18 +131,14 @@ namespace osu.Game.Database
/// <param name="paths">One or more archive locations on disk.</param>
public void Import(params string[] paths)
{
var notification = new ProgressNotification { State = ProgressNotificationState.Active };
var notification = new ProgressNotification
{
Text = "Import is initialising...",
Progress = 0,
State = ProgressNotificationState.Active,
};
PostNotification?.Invoke(notification);
Import(notification, paths);
}
protected void Import(ProgressNotification notification, params string[] paths)
{
notification.Progress = 0;
notification.Text = "Import is initialising...";
var term = $"{typeof(TModel).Name.Replace("Info", "").ToLower()}";
List<TModel> imported = new List<TModel>();
@@ -155,18 +151,7 @@ namespace osu.Game.Database
try
{
var text = "Importing ";
if (path.Length > 1)
text += $"{++current} of {paths.Length} {term}s..";
else
text += $"{term}..";
// only show the filename if it isn't a temporary one (as those look ugly).
if (!path.Contains(Path.GetTempPath()))
text += $"\n{Path.GetFileName(path)}";
notification.Text = text;
notification.Text = $"Importing ({++current} of {paths.Length})\n{Path.GetFileName(path)}";
imported.Add(Import(path));
@@ -186,20 +171,13 @@ namespace osu.Game.Database
}
else
{
notification.CompletionText = imported.Count == 1
? $"Imported {imported.First()}!"
: $"Imported {current} {term}s!";
if (imported.Count > 0 && PresentImport != null)
notification.CompletionText = $"Imported {current} {typeof(TModel).Name.Replace("Info", "").ToLower()}s!";
notification.CompletionClickAction += () =>
{
notification.CompletionText += " Click to view.";
notification.CompletionClickAction = () =>
{
PresentImport?.Invoke(imported);
return true;
};
}
if (imported.Count > 0)
PresentCompletedImport(imported);
return true;
};
notification.State = ProgressNotificationState.Completed;
}
}
@@ -232,10 +210,9 @@ namespace osu.Game.Database
return import;
}
/// <summary>
/// Fired when the user requests to view the resulting import.
/// </summary>
public Action<IEnumerable<TModel>> PresentImport;
protected virtual void PresentCompletedImport(IEnumerable<TModel> imported)
{
}
/// <summary>
/// Import an item from an <see cref="ArchiveReader"/>.
@@ -251,7 +228,7 @@ namespace osu.Game.Database
model.Hash = computeHash(archive);
return Import(model, archive);
return Import(model, false, archive);
}
catch (Exception e)
{
@@ -285,8 +262,9 @@ namespace osu.Game.Database
/// Import an item from a <see cref="TModel"/>.
/// </summary>
/// <param name="item">The model to be imported.</param>
/// <param name="silent">Whether the user should be notified fo the import.</param>
/// <param name="archive">An optional archive to use for model population.</param>
public TModel Import(TModel item, ArchiveReader archive = null)
public TModel Import(TModel item, bool silent = false, ArchiveReader archive = null)
{
delayEvents();
@@ -306,7 +284,7 @@ namespace osu.Game.Database
{
Undelete(existing);
Logger.Log($"Found existing {typeof(TModel)} for {item} (ID {existing.ID}). Skipping import.", LoggingTarget.Database);
handleEvent(() => ItemAdded?.Invoke(existing, true));
handleEvent(() => ItemAdded?.Invoke(existing, true, silent));
return existing;
}
@@ -316,7 +294,7 @@ namespace osu.Game.Database
Populate(item, archive);
// import to store
ModelStore.Add(item);
ModelStore.Add(item, silent);
}
catch (Exception e)
{
@@ -16,7 +16,9 @@ namespace osu.Game.Database
public abstract class MutableDatabaseBackedStore<T> : DatabaseBackedStore
where T : class, IHasPrimaryKey, ISoftDelete
{
public event Action<T> ItemAdded;
public delegate void ItemAddedDelegate(T model, bool silent);
public event ItemAddedDelegate ItemAdded;
public event Action<T> ItemRemoved;
protected MutableDatabaseBackedStore(IDatabaseContextFactory contextFactory, Storage storage = null)
@@ -33,7 +35,8 @@ namespace osu.Game.Database
/// Add a <see cref="T"/> to the database.
/// </summary>
/// <param name="item">The item to add.</param>
public void Add(T item)
/// <param name="silent">Whether the user should be notified of the addition.</param>
public void Add(T item, bool silent)
{
using (var usage = ContextFactory.GetForWrite())
{
@@ -41,7 +44,7 @@ namespace osu.Game.Database
context.Attach(item);
}
ItemAdded?.Invoke(item);
ItemAdded?.Invoke(item, silent);
}
/// <summary>
@@ -54,7 +57,7 @@ namespace osu.Game.Database
usage.Context.Update(item);
ItemRemoved?.Invoke(item);
ItemAdded?.Invoke(item);
ItemAdded?.Invoke(item, true);
}
/// <summary>
@@ -91,7 +94,7 @@ namespace osu.Game.Database
item.DeletePending = false;
}
ItemAdded?.Invoke(item);
ItemAdded?.Invoke(item, true);
return true;
}
@@ -59,6 +59,7 @@ namespace osu.Game.Graphics.UserInterface
public abstract void Increment(T amount);
public float TextSize
{
get => DisplayedCountSpriteText.Font.Size;
@@ -36,12 +36,11 @@ namespace osu.Game.Online.API.Requests
[Description("Ranked & Approved")]
RankedApproved = 0,
Qualified = 3,
Approved = 1,
Loved = 8,
Favourites = 2,
[Description("Pending & WIP")]
PendingWIP = 4,
Qualified = 3,
Pending = 4,
Graveyard = 5,
[Description("My Maps")]
+6 -11
View File
@@ -213,6 +213,12 @@ namespace osu.Game.Online.Leaderboards
pendingUpdateScores?.Cancel();
pendingUpdateScores = Schedule(() =>
{
if (api?.IsLoggedIn != true)
{
PlaceholderState = PlaceholderState.NotLoggedIn;
return;
}
PlaceholderState = PlaceholderState.Retrieving;
loading.Show();
@@ -225,12 +231,6 @@ namespace osu.Game.Online.Leaderboards
if (getScoresRequest == null)
return;
if (api?.IsLoggedIn != true)
{
PlaceholderState = PlaceholderState.NotLoggedIn;
return;
}
getScoresRequest.Failure += e => Schedule(() =>
{
if (e is OperationCanceledException)
@@ -243,11 +243,6 @@ namespace osu.Game.Online.Leaderboards
});
}
/// <summary>
/// Performs a fetch/refresh of scores to be displayed.
/// </summary>
/// <param name="scoresCallback">A callback which should be called when fetching is completed. Scheduling is not required.</param>
/// <returns>An <see cref="APIRequest"/> responsible for the fetch operation. This will be queued and performed automatically.</returns>
protected abstract APIRequest FetchScores(Action<IEnumerable<ScoreInfo>> scoresCallback);
private Placeholder currentPlaceholder;
+79 -81
View File
@@ -167,6 +167,8 @@ namespace osu.Game
{
this.frameworkConfig = frameworkConfig;
ScoreManager.ItemAdded += (score, _, silent) => Schedule(() => LoadScore(score, silent));
if (!Host.IsPrimaryInstance)
{
Logger.Log(@"osu! does not support multiple running instances.", LoggingTarget.Runtime, LogLevel.Error);
@@ -215,12 +217,63 @@ namespace osu.Game
externalLinkOpener.OpenUrlExternally(url);
}
private ScheduledDelegate scoreLoad;
/// <summary>
/// Show a beatmap set as an overlay.
/// </summary>
/// <param name="setId">The set to display.</param>
public void ShowBeatmapSet(int setId) => beatmapSetOverlay.FetchAndShowBeatmapSet(setId);
/// <summary>
/// Present a beatmap at song select.
/// </summary>
/// <param name="beatmap">The beatmap to select.</param>
public void PresentBeatmap(BeatmapSetInfo beatmap)
{
if (menuScreen == null)
{
Schedule(() => PresentBeatmap(beatmap));
return;
}
CloseAllOverlays(false);
void setBeatmap()
{
if (Beatmap.Disabled)
{
Schedule(setBeatmap);
return;
}
var databasedSet = beatmap.OnlineBeatmapSetID != null ? BeatmapManager.QueryBeatmapSet(s => s.OnlineBeatmapSetID == beatmap.OnlineBeatmapSetID) : BeatmapManager.QueryBeatmapSet(s => s.Hash == beatmap.Hash);
if (databasedSet != null)
{
// Use first beatmap available for current ruleset, else switch ruleset.
var first = databasedSet.Beatmaps.Find(b => b.Ruleset == ruleset.Value) ?? databasedSet.Beatmaps.First();
ruleset.Value = first.Ruleset;
Beatmap.Value = BeatmapManager.GetWorkingBeatmap(first);
}
}
switch (screenStack.CurrentScreen)
{
case SongSelect _:
break;
default:
// navigate to song select if we are not already there.
menuScreen.MakeCurrent();
menuScreen.LoadToSolo();
break;
}
setBeatmap();
}
/// <summary>
/// Show a user's profile as an overlay.
/// </summary>
@@ -233,44 +286,19 @@ namespace osu.Game
/// <param name="beatmapId">The beatmap to show.</param>
public void ShowBeatmap(int beatmapId) => beatmapSetOverlay.FetchAndShowBeatmap(beatmapId);
/// <summary>
/// Present a beatmap at song select immediately.
/// The user should have already requested this interactively.
/// </summary>
/// <param name="beatmap">The beatmap to select.</param>
public void PresentBeatmap(BeatmapSetInfo beatmap)
protected void LoadScore(ScoreInfo score, bool silent)
{
var databasedSet = beatmap.OnlineBeatmapSetID != null
? BeatmapManager.QueryBeatmapSet(s => s.OnlineBeatmapSetID == beatmap.OnlineBeatmapSetID)
: BeatmapManager.QueryBeatmapSet(s => s.Hash == beatmap.Hash);
if (silent)
return;
if (databasedSet == null)
scoreLoad?.Cancel();
if (menuScreen == null)
{
Logger.Log("The requested beatmap could not be loaded.", LoggingTarget.Information);
scoreLoad = Schedule(() => LoadScore(score, false));
return;
}
performFromMainMenu(() =>
{
// we might already be at song select, so a check is required before performing the load to solo.
if (menuScreen.IsCurrentScreen())
menuScreen.LoadToSolo();
// Use first beatmap available for current ruleset, else switch ruleset.
var first = databasedSet.Beatmaps.Find(b => b.Ruleset == ruleset.Value) ?? databasedSet.Beatmaps.First();
ruleset.Value = first.Ruleset;
Beatmap.Value = BeatmapManager.GetWorkingBeatmap(first);
}, $"load {beatmap}", bypassScreenAllowChecks: true, targetScreen: typeof(PlaySongSelect));
}
/// <summary>
/// Present a score's replay immediately.
/// The user should have already requested this interactively.
/// </summary>
/// <param name="beatmap">The beatmap to select.</param>
public void PresentScore(ScoreInfo score)
{
var databasedScore = ScoreManager.GetScore(score);
var databasedScoreInfo = databasedScore.ScoreInfo;
if (databasedScore.Replay == null)
@@ -286,40 +314,14 @@ namespace osu.Game
return;
}
performFromMainMenu(() =>
{
ruleset.Value = databasedScoreInfo.Ruleset;
Beatmap.Value = BeatmapManager.GetWorkingBeatmap(databasedBeatmap);
Beatmap.Value.Mods.Value = databasedScoreInfo.Mods;
menuScreen.Push(new PlayerLoader(() => new ReplayPlayer(databasedScore)));
}, $"watch {databasedScoreInfo}", bypassScreenAllowChecks: true);
}
private ScheduledDelegate performFromMainMenuTask;
/// <summary>
/// Perform an action only after returning to the main menu.
/// Eagerly tries to exit the current screen until it succeeds.
/// </summary>
/// <param name="action">The action to perform once we are in the correct state.</param>
/// <param name="taskName">The task name to display in a notification (if we can't immediately reach the main menu state).</param>
/// <param name="targetScreen">An optional target screen type. If this screen is already current we can immediately perform the action without returning to the menu.</param>
/// <param name="bypassScreenAllowChecks">Whether checking <see cref="IOsuScreen.AllowExternalScreenChange"/> should be bypassed.</param>
private void performFromMainMenu(Action action, string taskName, Type targetScreen = null, bool bypassScreenAllowChecks = false)
{
performFromMainMenuTask?.Cancel();
// if the current screen does not allow screen changing, give the user an option to try again later.
if (!bypassScreenAllowChecks && (screenStack.CurrentScreen as IOsuScreen)?.AllowExternalScreenChange == false)
if ((screenStack.CurrentScreen as IOsuScreen)?.AllowExternalScreenChange == false)
{
notifications.Post(new SimpleNotification
{
Text = $"Click here to {taskName}",
Text = $"Click here to watch {databasedScoreInfo.User.Username} on {databasedScoreInfo.Beatmap}",
Activated = () =>
{
performFromMainMenu(action, taskName, targetScreen, true);
loadScore();
return true;
}
});
@@ -327,26 +329,24 @@ namespace osu.Game
return;
}
CloseAllOverlays(false);
loadScore();
// we may already be at the target screen type.
if (targetScreen != null && screenStack.CurrentScreen?.GetType() == targetScreen)
void loadScore()
{
action();
return;
if (!menuScreen.IsCurrentScreen() || Beatmap.Disabled)
{
menuScreen.MakeCurrent();
this.Delay(500).Schedule(loadScore, out scoreLoad);
return;
}
ruleset.Value = databasedScoreInfo.Ruleset;
Beatmap.Value = BeatmapManager.GetWorkingBeatmap(databasedBeatmap);
Beatmap.Value.Mods.Value = databasedScoreInfo.Mods;
menuScreen.Push(new PlayerLoader(() => new ReplayPlayer(databasedScore)));
}
// all conditions have been met to continue with the action.
if (menuScreen?.IsCurrentScreen() == true && !Beatmap.Disabled)
{
action();
return;
}
// menuScreen may not be initialised yet (null check required).
menuScreen?.MakeCurrent();
performFromMainMenuTask = Schedule(() => performFromMainMenu(action, taskName));
}
protected override void Dispose(bool isDisposing)
@@ -370,10 +370,8 @@ namespace osu.Game
BeatmapManager.PostNotification = n => notifications?.Post(n);
BeatmapManager.GetStableStorage = GetStorageForStableInstall;
BeatmapManager.PresentImport = items => PresentBeatmap(items.First());
ScoreManager.PostNotification = n => notifications?.Post(n);
ScoreManager.PresentImport = items => PresentScore(items.First());
BeatmapManager.PresentBeatmap = PresentBeatmap;
Container logoContainer;
+4 -37
View File
@@ -13,7 +13,6 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
namespace osu.Game.Overlays.Direct
{
@@ -24,7 +23,6 @@ namespace osu.Game.Overlays.Direct
private const float vertical_padding = 5;
private const float height = 70;
private FillFlowContainer statusContainer;
private PlayButton playButton;
private Box progressBar;
@@ -110,24 +108,10 @@ namespace osu.Game.Overlays.Direct
},
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
statusContainer = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Margin = new MarginPadding { Vertical = vertical_padding, Horizontal = 5 },
Spacing = new Vector2(5),
},
new FillFlowContainer
{
AutoSizeAxes = Axes.X,
Height = 20,
Margin = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding },
Children = GetDifficultyIcons(),
},
},
AutoSizeAxes = Axes.X,
Height = 20,
Margin = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding },
Children = GetDifficultyIcons(),
},
},
},
@@ -210,23 +194,6 @@ namespace osu.Game.Overlays.Direct
Colour = colours.Yellow,
},
});
if (SetInfo.OnlineInfo?.HasVideo ?? false)
{
statusContainer.Add(new IconPill(FontAwesome.fa_film) { IconSize = new Vector2(20) });
}
if (SetInfo.OnlineInfo?.HasStoryboard ?? false)
{
statusContainer.Add(new IconPill(FontAwesome.fa_image) { IconSize = new Vector2(20) });
}
statusContainer.Add(new BeatmapSetOnlineStatusPill
{
TextSize = 12,
TextPadding = new MarginPadding { Horizontal = 10, Vertical = 4 },
Status = SetInfo.OnlineInfo?.Status ?? BeatmapSetOnlineStatus.None,
});
}
}
}
@@ -118,7 +118,7 @@ namespace osu.Game.Overlays.Direct
private void onRequestFailure(Exception e) => Schedule(() => attachDownload(null));
private void setAdded(BeatmapSetInfo s, bool existing) => setDownloadStateFromManager(s, DownloadState.LocallyAvailable);
private void setAdded(BeatmapSetInfo s, bool existing, bool silent) => setDownloadStateFromManager(s, DownloadState.LocallyAvailable);
private void setRemoved(BeatmapSetInfo s) => setDownloadStateFromManager(s, DownloadState.NotDownloaded);
+4 -12
View File
@@ -12,14 +12,6 @@ namespace osu.Game.Overlays.Direct
{
public class IconPill : CircularContainer
{
public Vector2 IconSize
{
get => iconContainer.Size;
set => iconContainer.Size = value;
}
private readonly Container iconContainer;
public IconPill(FontAwesome icon)
{
AutoSizeAxes = Axes.Both;
@@ -33,16 +25,16 @@ namespace osu.Game.Overlays.Direct
Colour = Color4.Black,
Alpha = 0.5f,
},
iconContainer = new Container
new Container
{
Size = new Vector2(22),
Padding = new MarginPadding(5),
AutoSizeAxes = Axes.Both,
Margin = new MarginPadding(5),
Child = new SpriteIcon
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Icon = icon,
Size = new Vector2(12),
},
},
};
+2 -2
View File
@@ -75,7 +75,7 @@ namespace osu.Game.Overlays.Music
[BackgroundDependencyLoader]
private void load(BeatmapManager beatmaps, IBindable<WorkingBeatmap> beatmap)
{
beatmaps.GetAllUsableBeatmapSets().ForEach(b => addBeatmapSet(b, false));
beatmaps.GetAllUsableBeatmapSets().ForEach(b => addBeatmapSet(b, false, false));
beatmaps.ItemAdded += addBeatmapSet;
beatmaps.ItemRemoved += removeBeatmapSet;
@@ -83,7 +83,7 @@ namespace osu.Game.Overlays.Music
beatmapBacking.ValueChanged += _ => updateSelectedSet();
}
private void addBeatmapSet(BeatmapSetInfo obj, bool existing) => Schedule(() =>
private void addBeatmapSet(BeatmapSetInfo obj, bool existing, bool silent) => Schedule(() =>
{
if (existing)
return;
+1 -1
View File
@@ -212,7 +212,7 @@ namespace osu.Game.Overlays
beatmapSets.Insert(index, beatmapSetInfo);
}
private void handleBeatmapAdded(BeatmapSetInfo obj, bool existing)
private void handleBeatmapAdded(BeatmapSetInfo obj, bool existing, bool silent)
{
if (existing)
return;
@@ -82,7 +82,7 @@ namespace osu.Game.Overlays.Settings.Sections
private void itemRemoved(SkinInfo s) => Schedule(() => skinDropdown.Items = skinDropdown.Items.Where(i => i.ID != s.ID).ToArray());
private void itemAdded(SkinInfo s, bool existing)
private void itemAdded(SkinInfo s, bool existing, bool silent)
{
if (existing)
return;
@@ -301,16 +301,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
{
// Todo: This should return the normal SampleInfos if the specified sample file isn't found, but that's a pretty edge-case scenario
if (!string.IsNullOrEmpty(bankInfo.Filename))
{
return new List<SampleInfo>
{
new FileSampleInfo
{
Filename = bankInfo.Filename,
Volume = bankInfo.Volume
}
};
}
return new List<SampleInfo> { new FileSampleInfo { Filename = bankInfo.Filename } };
var soundTypes = new List<SampleInfo>
{
+5 -2
View File
@@ -12,6 +12,7 @@ using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Play;
using osuTK;
namespace osu.Game.Rulesets.UI
@@ -59,10 +60,12 @@ namespace osu.Game.Rulesets.UI
private WorkingBeatmap beatmap;
[BackgroundDependencyLoader]
private void load(IBindable<WorkingBeatmap> beatmap)
[BackgroundDependencyLoader(true)]
private void load(IBindable<WorkingBeatmap> beatmap, GameplayClock clock)
{
this.beatmap = beatmap.Value;
if (clock != null) Clock = clock;
}
/// <summary>
+31 -45
View File
@@ -41,7 +41,6 @@ namespace osu.Game.Rulesets.UI
protected RulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
{
InternalChild = KeyBindingContainer = CreateKeyBindingContainer(ruleset, variant, unique);
gameplayClock = new GameplayClock(framedClock = new FramedClock(manualClock = new ManualClock()));
}
#region Action mapping (for replays)
@@ -87,28 +86,22 @@ namespace osu.Game.Rulesets.UI
#region Clock control
private readonly ManualClock manualClock;
private readonly FramedClock framedClock;
[Cached]
private GameplayClock gameplayClock;
private IFrameBasedClock parentGameplayClock;
[BackgroundDependencyLoader(true)]
private void load(OsuConfigManager config, GameplayClock clock)
{
mouseDisabled = config.GetBindable<bool>(OsuSetting.MouseDisableButtons);
if (clock != null)
parentGameplayClock = clock;
}
private ManualClock clock;
private IFrameBasedClock parentClock;
protected override void LoadComplete()
{
base.LoadComplete();
setClock();
//our clock will now be our parent's clock, but we want to replace this to allow manual control.
parentClock = Clock;
ProcessCustomClock = false;
Clock = new FramedClock(clock = new ManualClock
{
CurrentTime = parentClock.CurrentTime,
Rate = parentClock.Rate,
});
}
/// <summary>
@@ -154,28 +147,25 @@ namespace osu.Game.Rulesets.UI
private void updateClock()
{
if (parentGameplayClock == null)
setClock(); // LoadComplete may not be run yet, but we still want the clock.
if (parentClock == null) return;
validState = true;
clock.Rate = parentClock.Rate;
clock.IsRunning = parentClock.IsRunning;
manualClock.Rate = parentGameplayClock.Rate;
manualClock.IsRunning = parentGameplayClock.IsRunning;
var newProposedTime = parentGameplayClock.CurrentTime;
var newProposedTime = parentClock.CurrentTime;
try
{
if (Math.Abs(manualClock.CurrentTime - newProposedTime) > sixty_frame_time * 1.2f)
if (Math.Abs(clock.CurrentTime - newProposedTime) > sixty_frame_time * 1.2f)
{
newProposedTime = manualClock.Rate > 0
? Math.Min(newProposedTime, manualClock.CurrentTime + sixty_frame_time)
: Math.Max(newProposedTime, manualClock.CurrentTime - sixty_frame_time);
newProposedTime = clock.Rate > 0
? Math.Min(newProposedTime, clock.CurrentTime + sixty_frame_time)
: Math.Max(newProposedTime, clock.CurrentTime - sixty_frame_time);
}
if (!isAttached)
{
manualClock.CurrentTime = newProposedTime;
clock.CurrentTime = newProposedTime;
}
else
{
@@ -187,39 +177,35 @@ namespace osu.Game.Rulesets.UI
validState = false;
requireMoreUpdateLoops = true;
manualClock.CurrentTime = newProposedTime;
clock.CurrentTime = newProposedTime;
return;
}
manualClock.CurrentTime = newTime.Value;
clock.CurrentTime = newTime.Value;
}
requireMoreUpdateLoops = manualClock.CurrentTime != parentGameplayClock.CurrentTime;
requireMoreUpdateLoops = clock.CurrentTime != parentClock.CurrentTime;
}
finally
{
// The manual clock time has changed in the above code. The framed clock now needs to be updated
// to ensure that the its time is valid for our children before input is processed
framedClock.ProcessFrame();
Clock.ProcessFrame();
}
}
private void setClock()
{
// in case a parent gameplay clock isn't available, just use the parent clock.
if (parentGameplayClock == null)
parentGameplayClock = Clock;
Clock = gameplayClock;
ProcessCustomClock = false;
}
#endregion
#region Setting application (disables etc.)
private Bindable<bool> mouseDisabled;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
mouseDisabled = config.GetBindable<bool>(OsuSetting.MouseDisableButtons);
}
protected override bool Handle(UIEvent e)
{
switch (e)
-2
View File
@@ -178,7 +178,5 @@ namespace osu.Game.Scoring
{
public string Acronym { get; set; }
}
public override string ToString() => $"{User} playing {Beatmap}";
}
}
+1 -1
View File
@@ -19,7 +19,7 @@ namespace osu.Game.Screens
/// <summary>
/// Whether a top-level component should be allowed to exit the current screen to, for example,
/// complete an import. Note that this can be overridden by a user if they specifically request.
/// complete an import.
/// </summary>
bool AllowExternalScreenChange { get; }
@@ -54,7 +54,7 @@ namespace osu.Game.Screens.Multi.Match.Components
hasBeatmap = beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == beatmap.OnlineBeatmapID) != null;
}
private void beatmapAdded(BeatmapSetInfo model, bool existing)
private void beatmapAdded(BeatmapSetInfo model, bool existing, bool silent)
{
if (Beatmap.Value == null)
return;
@@ -202,7 +202,7 @@ namespace osu.Game.Screens.Multi.Match
/// <summary>
/// Handle the case where a beatmap is imported (and can be used by this match).
/// </summary>
private void beatmapAdded(BeatmapSetInfo model, bool existing) => Schedule(() =>
private void beatmapAdded(BeatmapSetInfo model, bool existing, bool silent) => Schedule(() =>
{
if (Beatmap.Value != beatmapManager.DefaultBeatmap)
return;
-43
View File
@@ -1,43 +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.Framework.Timing;
namespace osu.Game.Screens.Play
{
/// <summary>
/// A clock which is used for gameplay elements that need to follow audio time 1:1.
/// Exposed via DI by <see cref="PausableGameplayContainer"/>.
/// <remarks>
/// The main purpose of this clock is to stop components using it from accidentally processing the main
/// <see cref="IFrameBasedClock"/>, as this should only be done once to ensure accuracy.
/// </remarks>
/// </summary>
public class GameplayClock : IFrameBasedClock
{
private readonly IFrameBasedClock underlyingClock;
public GameplayClock(IFrameBasedClock underlyingClock)
{
this.underlyingClock = underlyingClock;
}
public double CurrentTime => underlyingClock.CurrentTime;
public double Rate => underlyingClock.Rate;
public bool IsRunning => underlyingClock.IsRunning;
public void ProcessFrame()
{
// we do not want to process the underlying clock.
// this is handled by PauseContainer.
}
public double ElapsedFrameTime => underlyingClock.ElapsedFrameTime;
public double FramesPerSecond => underlyingClock.FramesPerSecond;
public FrameTimeInfo TimeInfo => underlyingClock.TimeInfo;
}
}
@@ -14,11 +14,10 @@ using osuTK.Graphics;
namespace osu.Game.Screens.Play
{
/// <summary>
/// A container which handles pausing children, displaying a pause overlay with choices and processing the clock.
/// Exposes a <see cref="GameplayClock"/> to children via DI.
/// A container which handles pausing children, displaying a pause overlay with choices etc.
/// This alleviates a lot of the intricate pause logic from being in <see cref="Player"/>
/// </summary>
public class PausableGameplayContainer : Container
public class PauseContainer : Container
{
public readonly BindableBool IsPaused = new BindableBool();
@@ -54,11 +53,11 @@ namespace osu.Game.Screens.Play
private readonly GameplayClock gameplayClock;
/// <summary>
/// Creates a new <see cref="PausableGameplayContainer"/>.
/// Creates a new <see cref="PauseContainer"/>.
/// </summary>
/// <param name="offsetClock">The gameplay clock. This is the clock that will process frames. Includes user/system offsets.</param>
/// <param name="adjustableClock">The seekable clock. This is the clock that will be paused and resumed. Should not be processed (it is processed automatically by <see cref="offsetClock"/>).</param>
public PausableGameplayContainer(FramedClock offsetClock, DecoupleableInterpolatingFramedClock adjustableClock)
public PauseContainer(FramedClock offsetClock, DecoupleableInterpolatingFramedClock adjustableClock)
{
this.offsetClock = offsetClock;
this.adjustableClock = adjustableClock;
@@ -155,4 +154,40 @@ namespace osu.Game.Screens.Play
}
}
}
/// <summary>
/// A clock which is used for gameplay elements that need to follow audio time 1:1.
/// Exposed via DI by <see cref="PauseContainer"/>.
/// <remarks>
/// THe main purpose of this clock is to stop components using it from accidentally processing the main
/// <see cref="IFrameBasedClock"/>, as this should only be done once to ensure accuracy.
/// </remarks>
/// </summary>
public class GameplayClock : IFrameBasedClock
{
private readonly IFrameBasedClock underlyingClock;
public GameplayClock(IFrameBasedClock underlyingClock)
{
this.underlyingClock = underlyingClock;
}
public double CurrentTime => underlyingClock.CurrentTime;
public double Rate => underlyingClock.Rate;
public bool IsRunning => underlyingClock.IsRunning;
public void ProcessFrame()
{
// we do not want to process the underlying clock.
// this is handled by PauseContainer.
}
public double ElapsedFrameTime => underlyingClock.ElapsedFrameTime;
public double FramesPerSecond => underlyingClock.FramesPerSecond;
public FrameTimeInfo TimeInfo => underlyingClock.TimeInfo;
}
}
+10 -10
View File
@@ -72,7 +72,7 @@ namespace osu.Game.Screens.Play
[Resolved]
private ScoreManager scoreManager { get; set; }
protected PausableGameplayContainer PausableGameplayContainer { get; private set; }
protected PauseContainer PauseContainer { get; private set; }
private RulesetInfo ruleset;
@@ -175,7 +175,7 @@ namespace osu.Game.Screens.Play
InternalChildren = new Drawable[]
{
PausableGameplayContainer = new PausableGameplayContainer(offsetClock, adjustableClock)
PauseContainer = new PauseContainer(offsetClock, adjustableClock)
{
Retries = RestartCount,
OnRetry = restart,
@@ -233,7 +233,7 @@ namespace osu.Game.Screens.Play
HUDOverlay.HoldToQuit.Action = performUserRequestedExit;
HUDOverlay.KeyCounter.Visible.BindTo(RulesetContainer.HasReplayLoaded);
RulesetContainer.IsPaused.BindTo(PausableGameplayContainer.IsPaused);
RulesetContainer.IsPaused.BindTo(PauseContainer.IsPaused);
if (ShowStoryboard.Value)
initializeStoryboard(false);
@@ -292,7 +292,7 @@ namespace osu.Game.Screens.Play
var score = CreateScore();
if (RulesetContainer.ReplayScore == null)
scoreManager.Import(score);
scoreManager.Import(score, true);
this.Push(CreateResults(score));
@@ -366,7 +366,7 @@ namespace osu.Game.Screens.Play
this.Delay(750).Schedule(() =>
{
if (!PausableGameplayContainer.IsPaused.Value)
if (!PauseContainer.IsPaused.Value)
{
adjustableClock.Start();
}
@@ -374,8 +374,8 @@ namespace osu.Game.Screens.Play
});
});
PausableGameplayContainer.Alpha = 0;
PausableGameplayContainer.FadeIn(750, Easing.OutQuint);
PauseContainer.Alpha = 0;
PauseContainer.FadeIn(750, Easing.OutQuint);
}
public override void OnSuspending(IScreen next)
@@ -393,7 +393,7 @@ namespace osu.Game.Screens.Play
return true;
}
if ((!AllowPause || HasFailed || !ValidForResume || PausableGameplayContainer?.IsPaused.Value != false || RulesetContainer?.HasReplayLoaded.Value != false) && (!PausableGameplayContainer?.IsResuming ?? true))
if ((!AllowPause || HasFailed || !ValidForResume || PauseContainer?.IsPaused.Value != false || RulesetContainer?.HasReplayLoaded.Value != false) && (!PauseContainer?.IsResuming ?? true))
{
// In the case of replays, we may have changed the playback rate.
applyRateFromMods();
@@ -402,7 +402,7 @@ namespace osu.Game.Screens.Play
}
if (LoadedBeatmapSuccessfully)
PausableGameplayContainer?.Pause();
PauseContainer?.Pause();
return true;
}
@@ -416,7 +416,7 @@ namespace osu.Game.Screens.Play
storyboardReplacesBackground.Value = false;
}
protected override bool OnScroll(ScrollEvent e) => mouseWheelDisabled.Value && !PausableGameplayContainer.IsPaused.Value;
protected override bool OnScroll(ScrollEvent e) => mouseWheelDisabled.Value && !PauseContainer.IsPaused.Value;
private void initializeStoryboard(bool asyncLoad)
{
+1 -11
View File
@@ -17,7 +17,6 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Screens.Menu;
using osu.Game.Screens.Play.HUD;
using osu.Game.Screens.Play.PlayerSettings;
using osuTK;
using osuTK.Graphics;
@@ -297,7 +296,6 @@ namespace osu.Game.Screens.Play
private readonly WorkingBeatmap beatmap;
private LoadingAnimation loading;
private Sprite backgroundSprite;
private ModDisplay modDisplay;
public bool Loading
{
@@ -324,7 +322,7 @@ namespace osu.Game.Screens.Play
[BackgroundDependencyLoader]
private void load()
{
var metadata = beatmap.BeatmapInfo?.Metadata ?? new BeatmapMetadata();
var metadata = beatmap?.BeatmapInfo?.Metadata ?? new BeatmapMetadata();
AutoSizeAxes = Axes.Both;
Children = new Drawable[]
@@ -393,14 +391,6 @@ namespace osu.Game.Screens.Play
Origin = Anchor.TopCentre,
Anchor = Anchor.TopCentre,
},
new ModDisplay
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
AutoSizeAxes = Axes.Both,
Margin = new MarginPadding { Top = 20 },
Current = beatmap.Mods
}
},
}
};
+1 -1
View File
@@ -582,7 +582,7 @@ namespace osu.Game.Screens.Select
}
}
private void onBeatmapSetAdded(BeatmapSetInfo s, bool existing) => Carousel.UpdateBeatmapSet(s);
private void onBeatmapSetAdded(BeatmapSetInfo s, bool existing, bool silent) => Carousel.UpdateBeatmapSet(s);
private void onBeatmapSetRemoved(BeatmapSetInfo s) => Carousel.RemoveBeatmapSet(s);
private void onBeatmapRestored(BeatmapInfo b) => Carousel.UpdateBeatmapSet(beatmaps.QueryBeatmapSet(s => s.ID == b.BeatmapSetInfoID));
private void onBeatmapHidden(BeatmapInfo b) => Carousel.UpdateBeatmapSet(beatmaps.QueryBeatmapSet(s => s.ID == b.BeatmapSetInfoID));
-13
View File
@@ -1,7 +1,6 @@
// 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.Collections.Generic;
using System.IO;
using System.Text;
using osu.Game.Beatmaps;
@@ -22,18 +21,6 @@ namespace osu.Game.Tests.Beatmaps
HitObjects = baseBeatmap.HitObjects;
BeatmapInfo.Ruleset = ruleset;
BeatmapInfo.BeatmapSet.Metadata = BeatmapInfo.Metadata;
BeatmapInfo.BeatmapSet.Beatmaps = new List<BeatmapInfo> { BeatmapInfo };
BeatmapInfo.BeatmapSet.OnlineInfo = new BeatmapSetOnlineInfo
{
Status = BeatmapSetOnlineStatus.Ranked,
Covers = new BeatmapSetOnlineCovers
{
Cover = "https://assets.ppy.sh/beatmaps/163112/covers/cover.jpg",
Card = "https://assets.ppy.sh/beatmaps/163112/covers/card.jpg",
List = "https://assets.ppy.sh/beatmaps/163112/covers/list.jpg"
}
};
}
private static Beatmap createTestBeatmap()
-2
View File
@@ -230,8 +230,6 @@
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTLINE_TYPE_PARAMETER_LIST/@EntryValue">True</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ANONYMOUS_METHOD_DECLARATION_BRACES/@EntryValue">NEXT_LINE</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INITIALIZER_BRACES/@EntryValue">NEXT_LINE</s:String>
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_BLANK_LINES_IN_CODE/@EntryValue">1</s:Int64>
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_BLANK_LINES_IN_DECLARATIONS/@EntryValue">1</s:Int64>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/LINE_FEED_AT_FILE_END/@EntryValue">True</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ACCESSORHOLDER_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ACCESSOR_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String>