Merge branch 'master' into match-subscreen-redesign

This commit is contained in:
smoogipoo 2021-08-18 15:19:29 +09:00
commit f3bc9c3e45
37 changed files with 304 additions and 197 deletions

View File

@ -52,7 +52,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.813.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.813.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.813.0" /> <PackageReference Include="ppy.osu.Framework.Android" Version="2021.818.0" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Transitive Dependencies"> <ItemGroup Label="Transitive Dependencies">
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. --> <!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->

View File

@ -5,23 +5,23 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Game;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Screens.Play;
namespace osu.Desktop.Windows namespace osu.Desktop.Windows
{ {
public class GameplayWinKeyBlocker : Component public class GameplayWinKeyBlocker : Component
{ {
private Bindable<bool> disableWinKey; private Bindable<bool> disableWinKey;
private Bindable<bool> localUserPlaying; private IBindable<bool> localUserPlaying;
[Resolved] [Resolved]
private GameHost host { get; set; } private GameHost host { get; set; }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuGame game, OsuConfigManager config) private void load(ILocalUserPlayInfo localUserInfo, OsuConfigManager config)
{ {
localUserPlaying = game.LocalUserPlaying.GetBoundCopy(); localUserPlaying = localUserInfo.IsPlaying.GetBoundCopy();
localUserPlaying.BindValueChanged(_ => updateBlocking()); localUserPlaying.BindValueChanged(_ => updateBlocking());
disableWinKey = config.GetBindable<bool>(OsuSetting.GameplayDisableWinKey); disableWinKey = config.GetBindable<bool>(OsuSetting.GameplayDisableWinKey);

View File

@ -9,7 +9,7 @@ using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Catch.Difficulty.Skills namespace osu.Game.Rulesets.Catch.Difficulty.Skills
{ {
public class Movement : StrainSkill public class Movement : StrainDecaySkill
{ {
private const float absolute_player_positioning_error = 16f; private const float absolute_player_positioning_error = 16f;
private const float normalized_hitobject_radius = 41.0f; private const float normalized_hitobject_radius = 41.0f;

View File

@ -10,7 +10,7 @@ using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Mania.Difficulty.Skills namespace osu.Game.Rulesets.Mania.Difficulty.Skills
{ {
public class Strain : StrainSkill public class Strain : StrainDecaySkill
{ {
private const double individual_decay_base = 0.125; private const double individual_decay_base = 0.125;
private const double overall_decay_base = 0.30; private const double overall_decay_base = 0.30;
@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills
return individualStrain + overallStrain - CurrentStrain; return individualStrain + overallStrain - CurrentStrain;
} }
protected override double GetPeakStrain(double offset) protected override double CalculateInitialStrain(double offset)
=> applyDecay(individualStrain, offset - Previous[0].StartTime, individual_decay_base) => applyDecay(individualStrain, offset - Previous[0].StartTime, individual_decay_base)
+ applyDecay(overallStrain, offset - Previous[0].StartTime, overall_decay_base); + applyDecay(overallStrain, offset - Previous[0].StartTime, overall_decay_base);

View File

@ -10,7 +10,7 @@ using osu.Framework.Utils;
namespace osu.Game.Rulesets.Osu.Difficulty.Skills namespace osu.Game.Rulesets.Osu.Difficulty.Skills
{ {
public abstract class OsuStrainSkill : StrainSkill public abstract class OsuStrainSkill : StrainDecaySkill
{ {
/// <summary> /// <summary>
/// The number of sections with the highest strains, which the peak strain reductions will apply to. /// The number of sections with the highest strains, which the peak strain reductions will apply to.

View File

@ -64,11 +64,14 @@ namespace osu.Game.Rulesets.Osu.Edit
if (hitObject is DrawableHitCircle circle) if (hitObject is DrawableHitCircle circle)
{ {
circle.ApproachCircle using (circle.BeginAbsoluteSequence(circle.HitStateUpdateTime))
.FadeOutFromOne(EDITOR_HIT_OBJECT_FADE_OUT_EXTENSION * 4) {
.Expire(); circle.ApproachCircle
.FadeOutFromOne(EDITOR_HIT_OBJECT_FADE_OUT_EXTENSION * 4)
.Expire();
circle.ApproachCircle.ScaleTo(1.1f, 300, Easing.OutQuint); circle.ApproachCircle.ScaleTo(1.1f, 300, Easing.OutQuint);
}
} }
if (hitObject is IHasMainCirclePiece mainPieceContainer) if (hitObject is IHasMainCirclePiece mainPieceContainer)

View File

@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
/// <summary> /// <summary>
/// Calculates the colour coefficient of taiko difficulty. /// Calculates the colour coefficient of taiko difficulty.
/// </summary> /// </summary>
public class Colour : StrainSkill public class Colour : StrainDecaySkill
{ {
protected override double SkillMultiplier => 1; protected override double SkillMultiplier => 1;
protected override double StrainDecayBase => 0.4; protected override double StrainDecayBase => 0.4;

View File

@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
/// <summary> /// <summary>
/// Calculates the rhythm coefficient of taiko difficulty. /// Calculates the rhythm coefficient of taiko difficulty.
/// </summary> /// </summary>
public class Rhythm : StrainSkill public class Rhythm : StrainDecaySkill
{ {
protected override double SkillMultiplier => 10; protected override double SkillMultiplier => 10;
protected override double StrainDecayBase => 0; protected override double StrainDecayBase => 0;

View File

@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
/// <remarks> /// <remarks>
/// The reference play style chosen uses two hands, with full alternating (the hand changes after every hit). /// The reference play style chosen uses two hands, with full alternating (the hand changes after every hit).
/// </remarks> /// </remarks>
public class Stamina : StrainSkill public class Stamina : StrainDecaySkill
{ {
protected override double SkillMultiplier => 1; protected override double SkillMultiplier => 1;
protected override double StrainDecayBase => 0.4; protected override double StrainDecayBase => 0.4;

View File

@ -50,10 +50,10 @@ namespace osu.Game.Tests.Skins.IO
var imported2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("test skin", "skinner"), "skin2.osk")); var imported2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("test skin", "skinner"), "skin2.osk"));
Assert.That(imported2.ID, Is.Not.EqualTo(imported.ID)); Assert.That(imported2.ID, Is.Not.EqualTo(imported.ID));
Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins().Count, Is.EqualTo(1)); Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins(true).Count, Is.EqualTo(1));
// the first should be overwritten by the second import. // the first should be overwritten by the second import.
Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins().First().Files.First().FileInfoID, Is.EqualTo(imported2.Files.First().FileInfoID)); Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins(true).First().Files.First().FileInfoID, Is.EqualTo(imported2.Files.First().FileInfoID));
} }
finally finally
{ {
@ -76,10 +76,10 @@ namespace osu.Game.Tests.Skins.IO
var imported2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk(string.Empty, string.Empty), "download.osk")); var imported2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk(string.Empty, string.Empty), "download.osk"));
Assert.That(imported2.ID, Is.Not.EqualTo(imported.ID)); Assert.That(imported2.ID, Is.Not.EqualTo(imported.ID));
Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins().Count, Is.EqualTo(2)); Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins(true).Count, Is.EqualTo(2));
Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins().First().Files.First().FileInfoID, Is.EqualTo(imported.Files.First().FileInfoID)); Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins(true).First().Files.First().FileInfoID, Is.EqualTo(imported.Files.First().FileInfoID));
Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins().Last().Files.First().FileInfoID, Is.EqualTo(imported2.Files.First().FileInfoID)); Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins(true).Last().Files.First().FileInfoID, Is.EqualTo(imported2.Files.First().FileInfoID));
} }
finally finally
{ {
@ -101,10 +101,10 @@ namespace osu.Game.Tests.Skins.IO
var imported2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("test skin v2.1", "skinner"), "skin2.osk")); var imported2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("test skin v2.1", "skinner"), "skin2.osk"));
Assert.That(imported2.ID, Is.Not.EqualTo(imported.ID)); Assert.That(imported2.ID, Is.Not.EqualTo(imported.ID));
Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins().Count, Is.EqualTo(2)); Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins(true).Count, Is.EqualTo(2));
Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins().First().Files.First().FileInfoID, Is.EqualTo(imported.Files.First().FileInfoID)); Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins(true).First().Files.First().FileInfoID, Is.EqualTo(imported.Files.First().FileInfoID));
Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins().Last().Files.First().FileInfoID, Is.EqualTo(imported2.Files.First().FileInfoID)); Assert.That(osu.Dependencies.Get<SkinManager>().GetAllUserSkins(true).Last().Files.First().FileInfoID, Is.EqualTo(imported2.Files.First().FileInfoID));
} }
finally finally
{ {

View File

@ -19,6 +19,8 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
public class TestSceneHUDOverlay : OsuManualInputManagerTestScene public class TestSceneHUDOverlay : OsuManualInputManagerTestScene
{ {
private OsuConfigManager localConfig;
private HUDOverlay hudOverlay; private HUDOverlay hudOverlay;
[Cached] [Cached]
@ -31,8 +33,14 @@ namespace osu.Game.Tests.Visual.Gameplay
private Drawable hideTarget => hudOverlay.KeyCounter; private Drawable hideTarget => hudOverlay.KeyCounter;
private FillFlowContainer<KeyCounter> keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType<FillFlowContainer<KeyCounter>>().First(); private FillFlowContainer<KeyCounter> keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType<FillFlowContainer<KeyCounter>>().First();
[Resolved] [BackgroundDependencyLoader]
private OsuConfigManager config { get; set; } private void load()
{
Dependencies.Cache(localConfig = new OsuConfigManager(LocalStorage));
}
[SetUp]
public void SetUp() => Schedule(() => localConfig.SetValue(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Always));
[Test] [Test]
public void TestComboCounterIncrementing() public void TestComboCounterIncrementing()
@ -85,11 +93,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
createNew(); createNew();
HUDVisibilityMode originalConfigValue = HUDVisibilityMode.HideDuringGameplay; AddStep("set hud to never show", () => localConfig.SetValue(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Never));
AddStep("get original config value", () => originalConfigValue = config.Get<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode));
AddStep("set hud to never show", () => config.SetValue(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Never));
AddUntilStep("wait for fade", () => !hideTarget.IsPresent); AddUntilStep("wait for fade", () => !hideTarget.IsPresent);
@ -98,37 +102,28 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("stop trigering", () => InputManager.ReleaseKey(Key.ControlLeft)); AddStep("stop trigering", () => InputManager.ReleaseKey(Key.ControlLeft));
AddUntilStep("wait for fade", () => !hideTarget.IsPresent); AddUntilStep("wait for fade", () => !hideTarget.IsPresent);
AddStep("set original config value", () => config.SetValue(OsuSetting.HUDVisibilityMode, originalConfigValue));
} }
[Test] [Test]
public void TestExternalHideDoesntAffectConfig() public void TestExternalHideDoesntAffectConfig()
{ {
HUDVisibilityMode originalConfigValue = HUDVisibilityMode.HideDuringGameplay;
createNew(); createNew();
AddStep("get original config value", () => originalConfigValue = config.Get<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode));
AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false); AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
AddAssert("config unchanged", () => originalConfigValue == config.Get<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode)); AddAssert("config unchanged", () => localConfig.GetBindable<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode).IsDefault);
AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true); AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true);
AddAssert("config unchanged", () => originalConfigValue == config.Get<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode)); AddAssert("config unchanged", () => localConfig.GetBindable<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode).IsDefault);
} }
[Test] [Test]
public void TestChangeHUDVisibilityOnHiddenKeyCounter() public void TestChangeHUDVisibilityOnHiddenKeyCounter()
{ {
bool keyCounterVisibleValue = false;
createNew(); createNew();
AddStep("save keycounter visible value", () => keyCounterVisibleValue = config.Get<bool>(OsuSetting.KeyOverlay));
AddStep("set keycounter visible false", () => AddStep("hide key overlay", () =>
{ {
config.SetValue(OsuSetting.KeyOverlay, false); localConfig.SetValue(OsuSetting.KeyOverlay, false);
hudOverlay.KeyCounter.AlwaysVisible.Value = false; hudOverlay.KeyCounter.AlwaysVisible.Value = false;
}); });
@ -139,24 +134,16 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true); AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true);
AddUntilStep("hidetarget is visible", () => hideTarget.IsPresent); AddUntilStep("hidetarget is visible", () => hideTarget.IsPresent);
AddAssert("key counters still hidden", () => !keyCounterFlow.IsPresent); AddAssert("key counters still hidden", () => !keyCounterFlow.IsPresent);
AddStep("return value", () => config.SetValue(OsuSetting.KeyOverlay, keyCounterVisibleValue));
} }
[Test] [Test]
public void TestHiddenHUDDoesntBlockSkinnableComponentsLoad() public void TestHiddenHUDDoesntBlockSkinnableComponentsLoad()
{ {
HUDVisibilityMode originalConfigValue = default; AddStep("set hud to never show", () => localConfig.SetValue(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Never));
AddStep("get original config value", () => originalConfigValue = config.Get<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode));
AddStep("set hud to never show", () => config.SetValue(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Never));
createNew(); createNew();
AddUntilStep("wait for hud load", () => hudOverlay.IsLoaded); AddUntilStep("wait for hud load", () => hudOverlay.IsLoaded);
AddUntilStep("skinnable components loaded", () => hudOverlay.ChildrenOfType<SkinnableTargetContainer>().Single().ComponentsLoaded); AddUntilStep("skinnable components loaded", () => hudOverlay.ChildrenOfType<SkinnableTargetContainer>().Single().ComponentsLoaded);
AddStep("set original config value", () => config.SetValue(OsuSetting.HUDVisibilityMode, originalConfigValue));
} }
private void createNew(Action<HUDOverlay> action = null) private void createNew(Action<HUDOverlay> action = null)
@ -175,5 +162,11 @@ namespace osu.Game.Tests.Visual.Gameplay
Child = hudOverlay; Child = hudOverlay;
}); });
} }
protected override void Dispose(bool isDisposing)
{
localConfig?.Dispose();
base.Dispose(isDisposing);
}
} }
} }

