1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-15 05:42:56 +08:00

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>
<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 Label="Transitive Dependencies">
<!-- 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.Graphics;
using osu.Framework.Platform;
using osu.Game;
using osu.Game.Configuration;
using osu.Game.Screens.Play;
namespace osu.Desktop.Windows
{
public class GameplayWinKeyBlocker : Component
{
private Bindable<bool> disableWinKey;
private Bindable<bool> localUserPlaying;
private IBindable<bool> localUserPlaying;
[Resolved]
private GameHost host { get; set; }
[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());
disableWinKey = config.GetBindable<bool>(OsuSetting.GameplayDisableWinKey);

View File

@ -9,7 +9,7 @@ using osu.Game.Rulesets.Mods;
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 normalized_hitobject_radius = 41.0f;

View File

@ -10,7 +10,7 @@ using osu.Game.Rulesets.Mods;
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 overall_decay_base = 0.30;
@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills
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(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
{
public abstract class OsuStrainSkill : StrainSkill
public abstract class OsuStrainSkill : StrainDecaySkill
{
/// <summary>
/// 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)
{
circle.ApproachCircle
.FadeOutFromOne(EDITOR_HIT_OBJECT_FADE_OUT_EXTENSION * 4)
.Expire();
using (circle.BeginAbsoluteSequence(circle.HitStateUpdateTime))
{
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)

View File

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

View File

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

View File

@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
/// <remarks>
/// The reference play style chosen uses two hands, with full alternating (the hand changes after every hit).
/// </remarks>
public class Stamina : StrainSkill
public class Stamina : StrainDecaySkill
{
protected override double SkillMultiplier => 1;
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"));
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.
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
{
@ -76,10 +76,10 @@ namespace osu.Game.Tests.Skins.IO
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(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().Last().Files.First().FileInfoID, Is.EqualTo(imported2.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(true).Last().Files.First().FileInfoID, Is.EqualTo(imported2.Files.First().FileInfoID));
}
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"));
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().Last().Files.First().FileInfoID, Is.EqualTo(imported2.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(true).Last().Files.First().FileInfoID, Is.EqualTo(imported2.Files.First().FileInfoID));
}
finally
{

View File

@ -19,6 +19,8 @@ namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneHUDOverlay : OsuManualInputManagerTestScene
{
private OsuConfigManager localConfig;
private HUDOverlay hudOverlay;
[Cached]
@ -31,8 +33,14 @@ namespace osu.Game.Tests.Visual.Gameplay
private Drawable hideTarget => hudOverlay.KeyCounter;
private FillFlowContainer<KeyCounter> keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType<FillFlowContainer<KeyCounter>>().First();
[Resolved]
private OsuConfigManager config { get; set; }
[BackgroundDependencyLoader]
private void load()
{
Dependencies.Cache(localConfig = new OsuConfigManager(LocalStorage));
}
[SetUp]
public void SetUp() => Schedule(() => localConfig.SetValue(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Always));
[Test]
public void TestComboCounterIncrementing()
@ -85,11 +93,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
createNew();
HUDVisibilityMode originalConfigValue = HUDVisibilityMode.HideDuringGameplay;
AddStep("get original config value", () => originalConfigValue = config.Get<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode));
AddStep("set hud to never show", () => config.SetValue(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Never));
AddStep("set hud to never show", () => localConfig.SetValue(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Never));
AddUntilStep("wait for fade", () => !hideTarget.IsPresent);
@ -98,37 +102,28 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("stop trigering", () => InputManager.ReleaseKey(Key.ControlLeft));
AddUntilStep("wait for fade", () => !hideTarget.IsPresent);
AddStep("set original config value", () => config.SetValue(OsuSetting.HUDVisibilityMode, originalConfigValue));
}
[Test]
public void TestExternalHideDoesntAffectConfig()
{
HUDVisibilityMode originalConfigValue = HUDVisibilityMode.HideDuringGameplay;
createNew();
AddStep("get original config value", () => originalConfigValue = config.Get<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode));
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);
AddAssert("config unchanged", () => originalConfigValue == config.Get<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode));
AddAssert("config unchanged", () => localConfig.GetBindable<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode).IsDefault);
}
[Test]
public void TestChangeHUDVisibilityOnHiddenKeyCounter()
{
bool keyCounterVisibleValue = false;
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;
});
@ -139,24 +134,16 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true);
AddUntilStep("hidetarget is visible", () => hideTarget.IsPresent);
AddAssert("key counters still hidden", () => !keyCounterFlow.IsPresent);
AddStep("return value", () => config.SetValue(OsuSetting.KeyOverlay, keyCounterVisibleValue));
}
[Test]
public void TestHiddenHUDDoesntBlockSkinnableComponentsLoad()
{
HUDVisibilityMode originalConfigValue = default;
AddStep("get original config value", () => originalConfigValue = config.Get<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode));
AddStep("set hud to never show", () => config.SetValue(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Never));
AddStep("set hud to never show", () => localConfig.SetValue(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Never));
createNew();
AddUntilStep("wait for hud load", () => hudOverlay.IsLoaded);
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)
@ -175,5 +162,11 @@ namespace osu.Game.Tests.Visual.Gameplay
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 osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate;
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.Users;
@ -23,6 +27,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Resolved]
private OsuGameBase game { get; set; }
[Resolved]
private OsuConfigManager config { get; set; }
[Resolved]
private BeatmapManager beatmapManager { get; set; }
@ -80,6 +87,32 @@ namespace osu.Game.Tests.Visual.Multiplayer
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]
public void TestTeamDisplay()
{

View File

@ -36,6 +36,11 @@ namespace osu.Game.Database
/// </summary>
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>
/// Add a <typeparamref name="T"/> to the database.
/// </summary>

View File

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

View File

@ -47,39 +47,13 @@ namespace osu.Game.Online
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.
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.
lastTimePolled = Time.Current - TimeBetweenPolls.Value;
scheduleNextPoll();
return false;
}
private void doPoll()
{
scheduledPoll = null;
pollingActive = true;
Poll().ContinueWith(_ => pollComplete());
}
/// <summary>
@ -90,13 +64,11 @@ namespace osu.Game.Online
return Task.CompletedTask;
}
/// <summary>
/// Immediately performs a <see cref="Poll"/>.
/// </summary>
public void PollImmediately()
private void doPoll()
{
lastTimePolled = Time.Current - TimeBetweenPolls.Value;
scheduleNextPoll();
scheduledPoll = null;
pollingActive = true;
Poll().ContinueWith(_ => pollComplete());
}
/// <summary>
@ -111,6 +83,33 @@ namespace osu.Game.Online
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()
{
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
/// for initial components that are generally retrieved via DI.
/// </summary>
public class OsuGame : OsuGameBase, IKeyBindingHandler<GlobalAction>
public class OsuGame : OsuGameBase, IKeyBindingHandler<GlobalAction>, ILocalUserPlayInfo
{
/// <summary>
/// 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)
Exit();
}
IBindable<bool> ILocalUserPlayInfo.IsPlaying => LocalUserPlaying;
}
}