View File

@ -6,13 +6,17 @@ using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer;
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus; using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate; using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osu.Game.Screens.Play.HUD;
using osu.Game.Screens.Play.PlayerSettings;
using osu.Game.Tests.Beatmaps.IO; using osu.Game.Tests.Beatmaps.IO;
using osu.Game.Users; using osu.Game.Users;
@ -23,6 +27,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Resolved] [Resolved]
private OsuGameBase game { get; set; } private OsuGameBase game { get; set; }
[Resolved]
private OsuConfigManager config { get; set; }
[Resolved] [Resolved]
private BeatmapManager beatmapManager { get; set; } private BeatmapManager beatmapManager { get; set; }
@ -80,6 +87,32 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddWaitStep("wait a bit", 20); AddWaitStep("wait a bit", 20);
} }
[Test]
public void TestSpectatorPlayerInteractiveElementsHidden()
{
HUDVisibilityMode originalConfigValue = default;
AddStep("get original config hud visibility", () => originalConfigValue = config.Get<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode));
AddStep("set config hud visibility to always", () => config.SetValue(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Always));
start(new[] { PLAYER_1_ID, PLAYER_2_ID });
loadSpectateScreen(false);
AddUntilStep("wait for player loaders", () => this.ChildrenOfType<PlayerLoader>().Count() == 2);
AddAssert("all player loader settings hidden", () => this.ChildrenOfType<PlayerLoader>().All(l => !l.ChildrenOfType<FillFlowContainer<PlayerSettingsGroup>>().Any()));
AddUntilStep("wait for players to load", () => spectatorScreen.AllPlayersLoaded);
// components wrapped in skinnable target containers load asynchronously, potentially taking more than one frame to load.
// therefore use until step rather than direct assert to account for that.
AddUntilStep("all interactive elements removed", () => this.ChildrenOfType<Player>().All(p =>
!p.ChildrenOfType<PlayerSettingsOverlay>().Any() &&
!p.ChildrenOfType<HoldForMenuButton>().Any() &&
p.ChildrenOfType<SongProgressBar>().SingleOrDefault()?.ShowHandle == false));
AddStep("restore config hud visibility", () => config.SetValue(OsuSetting.HUDVisibilityMode, originalConfigValue));
}
[Test] [Test]
public void TestTeamDisplay() public void TestTeamDisplay()
{ {

View File

@ -36,6 +36,11 @@ namespace osu.Game.Database
/// </summary> /// </summary>
public IQueryable<T> ConsumableItems => AddIncludesForConsumption(ContextFactory.Get().Set<T>()); public IQueryable<T> ConsumableItems => AddIncludesForConsumption(ContextFactory.Get().Set<T>());
/// <summary>
/// Access barebones items with no includes.
/// </summary>
public IQueryable<T> Items => ContextFactory.Get().Set<T>();
/// <summary> /// <summary>
/// Add a <typeparamref name="T"/> to the database. /// Add a <typeparamref name="T"/> to the database.
/// </summary> /// </summary>

View File

@ -7,6 +7,7 @@ using osu.Framework.Configuration;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Screens.Play;
namespace osu.Game.Input namespace osu.Game.Input
{ {
@ -24,14 +25,14 @@ namespace osu.Game.Input
private IBindable<bool> localUserPlaying; private IBindable<bool> localUserPlaying;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuGame game, FrameworkConfigManager frameworkConfigManager, OsuConfigManager osuConfigManager) private void load(ILocalUserPlayInfo localUserInfo, FrameworkConfigManager frameworkConfigManager, OsuConfigManager osuConfigManager)
{ {
frameworkConfineMode = frameworkConfigManager.GetBindable<ConfineMouseMode>(FrameworkSetting.ConfineMouseMode); frameworkConfineMode = frameworkConfigManager.GetBindable<ConfineMouseMode>(FrameworkSetting.ConfineMouseMode);
frameworkWindowMode = frameworkConfigManager.GetBindable<WindowMode>(FrameworkSetting.WindowMode); frameworkWindowMode = frameworkConfigManager.GetBindable<WindowMode>(FrameworkSetting.WindowMode);
frameworkWindowMode.BindValueChanged(_ => updateConfineMode()); frameworkWindowMode.BindValueChanged(_ => updateConfineMode());
osuConfineMode = osuConfigManager.GetBindable<OsuConfineMouseMode>(OsuSetting.ConfineMouseMode); osuConfineMode = osuConfigManager.GetBindable<OsuConfineMouseMode>(OsuSetting.ConfineMouseMode);
localUserPlaying = game.LocalUserPlaying.GetBoundCopy(); localUserPlaying = localUserInfo.IsPlaying.GetBoundCopy();
osuConfineMode.ValueChanged += _ => updateConfineMode(); osuConfineMode.ValueChanged += _ => updateConfineMode();
localUserPlaying.BindValueChanged(_ => updateConfineMode(), true); localUserPlaying.BindValueChanged(_ => updateConfineMode(), true);

View File

@ -47,39 +47,13 @@ namespace osu.Game.Online
pollIfNecessary(); pollIfNecessary();
} }
private bool pollIfNecessary() /// <summary>
/// Immediately performs a <see cref="Poll"/>.
/// </summary>
public void PollImmediately()
{ {
// we must be loaded so we have access to clock. lastTimePolled = Time.Current - TimeBetweenPolls.Value;
if (!IsLoaded) return false;
// there's already a poll process running.
if (pollingActive) return false;
// don't try polling if the time between polls hasn't been set.
if (TimeBetweenPolls.Value == 0) return false;
if (!lastTimePolled.HasValue)
{
doPoll();
return true;
}
if (Time.Current - lastTimePolled.Value > TimeBetweenPolls.Value)
{
doPoll();
return true;
}
// not enough time has passed since the last poll. we do want to schedule a poll to happen, though.
scheduleNextPoll(); scheduleNextPoll();
return false;
}
private void doPoll()
{
scheduledPoll = null;
pollingActive = true;
Poll().ContinueWith(_ => pollComplete());
} }
/// <summary> /// <summary>
@ -90,13 +64,11 @@ namespace osu.Game.Online
return Task.CompletedTask; return Task.CompletedTask;
} }
/// <summary> private void doPoll()
/// Immediately performs a <see cref="Poll"/>.
/// </summary>
public void PollImmediately()
{ {
lastTimePolled = Time.Current - TimeBetweenPolls.Value; scheduledPoll = null;
scheduleNextPoll(); pollingActive = true;
Poll().ContinueWith(_ => pollComplete());
} }
/// <summary> /// <summary>
@ -111,6 +83,33 @@ namespace osu.Game.Online
pollIfNecessary(); pollIfNecessary();
} }
private void pollIfNecessary()
{
// we must be loaded so we have access to clock.
if (!IsLoaded) return;
// there's already a poll process running.
if (pollingActive) return;
// don't try polling if the time between polls hasn't been set.
if (TimeBetweenPolls.Value == 0) return;
if (!lastTimePolled.HasValue)
{
doPoll();
return;
}
if (Time.Current - lastTimePolled.Value > TimeBetweenPolls.Value)
{
doPoll();
return;
}
// not enough time has passed since the last poll. we do want to schedule a poll to happen, though.
scheduleNextPoll();
}
private void scheduleNextPoll() private void scheduleNextPoll()
{ {
scheduledPoll?.Cancel(); scheduledPoll?.Cancel();

View File

@ -62,7 +62,7 @@ namespace osu.Game
/// The full osu! experience. Builds on top of <see cref="OsuGameBase"/> to add menus and binding logic /// The full osu! experience. Builds on top of <see cref="OsuGameBase"/> to add menus and binding logic
/// for initial components that are generally retrieved via DI. /// for initial components that are generally retrieved via DI.
/// </summary> /// </summary>
public class OsuGame : OsuGameBase, IKeyBindingHandler<GlobalAction> public class OsuGame : OsuGameBase, IKeyBindingHandler<GlobalAction>, ILocalUserPlayInfo
{ {
/// <summary> /// <summary>
/// The amount of global offset to apply when a left/right anchored overlay is displayed (ie. settings or notifications). /// The amount of global offset to apply when a left/right anchored overlay is displayed (ie. settings or notifications).
@ -1085,5 +1085,7 @@ namespace osu.Game
if (newScreen == null) if (newScreen == null)
Exit(); Exit();
} }
IBindable<bool> ILocalUserPlayInfo.IsPlaying => LocalUserPlaying;
} }
} }