View File

@ -4,6 +4,7 @@
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Screens.Play;
namespace osu.Game.Performance
{
@ -12,9 +13,9 @@ namespace osu.Game.Performance
private readonly IBindable<bool> localUserPlaying = new Bindable<bool>();
[BackgroundDependencyLoader]
private void load(OsuGame game)
private void load(ILocalUserPlayInfo localUserInfo)
{
localUserPlaying.BindTo(game.LocalUserPlaying);
localUserPlaying.BindTo(localUserInfo.IsPlaying);
}
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>
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>
/// The weight by which each strain value decays.
/// </summary>
protected virtual double DecayWeight => 0.9;
/// <summary>
/// The current strain level.
/// </summary>
protected double CurrentStrain { get; private set; } = 1;
/// <summary>
/// The length of each strain section.
/// </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>
/// Process a <see cref="DifficultyHitObject"/> and update current strain values accordingly.
/// </summary>
@ -68,10 +57,7 @@ namespace osu.Game.Rulesets.Difficulty.Skills
currentSectionEnd += SectionLength;
}
CurrentStrain *= strainDecay(current.DeltaTime);
CurrentStrain += StrainValueOf(current) * SkillMultiplier;
currentSectionPeak = Math.Max(CurrentStrain, currentSectionPeak);
currentSectionPeak = Math.Max(StrainValueAt(current), currentSectionPeak);
}
/// <summary>
@ -88,9 +74,9 @@ namespace osu.Game.Rulesets.Difficulty.Skills
/// <param name="time">The beginning of the new section in milliseconds.</param>
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.
currentSectionPeak = GetPeakStrain(time);
currentSectionPeak = CalculateInitialStrain(time);
}
/// <summary>
@ -98,7 +84,7 @@ namespace osu.Game.Rulesets.Difficulty.Skills
/// </summary>
/// <param name="time">The time to retrieve the peak strain at.</param>
/// <returns>The peak strain.</returns>
protected virtual double GetPeakStrain(double time) => CurrentStrain * strainDecay(time - Previous[0].StartTime);
protected abstract double CalculateInitialStrain(double time);
/// <summary>
/// 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;
}
/// <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.
// This will provide bounds and precision specifications for the slider bar.
difficultyBindable = ((DifficultyBindable)value).GetBoundCopy();
difficultyBindable = (DifficultyBindable)value.GetBoundCopy();
sliderDisplayCurrent.BindTo(difficultyBindable.CurrentNumber);
base.Current = difficultyBindable;