View File

@ -4,6 +4,7 @@
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Screens.Play;
namespace osu.Game.Performance namespace osu.Game.Performance
{ {
@ -12,9 +13,9 @@ namespace osu.Game.Performance
private readonly IBindable<bool> localUserPlaying = new Bindable<bool>(); private readonly IBindable<bool> localUserPlaying = new Bindable<bool>();
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuGame game) private void load(ILocalUserPlayInfo localUserInfo)
{ {
localUserPlaying.BindTo(game.LocalUserPlaying); localUserPlaying.BindTo(localUserInfo.IsPlaying);
} }
protected override void LoadComplete() protected override void LoadComplete()

View File

@ -0,0 +1,54 @@
// 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 osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Difficulty.Skills
{
/// <summary>
/// Used to processes strain values of <see cref="DifficultyHitObject"/>s, keep track of strain levels caused by the processed objects
/// and to calculate a final difficulty value representing the difficulty of hitting all the processed objects.
/// </summary>
public abstract class StrainDecaySkill : StrainSkill
{
/// <summary>
/// Strain values are multiplied by this number for the given skill. Used to balance the value of different skills between each other.
/// </summary>
protected abstract double SkillMultiplier { get; }
/// <summary>
/// Determines how quickly strain decays for the given skill.
/// For example a value of 0.15 indicates that strain decays to 15% of its original value in one second.
/// </summary>
protected abstract double StrainDecayBase { get; }
/// <summary>
/// The current strain level.
/// </summary>
protected double CurrentStrain { get; private set; } = 1;
protected StrainDecaySkill(Mod[] mods)
: base(mods)
{
}
protected override double CalculateInitialStrain(double time) => CurrentStrain * strainDecay(time - Previous[0].StartTime);
protected override double StrainValueAt(DifficultyHitObject current)
{
CurrentStrain *= strainDecay(current.DeltaTime);
CurrentStrain += StrainValueOf(current) * SkillMultiplier;
return CurrentStrain;
}
/// <summary>
/// Calculates the strain value of a <see cref="DifficultyHitObject"/>. This value is affected by previously processed objects.
/// </summary>
protected abstract double StrainValueOf(DifficultyHitObject current);
private double strainDecay(double ms) => Math.Pow(StrainDecayBase, ms / 1000);
}
}

View File

@ -15,27 +15,11 @@ namespace osu.Game.Rulesets.Difficulty.Skills
/// </summary> /// </summary>
public abstract class StrainSkill : Skill public abstract class StrainSkill : Skill
{ {
/// <summary>
/// Strain values are multiplied by this number for the given skill. Used to balance the value of different skills between each other.
/// </summary>
protected abstract double SkillMultiplier { get; }
/// <summary>
/// Determines how quickly strain decays for the given skill.
/// For example a value of 0.15 indicates that strain decays to 15% of its original value in one second.
/// </summary>
protected abstract double StrainDecayBase { get; }
/// <summary> /// <summary>
/// The weight by which each strain value decays. /// The weight by which each strain value decays.
/// </summary> /// </summary>
protected virtual double DecayWeight => 0.9; protected virtual double DecayWeight => 0.9;
/// <summary>
/// The current strain level.
/// </summary>
protected double CurrentStrain { get; private set; } = 1;
/// <summary> /// <summary>
/// The length of each strain section. /// The length of each strain section.
/// </summary> /// </summary>
@ -52,6 +36,11 @@ namespace osu.Game.Rulesets.Difficulty.Skills
{ {
} }
/// <summary>
/// Returns the strain value at <see cref="DifficultyHitObject"/>. This value is calculated with or without respect to previous objects.
/// </summary>
protected abstract double StrainValueAt(DifficultyHitObject current);
/// <summary> /// <summary>
/// Process a <see cref="DifficultyHitObject"/> and update current strain values accordingly. /// Process a <see cref="DifficultyHitObject"/> and update current strain values accordingly.
/// </summary> /// </summary>
@ -68,10 +57,7 @@ namespace osu.Game.Rulesets.Difficulty.Skills
currentSectionEnd += SectionLength; currentSectionEnd += SectionLength;
} }
CurrentStrain *= strainDecay(current.DeltaTime); currentSectionPeak = Math.Max(StrainValueAt(current), currentSectionPeak);
CurrentStrain += StrainValueOf(current) * SkillMultiplier;
currentSectionPeak = Math.Max(CurrentStrain, currentSectionPeak);
} }
/// <summary> /// <summary>
@ -88,9 +74,9 @@ namespace osu.Game.Rulesets.Difficulty.Skills
/// <param name="time">The beginning of the new section in milliseconds.</param> /// <param name="time">The beginning of the new section in milliseconds.</param>
private void startNewSectionFrom(double time) private void startNewSectionFrom(double time)
{ {
// The maximum strain of the new section is not zero by default, strain decays as usual regardless of section boundaries. // The maximum strain of the new section is not zero by default
// This means we need to capture the strain level at the beginning of the new section, and use that as the initial peak level. // This means we need to capture the strain level at the beginning of the new section, and use that as the initial peak level.
currentSectionPeak = GetPeakStrain(time); currentSectionPeak = CalculateInitialStrain(time);
} }
/// <summary> /// <summary>
@ -98,7 +84,7 @@ namespace osu.Game.Rulesets.Difficulty.Skills
/// </summary> /// </summary>
/// <param name="time">The time to retrieve the peak strain at.</param> /// <param name="time">The time to retrieve the peak strain at.</param>
/// <returns>The peak strain.</returns> /// <returns>The peak strain.</returns>
protected virtual double GetPeakStrain(double time) => CurrentStrain * strainDecay(time - Previous[0].StartTime); protected abstract double CalculateInitialStrain(double time);
/// <summary> /// <summary>
/// Returns a live enumerable of the peak strains for each <see cref="SectionLength"/> section of the beatmap, /// Returns a live enumerable of the peak strains for each <see cref="SectionLength"/> section of the beatmap,
@ -124,12 +110,5 @@ namespace osu.Game.Rulesets.Difficulty.Skills
return difficulty; return difficulty;
} }
/// <summary>
/// Calculates the strain value of a <see cref="DifficultyHitObject"/>. This value is affected by previously processed objects.
/// </summary>
protected abstract double StrainValueOf(DifficultyHitObject current);
private double strainDecay(double ms) => Math.Pow(StrainDecayBase, ms / 1000);
} }
} }