View File

@ -128,6 +128,6 @@ namespace osu.Game.Rulesets.Mods
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 DefaultPrecision => 1;
protected override Bindable<int> CreateInstance() => new BindableBeatDivisor();
/// <summary>
/// Retrieves the appropriate colour for a beat divisor.
/// </summary>

View File

@ -166,14 +166,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
}
if (IsSelected)
{
border.Show();
colour = colour.Lighten(0.3f);
}
else
{
border.Hide();
}
if (Item is IHasDuration duration && duration.Duration > 0)
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++)
{
repeatsContainer.Add(new Circle
repeatsContainer.Add(new Tick
{
Size = new Vector2(circle_size / 3),
Alpha = 0.2f,
Anchor = Anchor.CentreLeft,
Origin = Anchor.Centre,
RelativePositionAxes = Axes.X,
X = (float)(i + 1) / (repeats.RepeatCount + 1),
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;
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
{
private readonly HitObject hitObject;
@ -304,20 +305,15 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
private void updateState()
{
if (hasMouseDown)
{
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);
}
float scale = 0.5f;
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]

View File

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

View File

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

View File

@ -25,19 +25,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
[Resolved]
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)
{
base.OnResuming(last);
@ -47,7 +34,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
if (last is MultiplayerMatchSubScreen match)
{
RoomManager.RemoveRoom(match.Room);
multiplayerListingPollingComponent.PollImmediately();
ListingPollingComponent.PollImmediately();
}
}
@ -84,27 +71,29 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
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;
set
isConnected.BindTo(client.IsConnected);
isConnected.BindValueChanged(c => Scheduler.AddOnce(() =>
{
if (allowPolling == value)
return;
allowPolling = value;
if (!allowPolling)
return;
if (IsLoaded)
if (isConnected.Value && IsLoaded)
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="spectatorPlayerClock">The clock controlling the gameplay running state.</param>
public MultiSpectatorPlayer([NotNull] Score score, [NotNull] ISpectatorPlayerClock spectatorPlayerClock)
: base(score)
: base(score, new PlayerConfiguration { AllowUserInteraction = false })
{
this.spectatorPlayerClock = spectatorPlayerClock;
}
@ -34,6 +34,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
private void load()
{
spectatorPlayerClock.WaitingOnFrames.BindTo(waitingOnFrames);
HUDOverlay.PlayerSettingsOverlay.Expire();
HUDOverlay.HoldToQuit.Expire();
}
protected override void UpdateAfterChildren()

View File

@ -3,6 +3,7 @@
using System;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Game.Scoring;
using osu.Game.Screens.Menu;
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)
{
}

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

View File

@ -20,6 +20,11 @@ namespace osu.Game.Screens.Play
/// </summary>
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>
/// Whether the player should be allowed to skip intros/outros, advancing to the start of gameplay or the end of a storyboard.
/// </summary>

View File

@ -46,9 +46,14 @@ namespace osu.Game.Screens.Play
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; }
@ -140,7 +145,7 @@ namespace osu.Game.Screens.Play
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
new FillFlowContainer<PlayerSettingsGroup>
PlayerSettings = new FillFlowContainer<PlayerSettingsGroup>
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,

View File

@ -119,7 +119,8 @@ namespace osu.Game.Screens.Play
if (drawableRuleset != null)
{
AllowSeeking.BindTo(drawableRuleset.HasReplayLoaded);
if (player?.Configuration.AllowUserInteraction == true)
((IBindable<bool>)AllowSeeking).BindTo(drawableRuleset.HasReplayLoaded);
referenceClock = drawableRuleset.FrameStableClock;
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
public SpectatorPlayer(Score score)
public SpectatorPlayer(Score score, PlayerConfiguration configuration = null)
: base(configuration)
{
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.
/// </summary>
/// <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()
{
// 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)
{
@ -118,7 +124,8 @@ namespace osu.Game.Skinning
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 };

View File

@ -36,7 +36,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<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="Sentry" Version="3.8.3" />
<PackageReference Include="SharpCompress" Version="0.28.3" />

View File

@ -70,7 +70,7 @@
<Reference Include="System.Net.Http" />
</ItemGroup>
<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" />
</ItemGroup>
<!-- 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.Core" Version="2.2.6" />
<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="NUnit" Version="3.13.2" />
<PackageReference Include="SharpRaven" Version="2.4.0" />