View File

@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Mods
{ {
// Intercept and extract the internal number bindable from DifficultyBindable. // Intercept and extract the internal number bindable from DifficultyBindable.
// This will provide bounds and precision specifications for the slider bar. // This will provide bounds and precision specifications for the slider bar.
difficultyBindable = ((DifficultyBindable)value).GetBoundCopy(); difficultyBindable = (DifficultyBindable)value.GetBoundCopy();
sliderDisplayCurrent.BindTo(difficultyBindable.CurrentNumber); sliderDisplayCurrent.BindTo(difficultyBindable.CurrentNumber);
base.Current = difficultyBindable; base.Current = difficultyBindable;

View File

@ -128,6 +128,6 @@ namespace osu.Game.Rulesets.Mods
ExtendedLimits.UnbindFrom(otherDifficultyBindable.ExtendedLimits); ExtendedLimits.UnbindFrom(otherDifficultyBindable.ExtendedLimits);
} }
public new DifficultyBindable GetBoundCopy() => new DifficultyBindable { BindTarget = this }; protected override Bindable<float?> CreateInstance() => new DifficultyBindable();
} }
} }

View File

@ -41,6 +41,8 @@ namespace osu.Game.Screens.Edit
protected override int DefaultMaxValue => VALID_DIVISORS.Last(); protected override int DefaultMaxValue => VALID_DIVISORS.Last();
protected override int DefaultPrecision => 1; protected override int DefaultPrecision => 1;
protected override Bindable<int> CreateInstance() => new BindableBeatDivisor();
/// <summary> /// <summary>
/// Retrieves the appropriate colour for a beat divisor. /// Retrieves the appropriate colour for a beat divisor.
/// </summary> /// </summary>

View File

@ -166,14 +166,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
} }
if (IsSelected) if (IsSelected)
{
border.Show(); border.Show();
colour = colour.Lighten(0.3f);
}
else else
{
border.Hide(); border.Hide();
}
if (Item is IHasDuration duration && duration.Duration > 0) if (Item is IHasDuration duration && duration.Duration > 0)
circle.Colour = ColourInfo.GradientHorizontal(colour, colour.Lighten(0.4f)); circle.Colour = ColourInfo.GradientHorizontal(colour, colour.Lighten(0.4f));
@ -212,14 +207,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
for (int i = 0; i < repeats.RepeatCount; i++) for (int i = 0; i < repeats.RepeatCount; i++)
{ {
repeatsContainer.Add(new Circle repeatsContainer.Add(new Tick
{ {
Size = new Vector2(circle_size / 3), X = (float)(i + 1) / (repeats.RepeatCount + 1)
Alpha = 0.2f,
Anchor = Anchor.CentreLeft,
Origin = Anchor.Centre,
RelativePositionAxes = Axes.X,
X = (float)(i + 1) / (repeats.RepeatCount + 1),
}); });
} }
} }
@ -233,6 +223,17 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
public override Vector2 ScreenSpaceSelectionPoint => ScreenSpaceDrawQuad.TopLeft; public override Vector2 ScreenSpaceSelectionPoint => ScreenSpaceDrawQuad.TopLeft;
private class Tick : Circle
{
public Tick()
{
Size = new Vector2(circle_size / 4);
Anchor = Anchor.CentreLeft;
Origin = Anchor.Centre;
RelativePositionAxes = Axes.X;
}
}
public class DragArea : Circle public class DragArea : Circle
{ {
private readonly HitObject hitObject; private readonly HitObject hitObject;
@ -304,20 +305,15 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
private void updateState() private void updateState()
{ {
if (hasMouseDown) float scale = 0.5f;
{
this.ScaleTo(0.7f, 200, Easing.OutQuint);
}
else if (IsHovered)
{
this.ScaleTo(0.8f, 200, Easing.OutQuint);
}
else
{
this.ScaleTo(0.6f, 200, Easing.OutQuint);
}
this.FadeTo(IsHovered || hasMouseDown ? 0.8f : 0.2f, 200, Easing.OutQuint); if (hasMouseDown)
scale = 0.6f;
else if (IsHovered)
scale = 0.7f;
this.ScaleTo(scale, 200, Easing.OutQuint);
this.FadeTo(IsHovered || hasMouseDown ? 1f : 0.9f, 200, Easing.OutQuint);
} }
[Resolved] [Resolved]

View File

@ -186,16 +186,16 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
searchTextBox.Current.BindValueChanged(_ => updateFilterDebounced()); searchTextBox.Current.BindValueChanged(_ => updateFilterDebounced());
ruleset.BindValueChanged(_ => UpdateFilter()); ruleset.BindValueChanged(_ => UpdateFilter());
ListingPollingComponent.InitialRoomsReceived.BindValueChanged(_ => updateLoadingLayer());
isIdle.BindValueChanged(_ => updatePollingRate(this.IsCurrentScreen()), true); isIdle.BindValueChanged(_ => updatePollingRate(this.IsCurrentScreen()), true);
if (ongoingOperationTracker != null) if (ongoingOperationTracker != null)
{ {
operationInProgress.BindTo(ongoingOperationTracker.InProgress); operationInProgress.BindTo(ongoingOperationTracker.InProgress);
operationInProgress.BindValueChanged(_ => updateLoadingLayer(), true); operationInProgress.BindValueChanged(_ => updateLoadingLayer());
} }
ListingPollingComponent.InitialRoomsReceived.BindValueChanged(_ => updateLoadingLayer(), true);
updateFilter(); updateFilter();
} }

View File

@ -45,11 +45,13 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components
protected override void PopIn() protected override void PopIn()
{ {
base.PopIn();
Settings.MoveToY(0, TRANSITION_DURATION, Easing.OutQuint); Settings.MoveToY(0, TRANSITION_DURATION, Easing.OutQuint);
} }
protected override void PopOut() protected override void PopOut()
{ {
base.PopOut();
Settings.MoveToY(-1, TRANSITION_DURATION, Easing.InSine); Settings.MoveToY(-1, TRANSITION_DURATION, Easing.InSine);
} }

View File

@ -25,19 +25,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
[Resolved] [Resolved]
private MultiplayerClient client { get; set; } private MultiplayerClient client { get; set; }
private MultiplayerListingPollingComponent multiplayerListingPollingComponent => (MultiplayerListingPollingComponent)ListingPollingComponent;
private readonly IBindable<bool> isConnected = new Bindable<bool>();
protected override void LoadComplete()
{
base.LoadComplete();
isConnected.BindTo(client.IsConnected);
isConnected.BindValueChanged(c => Scheduler.AddOnce(() => multiplayerListingPollingComponent.AllowPolling = c.NewValue));
multiplayerListingPollingComponent.AllowPolling = isConnected.Value;
}
public override void OnResuming(IScreen last) public override void OnResuming(IScreen last)
{ {
base.OnResuming(last); base.OnResuming(last);
@ -47,7 +34,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
if (last is MultiplayerMatchSubScreen match) if (last is MultiplayerMatchSubScreen match)
{ {
RoomManager.RemoveRoom(match.Room); RoomManager.RemoveRoom(match.Room);
multiplayerListingPollingComponent.PollImmediately(); ListingPollingComponent.PollImmediately();
} }
} }
@ -84,27 +71,29 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
private class MultiplayerListingPollingComponent : ListingPollingComponent private class MultiplayerListingPollingComponent : ListingPollingComponent
{ {
private bool allowPolling; [Resolved]
private MultiplayerClient client { get; set; }
public bool AllowPolling private readonly IBindable<bool> isConnected = new Bindable<bool>();
[BackgroundDependencyLoader]
private void load()
{ {
get => allowPolling; isConnected.BindTo(client.IsConnected);
set isConnected.BindValueChanged(c => Scheduler.AddOnce(() =>
{ {
if (allowPolling == value) if (isConnected.Value && IsLoaded)
return;
allowPolling = value;
if (!allowPolling)
return;
if (IsLoaded)
PollImmediately(); PollImmediately();
} }), true);
} }
protected override Task Poll() => AllowPolling ? base.Poll() : Task.CompletedTask; protected override Task Poll()
{
if (!isConnected.Value)
return Task.CompletedTask;
return base.Poll();
}
} }
} }
} }

View File

@ -25,7 +25,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
/// <param name="score">The score containing the player's replay.</param> /// <param name="score">The score containing the player's replay.</param>
/// <param name="spectatorPlayerClock">The clock controlling the gameplay running state.</param> /// <param name="spectatorPlayerClock">The clock controlling the gameplay running state.</param>
public MultiSpectatorPlayer([NotNull] Score score, [NotNull] ISpectatorPlayerClock spectatorPlayerClock) public MultiSpectatorPlayer([NotNull] Score score, [NotNull] ISpectatorPlayerClock spectatorPlayerClock)
: base(score) : base(score, new PlayerConfiguration { AllowUserInteraction = false })
{ {
this.spectatorPlayerClock = spectatorPlayerClock; this.spectatorPlayerClock = spectatorPlayerClock;
} }
@ -34,6 +34,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
private void load() private void load()
{ {
spectatorPlayerClock.WaitingOnFrames.BindTo(waitingOnFrames); spectatorPlayerClock.WaitingOnFrames.BindTo(waitingOnFrames);
HUDOverlay.PlayerSettingsOverlay.Expire();
HUDOverlay.HoldToQuit.Expire();
} }
protected override void UpdateAfterChildren() protected override void UpdateAfterChildren()

View File

@ -3,6 +3,7 @@
using System; using System;
using JetBrains.Annotations; using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Game.Scoring; using osu.Game.Scoring;
using osu.Game.Screens.Menu; using osu.Game.Screens.Menu;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
@ -19,6 +20,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
{ {
} }
[BackgroundDependencyLoader]
private void load()
{
PlayerSettings.Expire();
}
protected override void LogoArriving(OsuLogo logo, bool resuming) protected override void LogoArriving(OsuLogo logo, bool resuming)
{ {
} }

View 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.Framework.Allocation;
using osu.Framework.Bindables;
namespace osu.Game.Screens.Play
{
[Cached]
public interface ILocalUserPlayInfo
{
/// <summary>
/// Whether the local user is currently playing.
/// </summary>
IBindable<bool> IsPlaying { get; }
}
}

View File

@ -38,7 +38,7 @@ namespace osu.Game.Screens.Play
{ {
[Cached] [Cached]
[Cached(typeof(ISamplePlaybackDisabler))] [Cached(typeof(ISamplePlaybackDisabler))]
public abstract class Player : ScreenWithBeatmapBackground, ISamplePlaybackDisabler public abstract class Player : ScreenWithBeatmapBackground, ISamplePlaybackDisabler, ILocalUserPlayInfo
{ {
/// <summary> /// <summary>
/// The delay upon completion of the beatmap before displaying the results screen. /// The delay upon completion of the beatmap before displaying the results screen.
@ -1052,5 +1052,7 @@ namespace osu.Game.Screens.Play
#endregion #endregion
IBindable<bool> ISamplePlaybackDisabler.SamplePlaybackDisabled => samplePlaybackDisabled; IBindable<bool> ISamplePlaybackDisabler.SamplePlaybackDisabled => samplePlaybackDisabled;
IBindable<bool> ILocalUserPlayInfo.IsPlaying => LocalUserPlaying;
} }
} }

View File

@ -20,6 +20,11 @@ namespace osu.Game.Screens.Play
/// </summary> /// </summary>
public bool AllowRestart { get; set; } = true; public bool AllowRestart { get; set; } = true;
/// <summary>
/// Whether the player should be able to interact with this player instance.
/// </summary>
public bool AllowUserInteraction { get; set; } = true;
/// <summary> /// <summary>
/// Whether the player should be allowed to skip intros/outros, advancing to the start of gameplay or the end of a storyboard. /// Whether the player should be allowed to skip intros/outros, advancing to the start of gameplay or the end of a storyboard.
/// </summary> /// </summary>

View File

@ -46,9 +46,14 @@ namespace osu.Game.Screens.Play
protected override bool PlayResumeSound => false; protected override bool PlayResumeSound => false;
protected BeatmapMetadataDisplay MetadataInfo; protected BeatmapMetadataDisplay MetadataInfo { get; private set; }
protected VisualSettings VisualSettings; /// <summary>
/// A fill flow containing the player settings groups, exposed for the ability to hide it from inheritors of the player loader.
/// </summary>
protected FillFlowContainer<PlayerSettingsGroup> PlayerSettings { get; private set; }
protected VisualSettings VisualSettings { get; private set; }
protected Task LoadTask { get; private set; } protected Task LoadTask { get; private set; }
@ -140,7 +145,7 @@ namespace osu.Game.Screens.Play
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
}, },
new FillFlowContainer<PlayerSettingsGroup> PlayerSettings = new FillFlowContainer<PlayerSettingsGroup>
{ {
Anchor = Anchor.TopRight, Anchor = Anchor.TopRight,
Origin = Anchor.TopRight, Origin = Anchor.TopRight,

View File

@ -119,7 +119,8 @@ namespace osu.Game.Screens.Play
if (drawableRuleset != null) if (drawableRuleset != null)
{ {
AllowSeeking.BindTo(drawableRuleset.HasReplayLoaded); if (player?.Configuration.AllowUserInteraction == true)
((IBindable<bool>)AllowSeeking).BindTo(drawableRuleset.HasReplayLoaded);
referenceClock = drawableRuleset.FrameStableClock; referenceClock = drawableRuleset.FrameStableClock;
Objects = drawableRuleset.Objects; Objects = drawableRuleset.Objects;

View File

@ -23,7 +23,8 @@ namespace osu.Game.Screens.Play
protected override bool CheckModsAllowFailure() => false; // todo: better support starting mid-way through beatmap protected override bool CheckModsAllowFailure() => false; // todo: better support starting mid-way through beatmap
public SpectatorPlayer(Score score) public SpectatorPlayer(Score score, PlayerConfiguration configuration = null)
: base(configuration)
{ {
this.score = score; this.score = score;
} }

View File

@ -105,12 +105,18 @@ namespace osu.Game.Skinning
/// Returns a list of all usable <see cref="SkinInfo"/>s that have been loaded by the user. /// Returns a list of all usable <see cref="SkinInfo"/>s that have been loaded by the user.
/// </summary> /// </summary>
/// <returns>A newly allocated list of available <see cref="SkinInfo"/>.</returns> /// <returns>A newly allocated list of available <see cref="SkinInfo"/>.</returns>
public List<SkinInfo> GetAllUserSkins() => ModelStore.ConsumableItems.Where(s => !s.DeletePending).ToList(); public List<SkinInfo> GetAllUserSkins(bool includeFiles = false)
{
if (includeFiles)
return ModelStore.ConsumableItems.Where(s => !s.DeletePending).ToList();
return ModelStore.Items.Where(s => !s.DeletePending).ToList();
}
public void SelectRandomSkin() public void SelectRandomSkin()
{ {
// choose from only user skins, removing the current selection to ensure a new one is chosen. // choose from only user skins, removing the current selection to ensure a new one is chosen.
var randomChoices = GetAllUsableSkins().Where(s => s.ID != CurrentSkinInfo.Value.ID).ToArray(); var randomChoices = ModelStore.Items.Where(s => !s.DeletePending && s.ID != CurrentSkinInfo.Value.ID).ToArray();
if (randomChoices.Length == 0) if (randomChoices.Length == 0)
{ {
@ -118,7 +124,8 @@ namespace osu.Game.Skinning
return; return;
} }
CurrentSkinInfo.Value = randomChoices.ElementAt(RNG.Next(0, randomChoices.Length)); var chosen = randomChoices.ElementAt(RNG.Next(0, randomChoices.Length));
CurrentSkinInfo.Value = ModelStore.ConsumableItems.Single(i => i.ID == chosen.ID);
} }
protected override SkinInfo CreateModel(ArchiveReader archive) => new SkinInfo { Name = archive.Name }; protected override SkinInfo CreateModel(ArchiveReader archive) => new SkinInfo { Name = archive.Name };

View File

@ -36,7 +36,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Realm" Version="10.3.0" /> <PackageReference Include="Realm" Version="10.3.0" />
<PackageReference Include="ppy.osu.Framework" Version="2021.813.0" /> <PackageReference Include="ppy.osu.Framework" Version="2021.818.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.813.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.813.0" />
<PackageReference Include="Sentry" Version="3.8.3" /> <PackageReference Include="Sentry" Version="3.8.3" />
<PackageReference Include="SharpCompress" Version="0.28.3" /> <PackageReference Include="SharpCompress" Version="0.28.3" />

View File

@ -70,7 +70,7 @@
<Reference Include="System.Net.Http" /> <Reference Include="System.Net.Http" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.813.0" /> <PackageReference Include="ppy.osu.Framework.iOS" Version="2021.818.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.813.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.813.0" />
</ItemGroup> </ItemGroup>
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) --> <!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
@ -93,7 +93,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="ppy.osu.Framework" Version="2021.813.0" /> <PackageReference Include="ppy.osu.Framework" Version="2021.818.0" />
<PackageReference Include="SharpCompress" Version="0.28.3" /> <PackageReference Include="SharpCompress" Version="0.28.3" />
<PackageReference Include="NUnit" Version="3.13.2" /> <PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="SharpRaven" Version="2.4.0" /> <PackageReference Include="SharpRaven" Version="2.4.0" />