1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-15 01:43:20 +08:00

Merge branch 'ppy:master' into catch-hide-in-relax

This commit is contained in:
NullifiedJosh 2022-10-06 18:52:59 +08:00 committed by GitHub
commit c73195fa77
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
121 changed files with 794 additions and 521 deletions

View File

@ -4,6 +4,9 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true cancel-in-progress: true
permissions:
contents: read # to fetch code (actions/checkout)
jobs: jobs:
inspect-code: inspect-code:
name: Code Quality name: Code Quality

View File

@ -8,8 +8,12 @@ on:
workflows: ["Continuous Integration"] workflows: ["Continuous Integration"]
types: types:
- completed - completed
permissions: {}
jobs: jobs:
annotate: annotate:
permissions:
checks: write # to create checks (dorny/test-reporter)
name: Annotate CI run with test results name: Annotate CI run with test results
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion != 'cancelled' }} if: ${{ github.event.workflow_run.conclusion != 'cancelled' }}

View File

@ -5,6 +5,9 @@ on:
tags: tags:
- '*' - '*'
permissions:
contents: read # to fetch code (actions/checkout)
jobs: jobs:
sentry_release: sentry_release:
runs-on: ubuntu-latest runs-on: ubuntu-latest

View File

@ -52,7 +52,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.831.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2022.831.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.922.0" /> <PackageReference Include="ppy.osu.Framework.Android" Version="2022.1005.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

@ -137,12 +137,13 @@ namespace osu.Desktop
{ {
base.SetHost(host); base.SetHost(host);
var iconStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico");
var desktopWindow = (SDL2DesktopWindow)host.Window; var desktopWindow = (SDL2DesktopWindow)host.Window;
var iconStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico");
if (iconStream != null)
desktopWindow.SetIconFromStream(iconStream);
desktopWindow.CursorState |= CursorState.Hidden; desktopWindow.CursorState |= CursorState.Hidden;
desktopWindow.SetIconFromStream(iconStream);
desktopWindow.Title = Name; desktopWindow.Title = Name;
desktopWindow.DragDrop += f => fileDrop(new[] { f }); desktopWindow.DragDrop += f => fileDrop(new[] { f });
} }

View File

@ -3,7 +3,6 @@
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Configuration;
using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
@ -16,22 +15,14 @@ namespace osu.Game.Rulesets.Catch.Mods
{ {
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1; public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
[SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] public override BindableFloat SizeMultiplier { get; } = new BindableFloat(1)
public override BindableFloat SizeMultiplier { get; } = new BindableFloat
{ {
MinValue = 0.5f, MinValue = 0.5f,
MaxValue = 1.5f, MaxValue = 1.5f,
Default = 1f,
Value = 1f,
Precision = 0.1f Precision = 0.1f
}; };
[SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")] public override BindableBool ComboBasedSize { get; } = new BindableBool(true);
public override BindableBool ComboBasedSize { get; } = new BindableBool
{
Default = true,
Value = true
};
public override float DefaultFlashlightSize => 350; public override float DefaultFlashlightSize => 350;

View File

@ -6,8 +6,6 @@ using osu.Framework.Bindables;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Configuration;
using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
@ -17,15 +15,8 @@ namespace osu.Game.Rulesets.Catch.Mods
{ {
public override LocalisableString Description => "Where's the catcher?"; public override LocalisableString Description => "Where's the catcher?";
[SettingSource( public override BindableInt HiddenComboCount { get; } = new BindableInt(10)
"Hidden at combo",
"The combo count at which the catcher becomes completely hidden",
SettingControlType = typeof(SettingsSlider<int, HiddenComboSlider>)
)]
public override BindableInt HiddenComboCount { get; } = new BindableInt
{ {
Default = 10,
Value = 10,
MinValue = 0, MinValue = 0,
MaxValue = 50, MaxValue = 50,
}; };

View File

@ -5,7 +5,6 @@ using System;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Layout; using osu.Framework.Layout;
using osu.Game.Configuration;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osuTK; using osuTK;
@ -17,22 +16,14 @@ namespace osu.Game.Rulesets.Mania.Mods
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(ModHidden) }; public override Type[] IncompatibleMods => new[] { typeof(ModHidden) };
[SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] public override BindableFloat SizeMultiplier { get; } = new BindableFloat(1)
public override BindableFloat SizeMultiplier { get; } = new BindableFloat
{ {
MinValue = 0.5f, MinValue = 0.5f,
MaxValue = 3f, MaxValue = 3f,
Default = 1f,
Value = 1f,
Precision = 0.1f Precision = 0.1f
}; };
[SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")] public override BindableBool ComboBasedSize { get; } = new BindableBool();
public override BindableBool ComboBasedSize { get; } = new BindableBool
{
Default = false,
Value = false
};
public override float DefaultFlashlightSize => 50; public override float DefaultFlashlightSize => 50;

View File

@ -54,10 +54,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
} }
} }
protected override void UpdateInitialTransforms()
{
}
protected override void UpdateStartTimeStateTransforms() => this.FadeOut(150); protected override void UpdateStartTimeStateTransforms() => this.FadeOut(150);
} }
} }

View File

@ -30,14 +30,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
public bool UpdateResult() => base.UpdateResult(true); public bool UpdateResult() => base.UpdateResult(true);
protected override void UpdateInitialTransforms()
{
base.UpdateInitialTransforms();
// This hitobject should never expire, so this is just a safe maximum.
LifetimeEnd = LifetimeStart + 30000;
}
protected override void UpdateHitStateTransforms(ArmedState state) protected override void UpdateHitStateTransforms(ArmedState state)
{ {
// suppress the base call explicitly. // suppress the base call explicitly.

View File

@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
public void UpdateResult() => base.UpdateResult(true); public void UpdateResult() => base.UpdateResult(true);
protected override double MaximumJudgementOffset => base.MaximumJudgementOffset * release_window_lenience; public override double MaximumJudgementOffset => base.MaximumJudgementOffset * release_window_lenience;
protected override void CheckForResult(bool userTriggered, double timeOffset) protected override void CheckForResult(bool userTriggered, double timeOffset)
{ {

View File

@ -23,10 +23,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
protected readonly IBindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>(); protected readonly IBindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>();
// Leaving the default (10s) makes hitobjects not appear, as this offset is used for the initial state transforms.
// Calculated as DrawableManiaRuleset.MAX_TIME_RANGE + some additional allowance for velocity < 1.
protected override double InitialLifetimeOffset => 30000;
[Resolved(canBeNull: true)] [Resolved(canBeNull: true)]
private ManiaPlayfield playfield { get; set; } private ManiaPlayfield playfield { get; set; }

View File

@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
if (!(currentObj.BaseObject is Spinner)) if (!(currentObj.BaseObject is Spinner))
{ {
double jumpDistance = (osuHitObject.StackedPosition - currentHitObject.EndPosition).Length; double jumpDistance = (osuHitObject.StackedPosition - currentHitObject.StackedEndPosition).Length;
cumulativeStrainTime += lastObj.StrainTime; cumulativeStrainTime += lastObj.StrainTime;

View File

@ -4,7 +4,6 @@
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Configuration;
namespace osu.Game.Rulesets.Osu.Mods namespace osu.Game.Rulesets.Osu.Mods
{ {
@ -18,13 +17,10 @@ namespace osu.Game.Rulesets.Osu.Mods
public override LocalisableString Description => "Hit them at the right size!"; public override LocalisableString Description => "Hit them at the right size!";
[SettingSource("Starting Size", "The initial size multiplier applied to all objects.")] public override BindableNumber<float> StartScale { get; } = new BindableFloat(2)
public override BindableNumber<float> StartScale { get; } = new BindableFloat
{ {
MinValue = 1f, MinValue = 1f,
MaxValue = 25f, MaxValue = 25f,
Default = 2f,
Value = 2f,
Precision = 0.1f, Precision = 0.1f,
}; };
} }

View File

@ -32,22 +32,14 @@ namespace osu.Game.Rulesets.Osu.Mods
Precision = default_follow_delay, Precision = default_follow_delay,
}; };
[SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] public override BindableFloat SizeMultiplier { get; } = new BindableFloat(1)
public override BindableFloat SizeMultiplier { get; } = new BindableFloat
{ {
MinValue = 0.5f, MinValue = 0.5f,
MaxValue = 2f, MaxValue = 2f,
Default = 1f,
Value = 1f,
Precision = 0.1f Precision = 0.1f
}; };
[SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")] public override BindableBool ComboBasedSize { get; } = new BindableBool(true);
public override BindableBool ComboBasedSize { get; } = new BindableBool
{
Default = true,
Value = true
};
public override float DefaultFlashlightSize => 180; public override float DefaultFlashlightSize => 180;

View File

@ -4,7 +4,6 @@
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Configuration;
namespace osu.Game.Rulesets.Osu.Mods namespace osu.Game.Rulesets.Osu.Mods
{ {
@ -18,13 +17,10 @@ namespace osu.Game.Rulesets.Osu.Mods
public override LocalisableString Description => "Hit them at the right size!"; public override LocalisableString Description => "Hit them at the right size!";
[SettingSource("Starting Size", "The initial size multiplier applied to all objects.")] public override BindableNumber<float> StartScale { get; } = new BindableFloat(0.5f)
public override BindableNumber<float> StartScale { get; } = new BindableFloat
{ {
MinValue = 0f, MinValue = 0f,
MaxValue = 0.99f, MaxValue = 0.99f,
Default = 0.5f,
Value = 0.5f,
Precision = 0.01f, Precision = 0.01f,
}; };
} }

View File

@ -7,8 +7,6 @@ using osu.Framework.Bindables;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
@ -22,15 +20,8 @@ namespace osu.Game.Rulesets.Osu.Mods
private PeriodTracker spinnerPeriods = null!; private PeriodTracker spinnerPeriods = null!;
[SettingSource( public override BindableInt HiddenComboCount { get; } = new BindableInt(10)
"Hidden at combo",
"The combo count at which the cursor becomes completely hidden",
SettingControlType = typeof(SettingsSlider<int, HiddenComboSlider>)
)]
public override BindableInt HiddenComboCount { get; } = new BindableInt
{ {
Default = 10,
Value = 10,
MinValue = 0, MinValue = 0,
MaxValue = 50, MaxValue = 50,
}; };

View File

@ -4,6 +4,7 @@
using System; using System;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Configuration;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
@ -20,6 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;
[SettingSource("Starting Size", "The initial size multiplier applied to all objects.")]
public abstract BindableNumber<float> StartScale { get; } public abstract BindableNumber<float> StartScale { get; }
protected virtual float EndScale => 1; protected virtual float EndScale => 1;

View File

@ -29,10 +29,8 @@ namespace osu.Game.Rulesets.Osu.Mods
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModTarget)).ToArray(); public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModTarget)).ToArray();
[SettingSource("Angle sharpness", "How sharp angles should be", SettingControlType = typeof(SettingsSlider<float>))] [SettingSource("Angle sharpness", "How sharp angles should be", SettingControlType = typeof(SettingsSlider<float>))]
public BindableFloat AngleSharpness { get; } = new BindableFloat public BindableFloat AngleSharpness { get; } = new BindableFloat(7)
{ {
Default = 7,
Value = 7,
MinValue = 1, MinValue = 1,
MaxValue = 10, MaxValue = 10,
Precision = 0.1f Precision = 0.1f

View File

@ -53,11 +53,7 @@ namespace osu.Game.Rulesets.Osu.Mods
}).ToArray(); }).ToArray();
[SettingSource("Seed", "Use a custom seed instead of a random one", SettingControlType = typeof(SettingsNumberBox))] [SettingSource("Seed", "Use a custom seed instead of a random one", SettingControlType = typeof(SettingsNumberBox))]
public Bindable<int?> Seed { get; } = new Bindable<int?> public Bindable<int?> Seed { get; } = new Bindable<int?>();
{
Default = null,
Value = null
};
[SettingSource("Metronome ticks", "Whether a metronome beat should play in the background")] [SettingSource("Metronome ticks", "Whether a metronome beat should play in the background")]
public Bindable<bool> Metronome { get; } = new BindableBool(true); public Bindable<bool> Metronome { get; } = new BindableBool(true);

View File

@ -204,12 +204,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
// todo: temporary / arbitrary, used for lifetime optimisation. // todo: temporary / arbitrary, used for lifetime optimisation.
this.Delay(800).FadeOut(); this.Delay(800).FadeOut();
// in the case of an early state change, the fade should be expedited to the current point in time.
if (HitStateUpdateTime < HitObject.StartTime)
ApproachCircle.FadeOut(50);
switch (state) switch (state)
{ {
default:
ApproachCircle.FadeOut();
break;
case ArmedState.Idle: case ArmedState.Idle:
HitArea.HitAction = null; HitArea.HitAction = null;
break; break;

View File

@ -11,7 +11,9 @@ using osu.Framework.Graphics.Primitives;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Rulesets.Osu.Scoring;
using osuTK; using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Objects.Drawables namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
@ -64,6 +66,23 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
ScaleBindable.UnbindFrom(HitObject.ScaleBindable); ScaleBindable.UnbindFrom(HitObject.ScaleBindable);
} }
protected override void UpdateInitialTransforms()
{
base.UpdateInitialTransforms();
// Dim should only be applied at a top level, as it will be implicitly applied to nested objects.
if (ParentHitObject == null)
{
// Of note, no one noticed this was missing for years, but it definitely feels like it should still exist.
// For now this is applied across all skins, and matches stable.
// For simplicity, dim colour is applied to the DrawableHitObject itself.
// We may need to make a nested container setup if this even causes a usage conflict (ie. with a mod).
this.FadeColour(new Color4(195, 195, 195, 255));
using (BeginDelayedSequence(InitialLifetimeOffset - OsuHitWindows.MISS_WINDOW))
this.FadeColour(Color4.White, 100);
}
}
protected sealed override double InitialLifetimeOffset => HitObject.TimePreempt; protected sealed override double InitialLifetimeOffset => HitObject.TimePreempt;
private OsuInputManager osuActionInputManager; private OsuInputManager osuActionInputManager;

View File

@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Origin = Anchor.Centre; Origin = Anchor.Centre;
} }
protected override double MaximumJudgementOffset => DrawableSpinner.HitObject.Duration; public override double MaximumJudgementOffset => DrawableSpinner.HitObject.Duration;
/// <summary> /// <summary>
/// Apply a judgement result. /// Apply a judgement result.

View File

@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Objects
// This is so on repeats ticks don't appear too late to be visually processed by the player. // This is so on repeats ticks don't appear too late to be visually processed by the player.
offset = 200; offset = 200;
else else
offset = TimeFadeIn * 0.66f; offset = TimePreempt * 0.66f;
TimePreempt = (StartTime - SpanStartTime) / 2 + offset; TimePreempt = (StartTime - SpanStartTime) / 2 + offset;
} }

View File

@ -1,20 +1,23 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Scoring namespace osu.Game.Rulesets.Osu.Scoring
{ {
public class OsuHitWindows : HitWindows public class OsuHitWindows : HitWindows
{ {
/// <summary>
/// osu! ruleset has a fixed miss window regardless of difficulty settings.
/// </summary>
public const double MISS_WINDOW = 400;
private static readonly DifficultyRange[] osu_ranges = private static readonly DifficultyRange[] osu_ranges =
{ {
new DifficultyRange(HitResult.Great, 80, 50, 20), new DifficultyRange(HitResult.Great, 80, 50, 20),
new DifficultyRange(HitResult.Ok, 140, 100, 60), new DifficultyRange(HitResult.Ok, 140, 100, 60),
new DifficultyRange(HitResult.Meh, 200, 150, 100), new DifficultyRange(HitResult.Meh, 200, 150, 100),
new DifficultyRange(HitResult.Miss, 400, 400, 400), new DifficultyRange(HitResult.Miss, MISS_WINDOW, MISS_WINDOW, MISS_WINDOW),
}; };
public override bool IsHitResultAllowed(HitResult result) public override bool IsHitResultAllowed(HitResult result)

View File

@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
}; };
accentColour = hitObject.AccentColour.GetBoundCopy(); accentColour = hitObject.AccentColour.GetBoundCopy();
accentColour.BindValueChanged(accent => BorderColour = accent.NewValue); accentColour.BindValueChanged(accent => BorderColour = accent.NewValue, true);
} }
} }
} }

View File

@ -3,6 +3,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using NUnit.Framework; using NUnit.Framework;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects;
@ -129,5 +130,32 @@ namespace osu.Game.Rulesets.Taiko.Tests.Judgements
AssertResult<Hit>(0, HitResult.Miss); AssertResult<Hit>(0, HitResult.Miss);
AssertResult<StrongNestedHitObject>(0, HitResult.IgnoreMiss); AssertResult<StrongNestedHitObject>(0, HitResult.IgnoreMiss);
} }
[Test]
public void TestHighVelocityHit()
{
const double hit_time = 1000;
var beatmap = CreateBeatmap(new Hit
{
Type = HitType.Centre,
StartTime = hit_time,
});
beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 6 });
beatmap.ControlPointInfo.Add(0, new EffectControlPoint { ScrollSpeed = 10 });
var hitWindows = new HitWindows();
hitWindows.SetDifficulty(beatmap.Difficulty.OverallDifficulty);
PerformTest(new List<ReplayFrame>
{
new TaikoReplayFrame(0),
new TaikoReplayFrame(hit_time - hitWindows.WindowFor(HitResult.Great), TaikoAction.LeftCentre),
}, beatmap);
AssertJudgementCount(1);
AssertResult<Hit>(0, HitResult.Ok);
}
} }
} }

View File

@ -20,6 +20,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
private int countOk; private int countOk;
private int countMeh; private int countMeh;
private int countMiss; private int countMiss;
private double accuracy;
private double effectiveMissCount; private double effectiveMissCount;
@ -36,6 +37,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
countOk = score.Statistics.GetValueOrDefault(HitResult.Ok); countOk = score.Statistics.GetValueOrDefault(HitResult.Ok);
countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh); countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh);
countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss); countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss);
accuracy = customAccuracy;
// The effectiveMissCount is calculated by gaining a ratio for totalSuccessfulHits and increasing the miss penalty for shorter object counts lower than 1000. // The effectiveMissCount is calculated by gaining a ratio for totalSuccessfulHits and increasing the miss penalty for shorter object counts lower than 1000.
if (totalSuccessfulHits > 0) if (totalSuccessfulHits > 0)
@ -87,7 +89,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
if (score.Mods.Any(m => m is ModFlashlight<TaikoHitObject>)) if (score.Mods.Any(m => m is ModFlashlight<TaikoHitObject>))
difficultyValue *= 1.050 * lengthBonus; difficultyValue *= 1.050 * lengthBonus;
return difficultyValue * Math.Pow(score.Accuracy, 2.0); return difficultyValue * Math.Pow(accuracy, 2.0);
} }
private double computeAccuracyValue(ScoreInfo score, TaikoDifficultyAttributes attributes) private double computeAccuracyValue(ScoreInfo score, TaikoDifficultyAttributes attributes)
@ -95,7 +97,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
if (attributes.GreatHitWindow <= 0) if (attributes.GreatHitWindow <= 0)
return 0; return 0;
double accuracyValue = Math.Pow(60.0 / attributes.GreatHitWindow, 1.1) * Math.Pow(score.Accuracy, 8.0) * Math.Pow(attributes.StarRating, 0.4) * 27.0; double accuracyValue = Math.Pow(60.0 / attributes.GreatHitWindow, 1.1) * Math.Pow(accuracy, 8.0) * Math.Pow(attributes.StarRating, 0.4) * 27.0;
double lengthBonus = Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3)); double lengthBonus = Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3));
accuracyValue *= lengthBonus; accuracyValue *= lengthBonus;
@ -110,5 +112,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
private int totalHits => countGreat + countOk + countMeh + countMiss; private int totalHits => countGreat + countOk + countMeh + countMiss;
private int totalSuccessfulHits => countGreat + countOk + countMeh; private int totalSuccessfulHits => countGreat + countOk + countMeh;
private double customAccuracy => totalHits > 0 ? (countGreat * 300 + countOk * 150) / (totalHits * 300.0) : 0;
} }
} }

View File

@ -4,7 +4,6 @@
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Layout; using osu.Framework.Layout;
using osu.Game.Configuration;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.UI; using osu.Game.Rulesets.Taiko.UI;
@ -17,22 +16,14 @@ namespace osu.Game.Rulesets.Taiko.Mods
{ {
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1; public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
[SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] public override BindableFloat SizeMultiplier { get; } = new BindableFloat(1)
public override BindableFloat SizeMultiplier { get; } = new BindableFloat
{ {
MinValue = 0.5f, MinValue = 0.5f,
MaxValue = 1.5f, MaxValue = 1.5f,
Default = 1f,
Value = 1f,
Precision = 0.1f Precision = 0.1f
}; };
[SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")] public override BindableBool ComboBasedSize { get; } = new BindableBool(true);
public override BindableBool ComboBasedSize { get; } = new BindableBool
{
Default = true,
Value = true
};
public override float DefaultFlashlightSize => 250; public override float DefaultFlashlightSize => 250;

View File

@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
Filled = HitObject.FirstTick Filled = HitObject.FirstTick
}); });
protected override double MaximumJudgementOffset => HitObject.HitWindow; public override double MaximumJudgementOffset => HitObject.HitWindow;
protected override void CheckForResult(bool userTriggered, double timeOffset) protected override void CheckForResult(bool userTriggered, double timeOffset)
{ {

View File

@ -204,31 +204,23 @@ namespace osu.Game.Tests.Online
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;
[SettingSource("Initial rate", "The starting speed of the track")] [SettingSource("Initial rate", "The starting speed of the track")]
public override BindableNumber<double> InitialRate { get; } = new BindableDouble public override BindableNumber<double> InitialRate { get; } = new BindableDouble(1.5)
{ {
MinValue = 1, MinValue = 1,
MaxValue = 2, MaxValue = 2,
Default = 1.5,
Value = 1.5,
Precision = 0.01, Precision = 0.01,
}; };
[SettingSource("Final rate", "The speed increase to ramp towards")] [SettingSource("Final rate", "The speed increase to ramp towards")]
public override BindableNumber<double> FinalRate { get; } = new BindableDouble public override BindableNumber<double> FinalRate { get; } = new BindableDouble(0.5)
{ {
MinValue = 0, MinValue = 0,
MaxValue = 1, MaxValue = 1,
Default = 0.5,
Value = 0.5,
Precision = 0.01, Precision = 0.01,
}; };
[SettingSource("Adjust pitch", "Should pitch be adjusted with speed")] [SettingSource("Adjust pitch", "Should pitch be adjusted with speed")]
public override BindableBool AdjustPitch { get; } = new BindableBool public override BindableBool AdjustPitch { get; } = new BindableBool(true);
{
Default = true,
Value = true
};
} }
private class TestModDifficultyAdjust : ModDifficultyAdjust private class TestModDifficultyAdjust : ModDifficultyAdjust

View File

@ -124,31 +124,23 @@ namespace osu.Game.Tests.Online
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;
[SettingSource("Initial rate", "The starting speed of the track")] [SettingSource("Initial rate", "The starting speed of the track")]
public override BindableNumber<double> InitialRate { get; } = new BindableDouble public override BindableNumber<double> InitialRate { get; } = new BindableDouble(1.5)
{ {
MinValue = 1, MinValue = 1,
MaxValue = 2, MaxValue = 2,
Default = 1.5,
Value = 1.5,
Precision = 0.01, Precision = 0.01,
}; };
[SettingSource("Final rate", "The speed increase to ramp towards")] [SettingSource("Final rate", "The speed increase to ramp towards")]
public override BindableNumber<double> FinalRate { get; } = new BindableDouble public override BindableNumber<double> FinalRate { get; } = new BindableDouble(0.5)
{ {
MinValue = 0, MinValue = 0,
MaxValue = 1, MaxValue = 1,
Default = 0.5,
Value = 0.5,
Precision = 0.01, Precision = 0.01,
}; };
[SettingSource("Adjust pitch", "Should pitch be adjusted with speed")] [SettingSource("Adjust pitch", "Should pitch be adjusted with speed")]
public override BindableBool AdjustPitch { get; } = new BindableBool public override BindableBool AdjustPitch { get; } = new BindableBool(true);
{
Default = true,
Value = true
};
} }
private class TestModEnum : Mod private class TestModEnum : Mod

View File

@ -8,7 +8,6 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations; using JetBrains.Annotations;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
@ -78,7 +77,7 @@ namespace osu.Game.Tests.Online
} }
}; };
beatmaps.AllowImport = new TaskCompletionSource<bool>(); beatmaps.AllowImport.Reset();
testBeatmapFile = TestResources.GetQuickTestBeatmapForImport(); testBeatmapFile = TestResources.GetQuickTestBeatmapForImport();
@ -132,7 +131,7 @@ namespace osu.Game.Tests.Online
AddStep("finish download", () => ((TestDownloadRequest)beatmapDownloader.GetExistingDownload(testBeatmapSet))!.TriggerSuccess(testBeatmapFile)); AddStep("finish download", () => ((TestDownloadRequest)beatmapDownloader.GetExistingDownload(testBeatmapSet))!.TriggerSuccess(testBeatmapFile));
addAvailabilityCheckStep("state importing", BeatmapAvailability.Importing); addAvailabilityCheckStep("state importing", BeatmapAvailability.Importing);
AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true)); AddStep("allow importing", () => beatmaps.AllowImport.Set());
AddUntilStep("wait for import", () => beatmaps.CurrentImport != null); AddUntilStep("wait for import", () => beatmaps.CurrentImport != null);
AddUntilStep("ensure beatmap available", () => beatmaps.IsAvailableLocally(testBeatmapSet)); AddUntilStep("ensure beatmap available", () => beatmaps.IsAvailableLocally(testBeatmapSet));
addAvailabilityCheckStep("state is locally available", BeatmapAvailability.LocallyAvailable); addAvailabilityCheckStep("state is locally available", BeatmapAvailability.LocallyAvailable);
@ -141,7 +140,7 @@ namespace osu.Game.Tests.Online
[Test] [Test]
public void TestTrackerRespectsSoftDeleting() public void TestTrackerRespectsSoftDeleting()
{ {
AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true)); AddStep("allow importing", () => beatmaps.AllowImport.Set());
AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).WaitSafely()); AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).WaitSafely());
addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable); addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable);
@ -155,7 +154,7 @@ namespace osu.Game.Tests.Online
[Test] [Test]
public void TestTrackerRespectsChecksum() public void TestTrackerRespectsChecksum()
{ {
AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true)); AddStep("allow importing", () => beatmaps.AllowImport.Set());
AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).WaitSafely()); AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).WaitSafely());
addAvailabilityCheckStep("initially locally available", BeatmapAvailability.LocallyAvailable); addAvailabilityCheckStep("initially locally available", BeatmapAvailability.LocallyAvailable);
@ -202,7 +201,7 @@ namespace osu.Game.Tests.Online
private class TestBeatmapManager : BeatmapManager private class TestBeatmapManager : BeatmapManager
{ {
public TaskCompletionSource<bool> AllowImport = new TaskCompletionSource<bool>(); public readonly ManualResetEventSlim AllowImport = new ManualResetEventSlim();
public Live<BeatmapSetInfo> CurrentImport { get; private set; } public Live<BeatmapSetInfo> CurrentImport { get; private set; }
@ -229,7 +228,9 @@ namespace osu.Game.Tests.Online
public override Live<BeatmapSetInfo> ImportModel(BeatmapSetInfo item, ArchiveReader archive = null, bool batchImport = false, CancellationToken cancellationToken = default) public override Live<BeatmapSetInfo> ImportModel(BeatmapSetInfo item, ArchiveReader archive = null, bool batchImport = false, CancellationToken cancellationToken = default)
{ {
testBeatmapManager.AllowImport.Task.WaitSafely(); if (!testBeatmapManager.AllowImport.Wait(TimeSpan.FromSeconds(10), cancellationToken))
throw new TimeoutException("Timeout waiting for import to be allowed.");
return (testBeatmapManager.CurrentImport = base.ImportModel(item, archive, batchImport, cancellationToken)); return (testBeatmapManager.CurrentImport = base.ImportModel(item, archive, batchImport, cancellationToken));
} }
} }

View File

@ -58,6 +58,16 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("wait for some scores not masked away", AddUntilStep("wait for some scores not masked away",
() => leaderboard.ChildrenOfType<GameplayLeaderboardScore>().Any(s => leaderboard.ScreenSpaceDrawQuad.Contains(s.ScreenSpaceDrawQuad.Centre))); () => leaderboard.ChildrenOfType<GameplayLeaderboardScore>().Any(s => leaderboard.ScreenSpaceDrawQuad.Contains(s.ScreenSpaceDrawQuad.Centre)));
AddUntilStep("wait for tracked score fully visible", () => leaderboard.ScreenSpaceDrawQuad.Intersects(leaderboard.TrackedScore!.ScreenSpaceDrawQuad));
AddStep("change score to middle", () => playerScore.Value = 1000000);
AddWaitStep("wait for movement", 5);
AddUntilStep("wait for tracked score fully visible", () => leaderboard.ScreenSpaceDrawQuad.Intersects(leaderboard.TrackedScore!.ScreenSpaceDrawQuad));
AddStep("change score to first", () => playerScore.Value = 5000000);
AddWaitStep("wait for movement", 5);
AddUntilStep("wait for tracked score fully visible", () => leaderboard.ScreenSpaceDrawQuad.Intersects(leaderboard.TrackedScore!.ScreenSpaceDrawQuad));
} }
[Test] [Test]

View File

@ -11,8 +11,10 @@ using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Threading; using osu.Framework.Threading;
using osu.Framework.Timing;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
@ -167,14 +169,39 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("add control points", () => addControlPoints(testControlPoints, Time.Current)); AddStep("add control points", () => addControlPoints(testControlPoints, Time.Current));
} }
private void addHitObject(double time) [Test]
public void TestVeryFlowScroll()
{
const double long_time_range = 100000;
var manualClock = new ManualClock();
AddStep("set manual clock", () =>
{
manualClock.CurrentTime = 0;
scrollContainers.ForEach(c => c.Clock = new FramedClock(manualClock));
setScrollAlgorithm(ScrollVisualisationMethod.Constant);
scrollContainers.ForEach(c => c.TimeRange = long_time_range);
});
AddStep("add hit objects", () =>
{
addHitObject(long_time_range);
addHitObject(long_time_range + 100, 250);
});
AddAssert("hit objects are alive", () => playfields.All(p => p.HitObjectContainer.AliveObjects.Count() == 2));
}
private void addHitObject(double time, float size = 75)
{ {
playfields.ForEach(p => playfields.ForEach(p =>
{ {
var hitObject = new TestDrawableHitObject(time); var hitObject = new TestHitObject(size) { StartTime = time };
setAnchor(hitObject, p); var drawable = new TestDrawableHitObject(hitObject);
p.Add(hitObject); setAnchor(drawable, p);
p.Add(drawable);
}); });
} }
@ -248,6 +275,8 @@ namespace osu.Game.Tests.Visual.Gameplay
} }
}; };
} }
protected override ScrollingHitObjectContainer CreateScrollingHitObjectContainer() => new TestScrollingHitObjectContainer();
} }
private class TestDrawableControlPoint : DrawableHitObject<HitObject> private class TestDrawableControlPoint : DrawableHitObject<HitObject>
@ -281,22 +310,41 @@ namespace osu.Game.Tests.Visual.Gameplay
} }
} }
private class TestDrawableHitObject : DrawableHitObject<HitObject> private class TestHitObject : HitObject
{ {
public TestDrawableHitObject(double time) public readonly float Size;
: base(new HitObject { StartTime = time, HitWindows = HitWindows.Empty })
{
Origin = Anchor.Custom;
OriginPosition = new Vector2(75 / 4.0f);
AutoSizeAxes = Axes.Both; public TestHitObject(float size)
{
Size = size;
}
}
private class TestDrawableHitObject : DrawableHitObject<TestHitObject>
{
public TestDrawableHitObject(TestHitObject hitObject)
: base(hitObject)
{
Origin = Anchor.Centre;
Size = new Vector2(hitObject.Size);
AddInternal(new Box AddInternal(new Box
{ {
Size = new Vector2(75), RelativeSizeAxes = Axes.Both,
Colour = new Color4(RNG.NextSingle(), RNG.NextSingle(), RNG.NextSingle(), 1) Colour = new Color4(RNG.NextSingle(), RNG.NextSingle(), RNG.NextSingle(), 1)
}); });
} }
} }
private class TestScrollingHitObjectContainer : ScrollingHitObjectContainer
{
protected override RectangleF GetConservativeBoundingBox(HitObjectLifetimeEntry entry)
{
if (entry.HitObject is TestHitObject testObject)
return new RectangleF().Inflate(testObject.Size / 2);
return base.GetConservativeBoundingBox(entry);
}
}
} }
} }

View File

@ -9,6 +9,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Configuration;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
@ -24,6 +25,16 @@ namespace osu.Game.Tests.Visual.Gameplay
private readonly BindableList<ScoreInfo> scores = new BindableList<ScoreInfo>(); private readonly BindableList<ScoreInfo> scores = new BindableList<ScoreInfo>();
private readonly Bindable<bool> configVisibility = new Bindable<bool>();
private SoloGameplayLeaderboard leaderboard = null!;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
config.BindWith(OsuSetting.GameplayLeaderboard, configVisibility);
}
[SetUpSteps] [SetUpSteps]
public void SetUpSteps() public void SetUpSteps()
{ {
@ -37,11 +48,12 @@ namespace osu.Game.Tests.Visual.Gameplay
Id = 2, Id = 2,
}; };
Child = new SoloGameplayLeaderboard(trackingUser) Child = leaderboard = new SoloGameplayLeaderboard(trackingUser)
{ {
Scores = { BindTarget = scores }, Scores = { BindTarget = scores },
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
AlwaysVisible = { Value = false },
Expanded = { Value = true }, Expanded = { Value = true },
}; };
}); });
@ -54,7 +66,24 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
AddSliderStep("score", 0, 1000000, 500000, v => scoreProcessor.TotalScore.Value = v); AddSliderStep("score", 0, 1000000, 500000, v => scoreProcessor.TotalScore.Value = v);
AddSliderStep("accuracy", 0f, 1f, 0.5f, v => scoreProcessor.Accuracy.Value = v); AddSliderStep("accuracy", 0f, 1f, 0.5f, v => scoreProcessor.Accuracy.Value = v);
AddSliderStep("combo", 0, 1000, 0, v => scoreProcessor.Combo.Value = v); AddSliderStep("combo", 0, 10000, 0, v => scoreProcessor.HighestCombo.Value = v);
AddStep("toggle expanded", () => leaderboard.Expanded.Value = !leaderboard.Expanded.Value);
}
[Test]
public void TestVisibility()
{
AddStep("set config visible true", () => configVisibility.Value = true);
AddUntilStep("leaderboard visible", () => leaderboard.Alpha == 1);
AddStep("set config visible false", () => configVisibility.Value = false);
AddUntilStep("leaderboard not visible", () => leaderboard.Alpha == 0);
AddStep("set always visible", () => leaderboard.AlwaysVisible.Value = true);
AddUntilStep("leaderboard visible", () => leaderboard.Alpha == 1);
AddStep("set config visible true", () => configVisibility.Value = true);
AddAssert("leaderboard still visible", () => leaderboard.Alpha == 1);
} }
private static List<ScoreInfo> createSampleScores() private static List<ScoreInfo> createSampleScores()

View File

@ -32,6 +32,7 @@ namespace osu.Game.Tests.Visual.Gameplay
protected new OutroPlayer Player => (OutroPlayer)base.Player; protected new OutroPlayer Player => (OutroPlayer)base.Player;
private double currentBeatmapDuration;
private double currentStoryboardDuration; private double currentStoryboardDuration;
private bool showResults = true; private bool showResults = true;
@ -45,7 +46,8 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("enable storyboard", () => LocalConfig.SetValue(OsuSetting.ShowStoryboard, true)); AddStep("enable storyboard", () => LocalConfig.SetValue(OsuSetting.ShowStoryboard, true));
AddStep("set dim level to 0", () => LocalConfig.SetValue<double>(OsuSetting.DimLevel, 0)); AddStep("set dim level to 0", () => LocalConfig.SetValue<double>(OsuSetting.DimLevel, 0));
AddStep("reset fail conditions", () => currentFailConditions = (_, _) => false); AddStep("reset fail conditions", () => currentFailConditions = (_, _) => false);
AddStep("set storyboard duration to 2s", () => currentStoryboardDuration = 2000); AddStep("set beatmap duration to 0s", () => currentBeatmapDuration = 0);
AddStep("set storyboard duration to 8s", () => currentStoryboardDuration = 8000);
AddStep("set ShowResults = true", () => showResults = true); AddStep("set ShowResults = true", () => showResults = true);
} }
@ -151,6 +153,24 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("player exited", () => Stack.CurrentScreen == null); AddAssert("player exited", () => Stack.CurrentScreen == null);
} }
[Test]
public void TestPerformExitAfterOutro()
{
CreateTest(() =>
{
AddStep("set beatmap duration to 4s", () => currentBeatmapDuration = 4000);
AddStep("set storyboard duration to 1s", () => currentStoryboardDuration = 1000);
});
AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.CurrentTime >= currentStoryboardDuration);
AddStep("exit via pause", () => Player.ExitViaPause());
AddAssert("player paused", () => !Player.IsResuming);
AddStep("resume player", () => Player.Resume());
AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value);
AddUntilStep("wait for score shown", () => Player.IsScoreShown);
}
protected override bool AllowFail => true; protected override bool AllowFail => true;
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
@ -160,7 +180,7 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
{ {
var beatmap = new Beatmap(); var beatmap = new Beatmap();
beatmap.HitObjects.Add(new HitCircle()); beatmap.HitObjects.Add(new HitCircle { StartTime = currentBeatmapDuration });
return beatmap; return beatmap;
} }
@ -189,7 +209,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private event Func<HealthProcessor, JudgementResult, bool> failConditions; private event Func<HealthProcessor, JudgementResult, bool> failConditions;
public OutroPlayer(Func<HealthProcessor, JudgementResult, bool> failConditions, bool showResults = true) public OutroPlayer(Func<HealthProcessor, JudgementResult, bool> failConditions, bool showResults = true)
: base(false, showResults) : base(showResults: showResults)
{ {
this.failConditions = failConditions; this.failConditions = failConditions;
} }

View File

@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual.Navigation
[Test] [Test]
public void TestEditDefaultSkin() public void TestEditDefaultSkin()
{ {
AddAssert("is default skin", () => skinManager.CurrentSkinInfo.Value.ID == SkinInfo.TRIANGLES_SKIN); AddAssert("is default skin", () => skinManager.CurrentSkinInfo.Value.ID == SkinInfo.ARGON_SKIN);
AddStep("open settings", () => { Game.Settings.Show(); }); AddStep("open settings", () => { Game.Settings.Show(); });
@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.Navigation
AddStep("open skin editor", () => skinEditor.Show()); AddStep("open skin editor", () => skinEditor.Show());
// Until step required as the skin editor may take time to load (and an extra scheduled frame for the mutable part). // Until step required as the skin editor may take time to load (and an extra scheduled frame for the mutable part).
AddUntilStep("is modified default skin", () => skinManager.CurrentSkinInfo.Value.ID != SkinInfo.TRIANGLES_SKIN); AddUntilStep("is modified default skin", () => skinManager.CurrentSkinInfo.Value.ID != SkinInfo.ARGON_SKIN);
AddAssert("is not protected", () => skinManager.CurrentSkinInfo.Value.PerformRead(s => !s.Protected)); AddAssert("is not protected", () => skinManager.CurrentSkinInfo.Value.PerformRead(s => !s.Protected));
AddUntilStep("export button enabled", () => Game.Settings.ChildrenOfType<SkinSection.ExportSkinButton>().SingleOrDefault()?.Enabled.Value == true); AddUntilStep("export button enabled", () => Game.Settings.ChildrenOfType<SkinSection.ExportSkinButton>().SingleOrDefault()?.Enabled.Value == true);

View File

@ -9,8 +9,10 @@ using NUnit.Framework;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Mania;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Scoring; using osu.Game.Scoring;
@ -92,6 +94,31 @@ namespace osu.Game.Tests.Visual.Navigation
returnToMenu(); returnToMenu();
} }
[Test]
public void TestFromSongSelectWithFilter([Values] ScorePresentType type)
{
AddStep("enter song select", () => Game.ChildrenOfType<ButtonSystem>().Single().OnSolo.Invoke());
AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.BeatmapSetsLoaded);
AddStep("filter to nothing", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).FilterControl.CurrentTextSearch.Value = "fdsajkl;fgewq");
AddUntilStep("wait for no results", () => Beatmap.IsDefault);
var firstImport = importScore(1, new CatchRuleset().RulesetInfo);
presentAndConfirm(firstImport, type);
}
[Test]
public void TestFromSongSelectWithConvertRulesetChange([Values] ScorePresentType type)
{
AddStep("enter song select", () => Game.ChildrenOfType<ButtonSystem>().Single().OnSolo.Invoke());
AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.BeatmapSetsLoaded);
AddStep("set convert to false", () => Game.LocalConfig.SetValue(OsuSetting.ShowConvertedBeatmaps, false));
var firstImport = importScore(1, new CatchRuleset().RulesetInfo);
presentAndConfirm(firstImport, type);
}
[Test] [Test]
public void TestFromSongSelect([Values] ScorePresentType type) public void TestFromSongSelect([Values] ScorePresentType type)
{ {

View File

@ -29,11 +29,7 @@ namespace osu.Game.Tests.Visual.Settings
{ {
Child = textBox = new SettingsTextBox Child = textBox = new SettingsTextBox
{ {
Current = new Bindable<string> Current = new Bindable<string>("test")
{
Default = "test",
Value = "test"
}
}; };
}); });
AddUntilStep("wait for loaded", () => textBox.IsLoaded); AddUntilStep("wait for loaded", () => textBox.IsLoaded);
@ -59,11 +55,7 @@ namespace osu.Game.Tests.Visual.Settings
{ {
Child = textBox = new SettingsTextBox Child = textBox = new SettingsTextBox
{ {
Current = new Bindable<string> Current = new Bindable<string>("test")
{
Default = "test",
Value = "test"
}
}; };
}); });
AddUntilStep("wait for loaded", () => textBox.IsLoaded); AddUntilStep("wait for loaded", () => textBox.IsLoaded);

View File

@ -67,11 +67,7 @@ namespace osu.Game.Tests.Visual.Settings
}; };
[SettingSource("Sample number textbox", "Textbox number entry", SettingControlType = typeof(SettingsNumberBox))] [SettingSource("Sample number textbox", "Textbox number entry", SettingControlType = typeof(SettingsNumberBox))]
public Bindable<int?> IntTextBoxBindable { get; } = new Bindable<int?> public Bindable<int?> IntTextBoxBindable { get; } = new Bindable<int?>();
{
Default = null,
Value = null
};
} }
private enum TestEnum private enum TestEnum

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -36,10 +34,9 @@ namespace osu.Game.Tests.Visual.SongSelect
[Cached(typeof(IDialogOverlay))] [Cached(typeof(IDialogOverlay))]
private readonly DialogOverlay dialogOverlay; private readonly DialogOverlay dialogOverlay;
private ScoreManager scoreManager; private ScoreManager scoreManager = null!;
private RulesetStore rulesetStore = null!;
private RulesetStore rulesetStore; private BeatmapManager beatmapManager = null!;
private BeatmapManager beatmapManager;
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{ {
@ -74,7 +71,7 @@ namespace osu.Game.Tests.Visual.SongSelect
[Test] [Test]
public void TestLocalScoresDisplay() public void TestLocalScoresDisplay()
{ {
BeatmapInfo beatmapInfo = null; BeatmapInfo beatmapInfo = null!;
AddStep(@"Set scope", () => leaderboard.Scope = BeatmapLeaderboardScope.Local); AddStep(@"Set scope", () => leaderboard.Scope = BeatmapLeaderboardScope.Local);
@ -387,7 +384,7 @@ namespace osu.Game.Tests.Visual.SongSelect
private class FailableLeaderboard : BeatmapLeaderboard private class FailableLeaderboard : BeatmapLeaderboard
{ {
public new void SetErrorState(LeaderboardState state) => base.SetErrorState(state); public new void SetErrorState(LeaderboardState state) => base.SetErrorState(state);
public new void SetScores(IEnumerable<ScoreInfo> scores, ScoreInfo userScore = default) => base.SetScores(scores, userScore); public new void SetScores(IEnumerable<ScoreInfo>? scores, ScoreInfo? userScore = null) => base.SetScores(scores, userScore);
} }
} }
} }

View File

@ -163,7 +163,7 @@ namespace osu.Game.Tests.Visual.UserInterface
InputManager.PressButton(MouseButton.Left); InputManager.PressButton(MouseButton.Left);
}); });
AddUntilStep("wait for fetch", () => leaderboard.Scores != null); AddUntilStep("wait for fetch", () => leaderboard.Scores.Any());
AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineID != scoreBeingDeleted.OnlineID)); AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineID != scoreBeingDeleted.OnlineID));
// "Clean up" // "Clean up"
@ -174,7 +174,7 @@ namespace osu.Game.Tests.Visual.UserInterface
public void TestDeleteViaDatabase() public void TestDeleteViaDatabase()
{ {
AddStep("delete top score", () => scoreManager.Delete(importedScores[0])); AddStep("delete top score", () => scoreManager.Delete(importedScores[0]));
AddUntilStep("wait for fetch", () => leaderboard.Scores != null); AddUntilStep("wait for fetch", () => leaderboard.Scores.Any());
AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineID != importedScores[0].OnlineID)); AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineID != importedScores[0].OnlineID));
} }
} }

View File

@ -107,9 +107,9 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep("start drag", () => AddStep("start drag", () =>
{ {
InputManager.MoveMouseTo(notification.ChildrenOfType<Notification>().Single()); InputManager.MoveMouseTo(notificationOverlay.ChildrenOfType<Notification>().Single());
InputManager.PressButton(MouseButton.Left); InputManager.PressButton(MouseButton.Left);
InputManager.MoveMouseTo(notification.ChildrenOfType<Notification>().Single().ScreenSpaceDrawQuad.Centre + new Vector2(-500, 0)); InputManager.MoveMouseTo(notificationOverlay.ChildrenOfType<Notification>().Single().ScreenSpaceDrawQuad.Centre + new Vector2(-500, 0));
}); });
AddStep("fling away", () => AddStep("fling away", () =>
@ -123,6 +123,45 @@ namespace osu.Game.Tests.Visual.UserInterface
AddAssert("unread count zero", () => notificationOverlay.UnreadCount.Value == 0); AddAssert("unread count zero", () => notificationOverlay.UnreadCount.Value == 0);
} }
[Test]
public void TestProgressNotificationCantBeFlung()
{
bool activated = false;
ProgressNotification notification = null!;
AddStep("post", () =>
{
activated = false;
notificationOverlay.Post(notification = new ProgressNotification
{
Text = @"Uploading to BSS...",
CompletionText = "Uploaded to BSS!",
Activated = () => activated = true,
});
progressingNotifications.Add(notification);
});
AddStep("start drag", () =>
{
InputManager.MoveMouseTo(notificationOverlay.ChildrenOfType<Notification>().Single());
InputManager.PressButton(MouseButton.Left);
InputManager.MoveMouseTo(notificationOverlay.ChildrenOfType<Notification>().Single().ScreenSpaceDrawQuad.Centre + new Vector2(-500, 0));
});
AddStep("attempt fling", () =>
{
InputManager.ReleaseButton(MouseButton.Left);
});
AddUntilStep("was not closed", () => !notification.WasClosed);
AddUntilStep("was not cancelled", () => notification.State == ProgressNotificationState.Active);
AddAssert("was not activated", () => !activated);
AddStep("reset mouse position", () => InputManager.MoveMouseTo(Vector2.Zero));
AddUntilStep("was completed", () => notification.State == ProgressNotificationState.Completed);
}
[Test] [Test]
public void TestDismissWithoutActivationCloseButton() public void TestDismissWithoutActivationCloseButton()
{ {
@ -465,7 +504,7 @@ namespace osu.Game.Tests.Visual.UserInterface
{ {
base.Update(); base.Update();
progressingNotifications.RemoveAll(n => n.State == ProgressNotificationState.Completed); progressingNotifications.RemoveAll(n => n.State == ProgressNotificationState.Completed && n.WasClosed);
if (progressingNotifications.Count(n => n.State == ProgressNotificationState.Active) < 3) if (progressingNotifications.Count(n => n.State == ProgressNotificationState.Active) < 3)
{ {

View File

@ -24,7 +24,6 @@ namespace osu.Game.Beatmaps.ControlPoints
public readonly BindableDouble SliderVelocityBindable = new BindableDouble(1) public readonly BindableDouble SliderVelocityBindable = new BindableDouble(1)
{ {
Precision = 0.01, Precision = 0.01,
Default = 1,
MinValue = 0.1, MinValue = 0.1,
MaxValue = 10 MaxValue = 10
}; };

View File

@ -28,7 +28,6 @@ namespace osu.Game.Beatmaps.ControlPoints
public readonly BindableDouble ScrollSpeedBindable = new BindableDouble(1) public readonly BindableDouble ScrollSpeedBindable = new BindableDouble(1)
{ {
Precision = 0.01, Precision = 0.01,
Default = 1,
MinValue = 0.01, MinValue = 0.01,
MaxValue = 10 MaxValue = 10
}; };

View File

@ -45,7 +45,6 @@ namespace osu.Game.Beatmaps.ControlPoints
{ {
MinValue = 0, MinValue = 0,
MaxValue = 100, MaxValue = 100,
Default = 100
}; };
/// <summary> /// <summary>

View File

@ -49,7 +49,6 @@ namespace osu.Game.Beatmaps.ControlPoints
/// </summary> /// </summary>
public readonly BindableDouble BeatLengthBindable = new BindableDouble(DEFAULT_BEAT_LENGTH) public readonly BindableDouble BeatLengthBindable = new BindableDouble(DEFAULT_BEAT_LENGTH)
{ {
Default = DEFAULT_BEAT_LENGTH,
MinValue = 6, MinValue = 6,
MaxValue = 60000 MaxValue = 60000
}; };

View File

@ -39,7 +39,7 @@ namespace osu.Game.Configuration
{ {
// UI/selection defaults // UI/selection defaults
SetDefault(OsuSetting.Ruleset, string.Empty); SetDefault(OsuSetting.Ruleset, string.Empty);
SetDefault(OsuSetting.Skin, SkinInfo.TRIANGLES_SKIN.ToString()); SetDefault(OsuSetting.Skin, SkinInfo.ARGON_SKIN.ToString());
SetDefault(OsuSetting.BeatmapDetailTab, PlayBeatmapDetailArea.TabType.Details); SetDefault(OsuSetting.BeatmapDetailTab, PlayBeatmapDetailArea.TabType.Details);
SetDefault(OsuSetting.BeatmapDetailModsFilter, false); SetDefault(OsuSetting.BeatmapDetailModsFilter, false);
@ -131,6 +131,7 @@ namespace osu.Game.Configuration
SetDefault(OsuSetting.ShowHealthDisplayWhenCantFail, true); SetDefault(OsuSetting.ShowHealthDisplayWhenCantFail, true);
SetDefault(OsuSetting.FadePlayfieldWhenHealthLow, true); SetDefault(OsuSetting.FadePlayfieldWhenHealthLow, true);
SetDefault(OsuSetting.KeyOverlay, false); SetDefault(OsuSetting.KeyOverlay, false);
SetDefault(OsuSetting.GameplayLeaderboard, true);
SetDefault(OsuSetting.AlwaysPlayFirstComboBreak, true); SetDefault(OsuSetting.AlwaysPlayFirstComboBreak, true);
SetDefault(OsuSetting.FloatingComments, false); SetDefault(OsuSetting.FloatingComments, false);
@ -294,6 +295,7 @@ namespace osu.Game.Configuration
LightenDuringBreaks, LightenDuringBreaks,
ShowStoryboard, ShowStoryboard,
KeyOverlay, KeyOverlay,
GameplayLeaderboard,
PositionalHitsounds, PositionalHitsounds,
PositionalHitsoundsLevel, PositionalHitsoundsLevel,
AlwaysPlayFirstComboBreak, AlwaysPlayFirstComboBreak,

View File

@ -89,6 +89,11 @@ namespace osu.Game.Localisation
/// </summary> /// </summary>
public static LocalisableString Collections => new TranslatableString(getKey(@"collections"), @"Collections"); public static LocalisableString Collections => new TranslatableString(getKey(@"collections"), @"Collections");
/// <summary>
/// "Mod presets"
/// </summary>
public static LocalisableString ModPresets => new TranslatableString(getKey(@"mod_presets"), @"Mod presets");
/// <summary> /// <summary>
/// "Name" /// "Name"
/// </summary> /// </summary>

View File

@ -44,11 +44,6 @@ namespace osu.Game.Localisation
/// </summary> /// </summary>
public static LocalisableString ClearAllCaches => new TranslatableString(getKey(@"clear_all_caches"), @"Clear all caches"); public static LocalisableString ClearAllCaches => new TranslatableString(getKey(@"clear_all_caches"), @"Clear all caches");
/// <summary>
/// "Compact realm"
/// </summary>
public static LocalisableString CompactRealm => new TranslatableString(getKey(@"compact_realm"), @"Compact realm");
private static string getKey(string key) => $"{prefix}:{key}"; private static string getKey(string key) => $"{prefix}:{key}";
} }
} }

View File

@ -79,6 +79,11 @@ namespace osu.Game.Localisation
/// </summary> /// </summary>
public static LocalisableString AlwaysShowKeyOverlay => new TranslatableString(getKey(@"key_overlay"), @"Always show key overlay"); public static LocalisableString AlwaysShowKeyOverlay => new TranslatableString(getKey(@"key_overlay"), @"Always show key overlay");
/// <summary>
/// "Always show gameplay leaderboard"
/// </summary>
public static LocalisableString AlwaysShowGameplayLeaderboard => new TranslatableString(getKey(@"gameplay_leaderboard"), @"Always show gameplay leaderboard");
/// <summary> /// <summary>
/// "Always play first combo break sound" /// "Always play first combo break sound"
/// </summary> /// </summary>

View File

@ -64,6 +64,11 @@ namespace osu.Game.Localisation
/// </summary> /// </summary>
public static LocalisableString RunSetupWizard => new TranslatableString(getKey(@"run_setup_wizard"), @"Run setup wizard"); public static LocalisableString RunSetupWizard => new TranslatableString(getKey(@"run_setup_wizard"), @"Run setup wizard");
/// <summary>
/// "You are running the latest release ({0})"
/// </summary>
public static LocalisableString RunningLatestRelease(string version) => new TranslatableString(getKey(@"running_latest_release"), @"You are running the latest release ({0})", version);
private static string getKey(string key) => $"{prefix}:{key}"; private static string getKey(string key) => $"{prefix}:{key}";
} }
} }

View File

@ -19,6 +19,41 @@ namespace osu.Game.Localisation
/// </summary> /// </summary>
public static LocalisableString SelectDirectory => new TranslatableString(getKey(@"select_directory"), @"Select directory"); public static LocalisableString SelectDirectory => new TranslatableString(getKey(@"select_directory"), @"Select directory");
/// <summary>
/// "Migration in progress"
/// </summary>
public static LocalisableString MigrationInProgress => new TranslatableString(getKey(@"migration_in_progress"), @"Migration in progress");
/// <summary>
/// "This could take a few minutes depending on the speed of your disk(s)."
/// </summary>
public static LocalisableString MigrationDescription => new TranslatableString(getKey(@"migration_description"), @"This could take a few minutes depending on the speed of your disk(s).");
/// <summary>
/// "Please avoid interacting with the game!"
/// </summary>
public static LocalisableString ProhibitedInteractDuringMigration => new TranslatableString(getKey(@"prohibited_interact_during_migration"), @"Please avoid interacting with the game!");
/// <summary>
/// "Some files couldn't be cleaned up during migration. Clicking this notification will open the folder so you can manually clean things up."
/// </summary>
public static LocalisableString FailedCleanupNotification => new TranslatableString(getKey(@"failed_cleanup_notification"), @"Some files couldn't be cleaned up during migration. Clicking this notification will open the folder so you can manually clean things up.");
/// <summary>
/// "Please select a new location"
/// </summary>
public static LocalisableString SelectNewLocation => new TranslatableString(getKey(@"select_new_location"), @"Please select a new location");
/// <summary>
/// "The target directory already seems to have an osu! install. Use that data instead?"
/// </summary>
public static LocalisableString TargetDirectoryAlreadyInstalledOsu => new TranslatableString(getKey(@"target_directory_already_installed_osu"), @"The target directory already seems to have an osu! install. Use that data instead?");
/// <summary>
/// "To complete this operation, osu! will close. Please open it again to use the new data location."
/// </summary>
public static LocalisableString RestartAndReOpenRequiredForCompletion => new TranslatableString(getKey(@"restart_and_re_open_required_for_completion"), @"To complete this operation, osu! will close. Please open it again to use the new data location.");
/// <summary> /// <summary>
/// "Import beatmaps from stable" /// "Import beatmaps from stable"
/// </summary> /// </summary>
@ -84,6 +119,26 @@ namespace osu.Game.Localisation
/// </summary> /// </summary>
public static LocalisableString RestoreAllRecentlyDeletedModPresets => new TranslatableString(getKey(@"restore_all_recently_deleted_mod_presets"), @"Restore all recently deleted mod presets"); public static LocalisableString RestoreAllRecentlyDeletedModPresets => new TranslatableString(getKey(@"restore_all_recently_deleted_mod_presets"), @"Restore all recently deleted mod presets");
/// <summary>
/// "Deleted all collections!"
/// </summary>
public static LocalisableString DeletedAllCollections => new TranslatableString(getKey(@"deleted_all_collections"), @"Deleted all collections!");
/// <summary>
/// "Deleted all mod presets!"
/// </summary>
public static LocalisableString DeletedAllModPresets => new TranslatableString(getKey(@"deleted_all_mod_presets"), @"Deleted all mod presets!");
/// <summary>
/// "Restored all deleted mod presets!"
/// </summary>
public static LocalisableString RestoredAllDeletedModPresets => new TranslatableString(getKey(@"restored_all_deleted_mod_presets"), @"Restored all deleted mod presets!");
/// <summary>
/// "Please select your osu!stable install location"
/// </summary>
public static LocalisableString StableDirectorySelectHeader => new TranslatableString(getKey(@"stable_directory_select_header"), @"Please select your osu!stable install location");
private static string getKey(string key) => $"{prefix}:{key}"; private static string getKey(string key) => $"{prefix}:{key}";
} }
} }

View File

@ -19,6 +19,11 @@ namespace osu.Game.Localisation
/// </summary> /// </summary>
public static LocalisableString NoTabletDetected => new TranslatableString(getKey(@"no_tablet_detected"), @"No tablet detected!"); public static LocalisableString NoTabletDetected => new TranslatableString(getKey(@"no_tablet_detected"), @"No tablet detected!");
/// <summary>
/// "If your tablet is not detected, please read [this FAQ]({0}) for troubleshooting steps."
/// </summary>
public static LocalisableString NoTabletDetectedDescription(string url) => new TranslatableString(getKey(@"no_tablet_detected_description"), @"If your tablet is not detected, please read [this FAQ]({0}) for troubleshooting steps.", url);
/// <summary> /// <summary>
/// "Reset to full area" /// "Reset to full area"
/// </summary> /// </summary>

View File

@ -9,27 +9,25 @@ namespace osu.Game.Online.API.Requests
{ {
public class GetBeatmapRequest : APIRequest<APIBeatmap> public class GetBeatmapRequest : APIRequest<APIBeatmap>
{ {
private readonly IBeatmapInfo beatmapInfo; public readonly IBeatmapInfo BeatmapInfo;
public readonly string Filename;
private readonly string filename;
public GetBeatmapRequest(IBeatmapInfo beatmapInfo) public GetBeatmapRequest(IBeatmapInfo beatmapInfo)
{ {
this.beatmapInfo = beatmapInfo; BeatmapInfo = beatmapInfo;
Filename = (beatmapInfo as BeatmapInfo)?.Path ?? string.Empty;
filename = (beatmapInfo as BeatmapInfo)?.Path ?? string.Empty;
} }
protected override WebRequest CreateWebRequest() protected override WebRequest CreateWebRequest()
{ {
var request = base.CreateWebRequest(); var request = base.CreateWebRequest();
if (beatmapInfo.OnlineID > 0) if (BeatmapInfo.OnlineID > 0)
request.AddParameter(@"id", beatmapInfo.OnlineID.ToString()); request.AddParameter(@"id", BeatmapInfo.OnlineID.ToString());
if (!string.IsNullOrEmpty(beatmapInfo.MD5Hash)) if (!string.IsNullOrEmpty(BeatmapInfo.MD5Hash))
request.AddParameter(@"checksum", beatmapInfo.MD5Hash); request.AddParameter(@"checksum", BeatmapInfo.MD5Hash);
if (!string.IsNullOrEmpty(filename)) if (!string.IsNullOrEmpty(Filename))
request.AddParameter(@"filename", filename); request.AddParameter(@"filename", Filename);
return request; return request;
} }

View File

@ -228,7 +228,7 @@ namespace osu.Game.Online.API.Requests.Responses
[JsonProperty(@"rank_history")] [JsonProperty(@"rank_history")]
private APIRankHistory rankHistory private APIRankHistory rankHistory
{ {
set => statistics.RankHistory = value; set => Statistics.RankHistory = value;
} }
[JsonProperty("badges")] [JsonProperty("badges")]

View File

@ -1,14 +1,11 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using JetBrains.Annotations;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Development; using osu.Framework.Development;
@ -54,23 +51,23 @@ namespace osu.Game.Online.Leaderboards
private readonly Container placeholderContainer; private readonly Container placeholderContainer;
private readonly UserTopScoreContainer<TScoreInfo> userScoreContainer; private readonly UserTopScoreContainer<TScoreInfo> userScoreContainer;
private FillFlowContainer<LeaderboardScore> scoreFlowContainer; private FillFlowContainer<LeaderboardScore>? scoreFlowContainer;
private readonly LoadingSpinner loading; private readonly LoadingSpinner loading;
private CancellationTokenSource currentFetchCancellationSource; private CancellationTokenSource? currentFetchCancellationSource;
private CancellationTokenSource currentScoresAsyncLoadCancellationSource; private CancellationTokenSource? currentScoresAsyncLoadCancellationSource;
private APIRequest fetchScoresRequest; private APIRequest? fetchScoresRequest;
private LeaderboardState state; private LeaderboardState state;
[Resolved(CanBeNull = true)] [Resolved(CanBeNull = true)]
private IAPIProvider api { get; set; } private IAPIProvider? api { get; set; }
private readonly IBindable<APIState> apiState = new Bindable<APIState>(); private readonly IBindable<APIState> apiState = new Bindable<APIState>();
private TScope scope; private TScope scope = default!;
public TScope Scope public TScope Scope
{ {
@ -179,16 +176,21 @@ namespace osu.Game.Online.Leaderboards
/// </summary> /// </summary>
/// <param name="scores">The scores to display.</param> /// <param name="scores">The scores to display.</param>
/// <param name="userScore">The user top score, if any.</param> /// <param name="userScore">The user top score, if any.</param>
protected void SetScores(IEnumerable<TScoreInfo> scores, TScoreInfo userScore = default) protected void SetScores(IEnumerable<TScoreInfo>? scores, TScoreInfo? userScore = default)
{ {
this.scores.Clear(); this.scores.Clear();
if (scores != null) if (scores != null)
this.scores.AddRange(scores); this.scores.AddRange(scores);
// Schedule needs to be non-delayed here for the weird logic in refetchScores to work. // Non-delayed schedule may potentially run inline (due to IsMainThread check passing) after leaderboard is disposed.
// If it is removed, the placeholder will be incorrectly updated to "no scores" rather than "retrieving". // This is guarded against in BeatmapLeaderboard via web request cancellation, but let's be extra safe.
// This whole flow should be refactored in the future. if (!IsDisposed)
Scheduler.Add(applyNewScores, false); {
// Schedule needs to be non-delayed here for the weird logic in refetchScores to work.
// If it is removed, the placeholder will be incorrectly updated to "no scores" rather than "retrieving".
// This whole flow should be refactored in the future.
Scheduler.Add(applyNewScores, false);
}
void applyNewScores() void applyNewScores()
{ {
@ -208,8 +210,7 @@ namespace osu.Game.Online.Leaderboards
/// </summary> /// </summary>
/// <param name="cancellationToken"></param> /// <param name="cancellationToken"></param>
/// <returns>An <see cref="APIRequest"/> responsible for the fetch operation. This will be queued and performed automatically.</returns> /// <returns>An <see cref="APIRequest"/> responsible for the fetch operation. This will be queued and performed automatically.</returns>
[CanBeNull] protected abstract APIRequest? FetchScores(CancellationToken cancellationToken);
protected abstract APIRequest FetchScores(CancellationToken cancellationToken);
protected abstract LeaderboardScore CreateDrawableScore(TScoreInfo model, int index); protected abstract LeaderboardScore CreateDrawableScore(TScoreInfo model, int index);
@ -293,7 +294,7 @@ namespace osu.Game.Online.Leaderboards
#region Placeholder handling #region Placeholder handling
private Placeholder placeholder; private Placeholder? placeholder;
private void setState(LeaderboardState state) private void setState(LeaderboardState state)
{ {
@ -320,7 +321,7 @@ namespace osu.Game.Online.Leaderboards
placeholder.FadeInFromZero(fade_duration, Easing.OutQuint); placeholder.FadeInFromZero(fade_duration, Easing.OutQuint);
} }
private Placeholder getPlaceholderFor(LeaderboardState state) private Placeholder? getPlaceholderFor(LeaderboardState state)
{ {
switch (state) switch (state)
{ {

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using System; using System;
using System.Threading; using System.Threading;
using osu.Framework.Bindables; using osu.Framework.Bindables;
@ -18,13 +16,15 @@ namespace osu.Game.Online.Leaderboards
{ {
private const int duration = 500; private const int duration = 500;
public Bindable<TScoreInfo> Score = new Bindable<TScoreInfo>(); public Bindable<TScoreInfo?> Score = new Bindable<TScoreInfo?>();
private readonly Container scoreContainer; private readonly Container scoreContainer;
private readonly Func<TScoreInfo, LeaderboardScore> createScoreDelegate; private readonly Func<TScoreInfo, LeaderboardScore> createScoreDelegate;
protected override bool StartHidden => true; protected override bool StartHidden => true;
private CancellationTokenSource? loadScoreCancellation;
public UserTopScoreContainer(Func<TScoreInfo, LeaderboardScore> createScoreDelegate) public UserTopScoreContainer(Func<TScoreInfo, LeaderboardScore> createScoreDelegate)
{ {
this.createScoreDelegate = createScoreDelegate; this.createScoreDelegate = createScoreDelegate;
@ -65,9 +65,7 @@ namespace osu.Game.Online.Leaderboards
Score.BindValueChanged(onScoreChanged); Score.BindValueChanged(onScoreChanged);
} }
private CancellationTokenSource loadScoreCancellation; private void onScoreChanged(ValueChangedEvent<TScoreInfo?> score)
private void onScoreChanged(ValueChangedEvent<TScoreInfo> score)
{ {
var newScore = score.NewValue; var newScore = score.NewValue;

View File

@ -1,9 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Diagnostics; using System.Diagnostics;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;
using Microsoft.AspNetCore.SignalR.Client; using Microsoft.AspNetCore.SignalR.Client;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
@ -58,7 +58,7 @@ namespace osu.Game.Online.Spectator
{ {
await connection.InvokeAsync(nameof(ISpectatorServer.BeginPlaySession), state); await connection.InvokeAsync(nameof(ISpectatorServer.BeginPlaySession), state);
} }
catch (HubException exception) catch (Exception exception)
{ {
if (exception.GetHubExceptionMessage() == HubClientConnector.SERVER_SHUTDOWN_MESSAGE) if (exception.GetHubExceptionMessage() == HubClientConnector.SERVER_SHUTDOWN_MESSAGE)
{ {

View File

@ -561,9 +561,11 @@ namespace osu.Game
return; return;
} }
// This should be able to be performed from song select, but that is disabled for now
// due to the weird decoupled ruleset logic (which can cause a crash in certain filter scenarios).
PerformFromScreen(screen => PerformFromScreen(screen =>
{ {
Logger.Log($"{nameof(PresentScore)} updating beatmap ({databasedBeatmap}) and ruleset ({databasedScore.ScoreInfo.Ruleset} to match score"); Logger.Log($"{nameof(PresentScore)} updating beatmap ({databasedBeatmap}) and ruleset ({databasedScore.ScoreInfo.Ruleset}) to match score");
Ruleset.Value = databasedScore.ScoreInfo.Ruleset; Ruleset.Value = databasedScore.ScoreInfo.Ruleset;
Beatmap.Value = BeatmapManager.GetWorkingBeatmap(databasedBeatmap); Beatmap.Value = BeatmapManager.GetWorkingBeatmap(databasedBeatmap);
@ -578,7 +580,7 @@ namespace osu.Game
screen.Push(new SoloResultsScreen(databasedScore.ScoreInfo, false)); screen.Push(new SoloResultsScreen(databasedScore.ScoreInfo, false));
break; break;
} }
}, validScreens: new[] { typeof(PlaySongSelect) }); });
} }
public override Task Import(params ImportTask[] imports) public override Task Import(params ImportTask[] imports)

View File

@ -5,6 +5,7 @@
using System; using System;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Resources.Localisation.Web; using osu.Game.Resources.Localisation.Web;
namespace osu.Game.Overlays.Dialog namespace osu.Game.Overlays.Dialog
@ -20,7 +21,7 @@ namespace osu.Game.Overlays.Dialog
/// <param name="message">The description of the action to be displayed to the user.</param> /// <param name="message">The description of the action to be displayed to the user.</param>
/// <param name="onConfirm">An action to perform on confirmation.</param> /// <param name="onConfirm">An action to perform on confirmation.</param>
/// <param name="onCancel">An optional action to perform on cancel.</param> /// <param name="onCancel">An optional action to perform on cancel.</param>
public ConfirmDialog(string message, Action onConfirm, Action onCancel = null) public ConfirmDialog(LocalisableString message, Action onConfirm, Action onCancel = null)
{ {
HeaderText = message; HeaderText = message;
BodyText = "Last chance to turn back"; BodyText = "Last chance to turn back";

View File

@ -123,7 +123,7 @@ namespace osu.Game.Overlays.FirstRunSetup
beatmapSubscription?.Dispose(); beatmapSubscription?.Dispose();
} }
private void beatmapsChanged(IRealmCollection<BeatmapSetInfo> sender, ChangeSet? changes, Exception error) private void beatmapsChanged(IRealmCollection<BeatmapSetInfo> sender, ChangeSet? changes, Exception error) => Schedule(() =>
{ {
currentlyLoadedBeatmaps.Text = FirstRunSetupBeatmapScreenStrings.CurrentlyLoadedBeatmaps(sender.Count); currentlyLoadedBeatmaps.Text = FirstRunSetupBeatmapScreenStrings.CurrentlyLoadedBeatmaps(sender.Count);
@ -139,7 +139,7 @@ namespace osu.Game.Overlays.FirstRunSetup
currentlyLoadedBeatmaps.ScaleTo(1.1f) currentlyLoadedBeatmaps.ScaleTo(1.1f)
.ScaleTo(1, 1500, Easing.OutQuint); .ScaleTo(1, 1500, Easing.OutQuint);
} }
} });
private void downloadTutorial() private void downloadTutorial()
{ {

View File

@ -8,6 +8,7 @@ using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Effects;
@ -24,7 +25,7 @@ namespace osu.Game.Overlays.Music
public class PlaylistOverlay : VisibilityContainer public class PlaylistOverlay : VisibilityContainer
{ {
private const float transition_duration = 600; private const float transition_duration = 600;
private const float playlist_height = 510; public const float PLAYLIST_HEIGHT = 510;
private readonly BindableList<Live<BeatmapSetInfo>> beatmapSets = new BindableList<Live<BeatmapSetInfo>>(); private readonly BindableList<Live<BeatmapSetInfo>> beatmapSets = new BindableList<Live<BeatmapSetInfo>>();
@ -130,7 +131,7 @@ namespace osu.Game.Overlays.Music
filter.Search.HoldFocus = true; filter.Search.HoldFocus = true;
Schedule(() => filter.Search.TakeFocus()); Schedule(() => filter.Search.TakeFocus());
this.ResizeTo(new Vector2(1, playlist_height), transition_duration, Easing.OutQuint); this.ResizeTo(new Vector2(1, RelativeSizeAxes.HasFlagFast(Axes.Y) ? 1f : PLAYLIST_HEIGHT), transition_duration, Easing.OutQuint);
this.FadeIn(transition_duration, Easing.OutQuint); this.FadeIn(transition_duration, Easing.OutQuint);
} }

View File

@ -58,12 +58,11 @@ namespace osu.Game.Overlays
[Resolved] [Resolved]
private RealmAccess realm { get; set; } private RealmAccess realm { get; set; }
[BackgroundDependencyLoader] protected override void LoadComplete()
private void load()
{ {
// Todo: These binds really shouldn't be here, but are unlikely to cause any issues for now. base.LoadComplete();
// They are placed here for now since some tests rely on setting the beatmap _and_ their hierarchies inside their load(), which runs before the MusicController's load().
beatmap.BindValueChanged(beatmapChanged, true); beatmap.BindValueChanged(b => changeBeatmap(b.NewValue), true);
mods.BindValueChanged(_ => ResetTrackAdjustments(), true); mods.BindValueChanged(_ => ResetTrackAdjustments(), true);
} }
@ -263,8 +262,6 @@ namespace osu.Game.Overlays
private IQueryable<BeatmapSetInfo> getBeatmapSets() => realm.Realm.All<BeatmapSetInfo>().Where(s => !s.DeletePending); private IQueryable<BeatmapSetInfo> getBeatmapSets() => realm.Realm.All<BeatmapSetInfo>().Where(s => !s.DeletePending);
private void beatmapChanged(ValueChangedEvent<WorkingBeatmap> beatmap) => changeBeatmap(beatmap.NewValue);
private void changeBeatmap(WorkingBeatmap newWorking) private void changeBeatmap(WorkingBeatmap newWorking)
{ {
// This method can potentially be triggered multiple times as it is eagerly fired in next() / prev() to ensure correct execution order // This method can potentially be triggered multiple times as it is eagerly fired in next() / prev() to ensure correct execution order

View File

@ -113,9 +113,12 @@ namespace osu.Game.Overlays
if (enabled) if (enabled)
// we want a slight delay before toggling notifications on to avoid the user becoming overwhelmed. // we want a slight delay before toggling notifications on to avoid the user becoming overwhelmed.
notificationsEnabler = Scheduler.AddDelayed(() => processingPosts = true, State.Value == Visibility.Visible ? 0 : 100); notificationsEnabler = Scheduler.AddDelayed(() => processingPosts = true, State.Value == Visibility.Visible ? 0 : 250);
else else
{
processingPosts = false; processingPosts = false;
toastTray.FlushAllToasts();
}
} }
protected override void LoadComplete() protected override void LoadComplete()

View File

@ -68,6 +68,8 @@ namespace osu.Game.Overlays.Notifications
public virtual bool Read { get; set; } public virtual bool Read { get; set; }
protected virtual bool AllowFlingDismiss => true;
public new bool IsDragged => dragContainer.IsDragged; public new bool IsDragged => dragContainer.IsDragged;
protected virtual IconUsage CloseButtonIcon => FontAwesome.Solid.Check; protected virtual IconUsage CloseButtonIcon => FontAwesome.Solid.Check;
@ -315,7 +317,7 @@ namespace osu.Game.Overlays.Notifications
protected override void OnDragEnd(DragEndEvent e) protected override void OnDragEnd(DragEndEvent e)
{ {
if (Rotation < -10 || velocity.X < -0.3f) if (notification.AllowFlingDismiss && (Rotation < -10 || velocity.X < -0.3f))
notification.Close(true); notification.Close(true);
else if (X > 30 || velocity.X > 0.3f) else if (X > 30 || velocity.X > 0.3f)
notification.ForwardToOverlay?.Invoke(); notification.ForwardToOverlay?.Invoke();

View File

@ -25,6 +25,8 @@ namespace osu.Game.Overlays.Notifications
public Func<bool>? CancelRequested { get; set; } public Func<bool>? CancelRequested { get; set; }
protected override bool AllowFlingDismiss => false;
/// <summary> /// <summary>
/// The function to post completion notifications back to. /// The function to post completion notifications back to.
/// </summary> /// </summary>

View File

@ -38,6 +38,7 @@ namespace osu.Game.Overlays
private const float transition_length = 800; private const float transition_length = 800;
private const float progress_height = 10; private const float progress_height = 10;
private const float bottom_black_area_height = 55; private const float bottom_black_area_height = 55;
private const float margin = 10;
private Drawable background; private Drawable background;
private ProgressBar progressBar; private ProgressBar progressBar;
@ -53,6 +54,7 @@ namespace osu.Game.Overlays
private Container dragContainer; private Container dragContainer;
private Container playerContainer; private Container playerContainer;
private Container playlistContainer;
protected override string PopInSampleName => "UI/now-playing-pop-in"; protected override string PopInSampleName => "UI/now-playing-pop-in";
protected override string PopOutSampleName => "UI/now-playing-pop-out"; protected override string PopOutSampleName => "UI/now-playing-pop-out";
@ -69,7 +71,7 @@ namespace osu.Game.Overlays
public NowPlayingOverlay() public NowPlayingOverlay()
{ {
Width = 400; Width = 400;
Margin = new MarginPadding(10); Margin = new MarginPadding(margin);
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -82,7 +84,6 @@ namespace osu.Game.Overlays
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new Drawable[] Children = new Drawable[]
{ {
playerContainer = new Container playerContainer = new Container
@ -182,8 +183,13 @@ namespace osu.Game.Overlays
} }
}, },
}, },
playlistContainer = new Container
{
RelativeSizeAxes = Axes.X,
Y = player_height + margin,
}
} }
} },
}; };
} }
@ -193,11 +199,10 @@ namespace osu.Game.Overlays
{ {
LoadComponentAsync(playlist = new PlaylistOverlay LoadComponentAsync(playlist = new PlaylistOverlay
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.Both,
Y = player_height + 10,
}, _ => }, _ =>
{ {
dragContainer.Add(playlist); playlistContainer.Add(playlist);
playlist.State.BindValueChanged(s => playlistButton.FadeColour(s.NewValue == Visibility.Visible ? colours.Yellow : Color4.White, 200, Easing.OutQuint), true); playlist.State.BindValueChanged(s => playlistButton.FadeColour(s.NewValue == Visibility.Visible ? colours.Yellow : Color4.White, 200, Easing.OutQuint), true);
@ -242,7 +247,18 @@ namespace osu.Game.Overlays
{ {
base.UpdateAfterChildren(); base.UpdateAfterChildren();
Height = dragContainer.Height; playlistContainer.Height = MathF.Min(Parent.DrawHeight - margin * 3 - player_height, PlaylistOverlay.PLAYLIST_HEIGHT);
float height = player_height;
if (playlist != null)
{
height += playlist.DrawHeight;
if (playlist.State.Value == Visibility.Visible)
height += margin;
}
Height = dragContainer.Height = height;
} }
protected override void Update() protected override void Update()

View File

@ -35,7 +35,7 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings
}, },
new SettingsButton new SettingsButton
{ {
Text = DebugSettingsStrings.CompactRealm, Text = "Compact realm",
Action = () => Action = () =>
{ {
// Blocking operations implicitly causes a Compact(). // Blocking operations implicitly causes a Compact().

View File

@ -38,6 +38,11 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
Current = config.GetBindable<bool>(OsuSetting.KeyOverlay), Current = config.GetBindable<bool>(OsuSetting.KeyOverlay),
Keywords = new[] { "counter" }, Keywords = new[] { "counter" },
}, },
new SettingsCheckbox
{
LabelText = GameplaySettingsStrings.AlwaysShowGameplayLeaderboard,
Current = config.GetBindable<bool>(OsuSetting.GameplayLeaderboard),
},
}; };
} }
} }

View File

@ -54,7 +54,7 @@ namespace osu.Game.Overlays.Settings.Sections.General
{ {
notifications?.Post(new SimpleNotification notifications?.Post(new SimpleNotification
{ {
Text = $"You are running the latest release ({game.Version})", Text = GeneralSettingsStrings.RunningLatestRelease(game.Version),
Icon = FontAwesome.Solid.CheckCircle, Icon = FontAwesome.Solid.CheckCircle,
}); });
} }

View File

@ -73,8 +73,8 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
windowModes.BindTo(host.Window.SupportedWindowModes); windowModes.BindTo(host.Window.SupportedWindowModes);
} }
if (host.Window is WindowsWindow windowsWindow) if (host.Renderer is IWindowsRenderer windowsRenderer)
fullscreenCapability.BindTo(windowsWindow.FullscreenCapability); fullscreenCapability.BindTo(windowsRenderer.FullscreenCapability);
Children = new Drawable[] Children = new Drawable[]
{ {

View File

@ -72,7 +72,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours, LocalisationManager localisation)
{ {
Children = new Drawable[] Children = new Drawable[]
{ {
@ -110,11 +110,10 @@ namespace osu.Game.Overlays.Settings.Sections.Input
if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows || RuntimeInfo.OS == RuntimeInfo.Platform.Linux) if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows || RuntimeInfo.OS == RuntimeInfo.Platform.Linux)
{ {
t.NewLine(); t.NewLine();
t.AddText("If your tablet is not detected, please read "); var formattedSource = MessageFormatter.FormatText(localisation.GetLocalisedBindableString(TabletSettingsStrings.NoTabletDetectedDescription(RuntimeInfo.OS == RuntimeInfo.Platform.Windows
t.AddLink("this FAQ", LinkAction.External, RuntimeInfo.OS == RuntimeInfo.Platform.Windows
? @"https://opentabletdriver.net/Wiki/FAQ/Windows" ? @"https://opentabletdriver.net/Wiki/FAQ/Windows"
: @"https://opentabletdriver.net/Wiki/FAQ/Linux"); : @"https://opentabletdriver.net/Wiki/FAQ/Linux")).Value);
t.AddText(" for troubleshooting steps."); t.AddLinks(formattedSource.Text, formattedSource.Links);
} }
}), }),
} }
@ -215,21 +214,21 @@ namespace osu.Game.Overlays.Settings.Sections.Input
rotation.BindTo(tabletHandler.Rotation); rotation.BindTo(tabletHandler.Rotation);
areaOffset.BindTo(tabletHandler.AreaOffset); areaOffset.BindTo(tabletHandler.AreaOffset);
areaOffset.BindValueChanged(val => areaOffset.BindValueChanged(val => Schedule(() =>
{ {
offsetX.Value = val.NewValue.X; offsetX.Value = val.NewValue.X;
offsetY.Value = val.NewValue.Y; offsetY.Value = val.NewValue.Y;
}, true); }), true);
offsetX.BindValueChanged(val => areaOffset.Value = new Vector2(val.NewValue, areaOffset.Value.Y)); offsetX.BindValueChanged(val => areaOffset.Value = new Vector2(val.NewValue, areaOffset.Value.Y));
offsetY.BindValueChanged(val => areaOffset.Value = new Vector2(areaOffset.Value.X, val.NewValue)); offsetY.BindValueChanged(val => areaOffset.Value = new Vector2(areaOffset.Value.X, val.NewValue));
areaSize.BindTo(tabletHandler.AreaSize); areaSize.BindTo(tabletHandler.AreaSize);
areaSize.BindValueChanged(val => areaSize.BindValueChanged(val => Schedule(() =>
{ {
sizeX.Value = val.NewValue.X; sizeX.Value = val.NewValue.X;
sizeY.Value = val.NewValue.Y; sizeY.Value = val.NewValue.Y;
}, true); }), true);
sizeX.BindValueChanged(val => sizeX.BindValueChanged(val =>
{ {
@ -255,7 +254,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
}); });
tablet.BindTo(tabletHandler.Tablet); tablet.BindTo(tabletHandler.Tablet);
tablet.BindValueChanged(val => tablet.BindValueChanged(val => Schedule(() =>
{ {
Scheduler.AddOnce(updateVisibility); Scheduler.AddOnce(updateVisibility);
@ -274,7 +273,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
sizeY.Default = sizeY.MaxValue = tab.Size.Y; sizeY.Default = sizeY.MaxValue = tab.Size.Y;
areaSize.Default = new Vector2(sizeX.Default, sizeY.Default); areaSize.Default = new Vector2(sizeX.Default, sizeY.Default);
}, true); }), true);
} }
private void updateVisibility() private void updateVisibility()

View File

@ -13,7 +13,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
{ {
public class BeatmapSettings : SettingsSubsection public class BeatmapSettings : SettingsSubsection
{ {
protected override LocalisableString Header => "Beatmaps"; protected override LocalisableString Header => CommonStrings.Beatmaps;
private SettingsButton importBeatmapsButton = null!; private SettingsButton importBeatmapsButton = null!;
private SettingsButton deleteBeatmapsButton = null!; private SettingsButton deleteBeatmapsButton = null!;

View File

@ -12,7 +12,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
{ {
public class CollectionsSettings : SettingsSubsection public class CollectionsSettings : SettingsSubsection
{ {
protected override LocalisableString Header => "Collections"; protected override LocalisableString Header => CommonStrings.Collections;
private SettingsButton importCollectionsButton = null!; private SettingsButton importCollectionsButton = null!;
@ -51,7 +51,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
private void deleteAllCollections() private void deleteAllCollections()
{ {
realm.Write(r => r.RemoveAll<BeatmapCollection>()); realm.Write(r => r.RemoveAll<BeatmapCollection>());
notificationOverlay?.Post(new ProgressCompletionNotification { Text = "Deleted all collections!" }); notificationOverlay?.Post(new ProgressCompletionNotification { Text = MaintenanceSettingsStrings.DeletedAllCollections });
} }
} }
} }

View File

@ -15,6 +15,7 @@ using osu.Framework.Screens;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Localisation;
using osu.Game.Overlays.Notifications; using osu.Game.Overlays.Notifications;
using osu.Game.Screens; using osu.Game.Screens;
using osuTK; using osuTK;
@ -71,14 +72,14 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Text = "Migration in progress", Text = MaintenanceSettingsStrings.MigrationInProgress,
Font = OsuFont.Default.With(size: 40) Font = OsuFont.Default.With(size: 40)
}, },
new OsuSpriteText new OsuSpriteText
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Text = "This could take a few minutes depending on the speed of your disk(s).", Text = MaintenanceSettingsStrings.MigrationDescription,
Font = OsuFont.Default.With(size: 30) Font = OsuFont.Default.With(size: 30)
}, },
new LoadingSpinner(true) new LoadingSpinner(true)
@ -89,7 +90,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Text = "Please avoid interacting with the game!", Text = MaintenanceSettingsStrings.ProhibitedInteractDuringMigration,
Font = OsuFont.Default.With(size: 30) Font = OsuFont.Default.With(size: 30)
}, },
} }
@ -111,7 +112,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
{ {
notifications.Post(new SimpleNotification notifications.Post(new SimpleNotification
{ {
Text = "Some files couldn't be cleaned up during migration. Clicking this notification will open the folder so you can manually clean things up.", Text = MaintenanceSettingsStrings.FailedCleanupNotification,
Activated = () => Activated = () =>
{ {
originalStorage.PresentExternally(); originalStorage.PresentExternally();

View File

@ -12,6 +12,7 @@ using osu.Framework.Logging;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Game.IO; using osu.Game.IO;
using osu.Game.Localisation;
using osu.Game.Overlays.Dialog; using osu.Game.Overlays.Dialog;
namespace osu.Game.Overlays.Settings.Sections.Maintenance namespace osu.Game.Overlays.Settings.Sections.Maintenance
@ -35,7 +36,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
public override bool HideOverlaysOnEnter => true; public override bool HideOverlaysOnEnter => true;
public override LocalisableString HeaderText => "Please select a new location"; public override LocalisableString HeaderText => MaintenanceSettingsStrings.SelectNewLocation;
protected override void OnSelection(DirectoryInfo directory) protected override void OnSelection(DirectoryInfo directory)
{ {
@ -51,9 +52,9 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
// Quick test for whether there's already an osu! install at the target path. // Quick test for whether there's already an osu! install at the target path.
if (fileInfos.Any(f => f.Name == OsuGameBase.CLIENT_DATABASE_FILENAME)) if (fileInfos.Any(f => f.Name == OsuGameBase.CLIENT_DATABASE_FILENAME))
{ {
dialogOverlay.Push(new ConfirmDialog("The target directory already seems to have an osu! install. Use that data instead?", () => dialogOverlay.Push(new ConfirmDialog(MaintenanceSettingsStrings.TargetDirectoryAlreadyInstalledOsu, () =>
{ {
dialogOverlay.Push(new ConfirmDialog("To complete this operation, osu! will close. Please open it again to use the new data location.", () => dialogOverlay.Push(new ConfirmDialog(MaintenanceSettingsStrings.RestartAndReOpenRequiredForCompletion, () =>
{ {
(storage as OsuStorage)?.ChangeDataPath(target.FullName); (storage as OsuStorage)?.ChangeDataPath(target.FullName);
game.Exit(); game.Exit();

View File

@ -16,7 +16,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
{ {
public class ModPresetSettings : SettingsSubsection public class ModPresetSettings : SettingsSubsection
{ {
protected override LocalisableString Header => "Mod presets"; protected override LocalisableString Header => CommonStrings.ModPresets;
[Resolved] [Resolved]
private RealmAccess realm { get; set; } = null!; private RealmAccess realm { get; set; } = null!;
@ -64,7 +64,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
deleteAllButton.Enabled.Value = true; deleteAllButton.Enabled.Value = true;
if (deletionTask.IsCompletedSuccessfully) if (deletionTask.IsCompletedSuccessfully)
notificationOverlay?.Post(new ProgressCompletionNotification { Text = "Deleted all mod presets!" }); notificationOverlay?.Post(new ProgressCompletionNotification { Text = MaintenanceSettingsStrings.DeletedAllModPresets });
else if (deletionTask.IsFaulted) else if (deletionTask.IsFaulted)
Logger.Error(deletionTask.Exception, "Failed to delete all mod presets"); Logger.Error(deletionTask.Exception, "Failed to delete all mod presets");
} }
@ -81,7 +81,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
undeleteButton.Enabled.Value = true; undeleteButton.Enabled.Value = true;
if (undeletionTask.IsCompletedSuccessfully) if (undeletionTask.IsCompletedSuccessfully)
notificationOverlay?.Post(new ProgressCompletionNotification { Text = "Restored all deleted mod presets!" }); notificationOverlay?.Post(new ProgressCompletionNotification { Text = MaintenanceSettingsStrings.RestoredAllDeletedModPresets });
else if (undeletionTask.IsFaulted) else if (undeletionTask.IsFaulted)
Logger.Error(undeletionTask.Exception, "Failed to restore mod presets"); Logger.Error(undeletionTask.Exception, "Failed to restore mod presets");
} }

View File

@ -12,7 +12,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
{ {
public class ScoreSettings : SettingsSubsection public class ScoreSettings : SettingsSubsection
{ {
protected override LocalisableString Header => "Scores"; protected override LocalisableString Header => CommonStrings.Scores;
private SettingsButton importScoresButton = null!; private SettingsButton importScoresButton = null!;
private SettingsButton deleteScoresButton = null!; private SettingsButton deleteScoresButton = null!;

View File

@ -12,7 +12,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
{ {
public class SkinSettings : SettingsSubsection public class SkinSettings : SettingsSubsection
{ {
protected override LocalisableString Header => "Skins"; protected override LocalisableString Header => CommonStrings.Skins;
private SettingsButton importSkinsButton = null!; private SettingsButton importSkinsButton = null!;
private SettingsButton deleteSkinsButton = null!; private SettingsButton deleteSkinsButton = null!;

View File

@ -58,6 +58,9 @@ namespace osu.Game.Rulesets.Configuration
pendingWrites.Clear(); pendingWrites.Clear();
} }
if (!changed.Any())
return true;
realm?.Write(r => realm?.Write(r =>
{ {
foreach (var c in changed) foreach (var c in changed)

View File

@ -36,32 +36,24 @@ namespace osu.Game.Rulesets.Mods
public override Type[] IncompatibleMods => new[] { typeof(ModRateAdjust), typeof(ModTimeRamp), typeof(ModAutoplay) }; public override Type[] IncompatibleMods => new[] { typeof(ModRateAdjust), typeof(ModTimeRamp), typeof(ModAutoplay) };
[SettingSource("Initial rate", "The starting speed of the track")] [SettingSource("Initial rate", "The starting speed of the track")]
public BindableNumber<double> InitialRate { get; } = new BindableDouble public BindableNumber<double> InitialRate { get; } = new BindableDouble(1)
{ {
MinValue = 0.5, MinValue = 0.5,
MaxValue = 2, MaxValue = 2,
Default = 1,
Value = 1,
Precision = 0.01 Precision = 0.01
}; };
[SettingSource("Adjust pitch", "Should pitch be adjusted with speed")] [SettingSource("Adjust pitch", "Should pitch be adjusted with speed")]
public BindableBool AdjustPitch { get; } = new BindableBool public BindableBool AdjustPitch { get; } = new BindableBool(true);
{
Default = true,
Value = true
};
/// <summary> /// <summary>
/// The instantaneous rate of the track. /// The instantaneous rate of the track.
/// Every frame this mod will attempt to smoothly adjust this to meet <see cref="targetRate"/>. /// Every frame this mod will attempt to smoothly adjust this to meet <see cref="targetRate"/>.
/// </summary> /// </summary>
public BindableNumber<double> SpeedChange { get; } = new BindableDouble public BindableNumber<double> SpeedChange { get; } = new BindableDouble(1)
{ {
MinValue = min_allowable_rate, MinValue = min_allowable_rate,
MaxValue = max_allowable_rate, MaxValue = max_allowable_rate,
Default = 1,
Value = 1
}; };
// The two constants below denote the maximum allowable range of rates that `SpeedChange` can take. // The two constants below denote the maximum allowable range of rates that `SpeedChange` can take.

View File

@ -18,12 +18,10 @@ namespace osu.Game.Rulesets.Mods
public override LocalisableString Description => "Zoooooooooom..."; public override LocalisableString Description => "Zoooooooooom...";
[SettingSource("Speed increase", "The actual increase to apply")] [SettingSource("Speed increase", "The actual increase to apply")]
public override BindableNumber<double> SpeedChange { get; } = new BindableDouble public override BindableNumber<double> SpeedChange { get; } = new BindableDouble(1.5)
{ {
MinValue = 1.01, MinValue = 1.01,
MaxValue = 2, MaxValue = 2,
Default = 1.5,
Value = 1.5,
Precision = 0.01, Precision = 0.01,
}; };
} }

View File

@ -18,12 +18,10 @@ namespace osu.Game.Rulesets.Mods
public override LocalisableString Description => "Less zoom..."; public override LocalisableString Description => "Less zoom...";
[SettingSource("Speed decrease", "The actual decrease to apply")] [SettingSource("Speed decrease", "The actual decrease to apply")]
public override BindableNumber<double> SpeedChange { get; } = new BindableDouble public override BindableNumber<double> SpeedChange { get; } = new BindableDouble(0.75)
{ {
MinValue = 0.5, MinValue = 0.5,
MaxValue = 0.99, MaxValue = 0.99,
Default = 0.75,
Value = 0.75,
Precision = 0.01, Precision = 0.01,
}; };
} }

View File

@ -36,34 +36,20 @@ namespace osu.Game.Rulesets.Mods
private readonly BindableNumber<int> currentCombo = new BindableInt(); private readonly BindableNumber<int> currentCombo = new BindableInt();
[SettingSource("Enable metronome", "Add a metronome beat to help you keep track of the rhythm.")] [SettingSource("Enable metronome", "Add a metronome beat to help you keep track of the rhythm.")]
public BindableBool EnableMetronome { get; } = new BindableBool public BindableBool EnableMetronome { get; } = new BindableBool(true);
{
Default = true,
Value = true
};
[SettingSource("Final volume at combo", "The combo count at which point the track reaches its final volume.", SettingControlType = typeof(SettingsSlider<int, MuteComboSlider>))] [SettingSource("Final volume at combo", "The combo count at which point the track reaches its final volume.", SettingControlType = typeof(SettingsSlider<int, MuteComboSlider>))]
public BindableInt MuteComboCount { get; } = new BindableInt public BindableInt MuteComboCount { get; } = new BindableInt(100)
{ {
Default = 100,
Value = 100,
MinValue = 0, MinValue = 0,
MaxValue = 500, MaxValue = 500,
}; };
[SettingSource("Start muted", "Increase volume as combo builds.")] [SettingSource("Start muted", "Increase volume as combo builds.")]
public BindableBool InverseMuting { get; } = new BindableBool public BindableBool InverseMuting { get; } = new BindableBool();
{
Default = false,
Value = false
};
[SettingSource("Mute hit sounds", "Hit sounds are also muted alongside the track.")] [SettingSource("Mute hit sounds", "Hit sounds are also muted alongside the track.")]
public BindableBool AffectsHitSounds { get; } = new BindableBool public BindableBool AffectsHitSounds { get; } = new BindableBool(true);
{
Default = true,
Value = true
};
protected ModMuted() protected ModMuted()
{ {

View File

@ -6,7 +6,9 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring; using osu.Game.Scoring;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
@ -34,6 +36,11 @@ namespace osu.Game.Rulesets.Mods
protected float ComboBasedAlpha; protected float ComboBasedAlpha;
[SettingSource(
"Hidden at combo",
"The combo count at which the cursor becomes completely hidden",
SettingControlType = typeof(SettingsSlider<int, HiddenComboSlider>)
)]
public abstract BindableInt HiddenComboCount { get; } public abstract BindableInt HiddenComboCount { get; }
public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank; public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank;

View File

@ -18,10 +18,6 @@ namespace osu.Game.Rulesets.Mods
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;
[SettingSource("Seed", "Use a custom seed instead of a random one", SettingControlType = typeof(SettingsNumberBox))] [SettingSource("Seed", "Use a custom seed instead of a random one", SettingControlType = typeof(SettingsNumberBox))]
public Bindable<int?> Seed { get; } = new Bindable<int?> public Bindable<int?> Seed { get; } = new Bindable<int?>();
{
Default = null,
Value = null
};
} }
} }

View File

@ -39,10 +39,8 @@ namespace osu.Game.Rulesets.Mods
private double finalRateTime; private double finalRateTime;
private double beginRampTime; private double beginRampTime;
public BindableNumber<double> SpeedChange { get; } = new BindableDouble public BindableNumber<double> SpeedChange { get; } = new BindableDouble(1)
{ {
Default = 1,
Value = 1,
Precision = 0.01, Precision = 0.01,
}; };

View File

@ -6,7 +6,6 @@ using System.Linq;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Configuration;
namespace osu.Game.Rulesets.Mods namespace osu.Game.Rulesets.Mods
{ {
@ -17,32 +16,21 @@ namespace osu.Game.Rulesets.Mods
public override LocalisableString Description => "Sloooow doooown..."; public override LocalisableString Description => "Sloooow doooown...";
public override IconUsage? Icon => FontAwesome.Solid.ChevronCircleDown; public override IconUsage? Icon => FontAwesome.Solid.ChevronCircleDown;
[SettingSource("Initial rate", "The starting speed of the track")] public override BindableNumber<double> InitialRate { get; } = new BindableDouble(1)
public override BindableNumber<double> InitialRate { get; } = new BindableDouble
{ {
MinValue = 0.51, MinValue = 0.51,
MaxValue = 2, MaxValue = 2,
Default = 1,
Value = 1,
Precision = 0.01, Precision = 0.01,
}; };
[SettingSource("Final rate", "The speed increase to ramp towards")] public override BindableNumber<double> FinalRate { get; } = new BindableDouble(0.75)
public override BindableNumber<double> FinalRate { get; } = new BindableDouble
{ {
MinValue = 0.5, MinValue = 0.5,
MaxValue = 1.99, MaxValue = 1.99,
Default = 0.75,
Value = 0.75,
Precision = 0.01, Precision = 0.01,
}; };
[SettingSource("Adjust pitch", "Should pitch be adjusted with speed")] public override BindableBool AdjustPitch { get; } = new BindableBool(true);
public override BindableBool AdjustPitch { get; } = new BindableBool
{
Default = true,
Value = true
};
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModWindUp)).ToArray(); public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModWindUp)).ToArray();

View File

@ -6,7 +6,6 @@ using System.Linq;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Configuration;
namespace osu.Game.Rulesets.Mods namespace osu.Game.Rulesets.Mods
{ {
@ -17,32 +16,21 @@ namespace osu.Game.Rulesets.Mods
public override LocalisableString Description => "Can you keep up?"; public override LocalisableString Description => "Can you keep up?";
public override IconUsage? Icon => FontAwesome.Solid.ChevronCircleUp; public override IconUsage? Icon => FontAwesome.Solid.ChevronCircleUp;
[SettingSource("Initial rate", "The starting speed of the track")] public override BindableNumber<double> InitialRate { get; } = new BindableDouble(1)
public override BindableNumber<double> InitialRate { get; } = new BindableDouble
{ {
MinValue = 0.5, MinValue = 0.5,
MaxValue = 1.99, MaxValue = 1.99,
Default = 1,
Value = 1,
Precision = 0.01, Precision = 0.01,
}; };
[SettingSource("Final rate", "The speed increase to ramp towards")] public override BindableNumber<double> FinalRate { get; } = new BindableDouble(1.5)
public override BindableNumber<double> FinalRate { get; } = new BindableDouble
{ {
MinValue = 0.51, MinValue = 0.51,
MaxValue = 2, MaxValue = 2,
Default = 1.5,
Value = 1.5,
Precision = 0.01, Precision = 0.01,
}; };
[SettingSource("Adjust pitch", "Should pitch be adjusted with speed")] public override BindableBool AdjustPitch { get; } = new BindableBool(true);
public override BindableBool AdjustPitch { get; } = new BindableBool
{
Default = true,
Value = true
};
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModWindDown)).ToArray(); public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModWindDown)).ToArray();

View File

@ -651,7 +651,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
/// <remarks> /// <remarks>
/// This does not affect the time offset provided to invocations of <see cref="CheckForResult"/>. /// This does not affect the time offset provided to invocations of <see cref="CheckForResult"/>.
/// </remarks> /// </remarks>
protected virtual double MaximumJudgementOffset => HitObject.HitWindows?.WindowFor(HitResult.Miss) ?? 0; public virtual double MaximumJudgementOffset => HitObject.HitWindows?.WindowFor(HitResult.Miss) ?? 0;
/// <summary> /// <summary>
/// Applies the <see cref="Result"/> of this <see cref="DrawableHitObject"/>, notifying responders such as /// Applies the <see cref="Result"/> of this <see cref="DrawableHitObject"/>, notifying responders such as

View File

@ -60,7 +60,6 @@ namespace osu.Game.Rulesets.UI.Scrolling
/// </summary> /// </summary>
protected readonly BindableDouble TimeRange = new BindableDouble(time_span_default) protected readonly BindableDouble TimeRange = new BindableDouble(time_span_default)
{ {
Default = time_span_default,
MinValue = time_span_min, MinValue = time_span_min,
MaxValue = time_span_max MaxValue = time_span_max
}; };

View File

@ -3,11 +3,12 @@
#nullable disable #nullable disable
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
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.Framework.Graphics.Primitives;
using osu.Framework.Layout; using osu.Framework.Layout;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
@ -126,6 +127,16 @@ namespace osu.Game.Rulesets.UI.Scrolling
private float scrollLength => scrollingAxis == Direction.Horizontal ? DrawWidth : DrawHeight; private float scrollLength => scrollingAxis == Direction.Horizontal ? DrawWidth : DrawHeight;
public override void Add(HitObjectLifetimeEntry entry)
{
// Scroll info is not available until loaded.
// The lifetime of all entries will be updated in the first Update.
if (IsLoaded)
setComputedLifetimeStart(entry);
base.Add(entry);
}
protected override void AddDrawable(HitObjectLifetimeEntry entry, DrawableHitObject drawable) protected override void AddDrawable(HitObjectLifetimeEntry entry, DrawableHitObject drawable)
{ {
base.AddDrawable(entry, drawable); base.AddDrawable(entry, drawable);
@ -144,7 +155,6 @@ namespace osu.Game.Rulesets.UI.Scrolling
private void invalidateHitObject(DrawableHitObject hitObject) private void invalidateHitObject(DrawableHitObject hitObject)
{ {
hitObject.LifetimeStart = computeOriginAdjustedLifetimeStart(hitObject);
layoutComputed.Remove(hitObject); layoutComputed.Remove(hitObject);
} }
@ -156,10 +166,8 @@ namespace osu.Game.Rulesets.UI.Scrolling
layoutComputed.Clear(); layoutComputed.Clear();
// Reset lifetime to the conservative estimation.
// If a drawable becomes alive by this lifetime, its lifetime will be updated to a more precise lifetime in the next update.
foreach (var entry in Entries) foreach (var entry in Entries)
entry.SetInitialLifetime(); setComputedLifetimeStart(entry);
scrollingInfo.Algorithm.Reset(); scrollingInfo.Algorithm.Reset();
@ -186,35 +194,46 @@ namespace osu.Game.Rulesets.UI.Scrolling
} }
} }
private double computeOriginAdjustedLifetimeStart(DrawableHitObject hitObject) /// <summary>
/// Get a conservative maximum bounding box of a <see cref="DrawableHitObject"/> corresponding to <paramref name="entry"/>.
/// It is used to calculate when the hit object appears.
/// </summary>
protected virtual RectangleF GetConservativeBoundingBox(HitObjectLifetimeEntry entry) => new RectangleF().Inflate(100);
private double computeDisplayStartTime(HitObjectLifetimeEntry entry)
{ {
// Origin position may be relative to the parent size RectangleF boundingBox = GetConservativeBoundingBox(entry);
Debug.Assert(hitObject.Parent != null); float startOffset = 0;
float originAdjustment = 0.0f;
// calculate the dimension of the part of the hitobject that should already be visible
// when the hitobject origin first appears inside the scrolling container
switch (direction.Value) switch (direction.Value)
{ {
case ScrollingDirection.Up: case ScrollingDirection.Right:
originAdjustment = hitObject.OriginPosition.Y; startOffset = boundingBox.Right;
break; break;
case ScrollingDirection.Down: case ScrollingDirection.Down:
originAdjustment = hitObject.DrawHeight - hitObject.OriginPosition.Y; startOffset = boundingBox.Bottom;
break; break;
case ScrollingDirection.Left: case ScrollingDirection.Left:
originAdjustment = hitObject.OriginPosition.X; startOffset = -boundingBox.Left;
break; break;
case ScrollingDirection.Right: case ScrollingDirection.Up:
originAdjustment = hitObject.DrawWidth - hitObject.OriginPosition.X; startOffset = -boundingBox.Top;
break; break;
} }
return scrollingInfo.Algorithm.GetDisplayStartTime(hitObject.HitObject.StartTime, originAdjustment, timeRange.Value, scrollLength); return scrollingInfo.Algorithm.GetDisplayStartTime(entry.HitObject.StartTime, startOffset, timeRange.Value, scrollLength);
}
private void setComputedLifetimeStart(HitObjectLifetimeEntry entry)
{
double computedStartTime = computeDisplayStartTime(entry);
// always load the hitobject before its first judgement offset
double judgementOffset = entry.HitObject.HitWindows?.WindowFor(Scoring.HitResult.Miss) ?? 0;
entry.LifetimeStart = Math.Min(entry.HitObject.StartTime - judgementOffset, computedStartTime);
} }
private void updateLayoutRecursive(DrawableHitObject hitObject) private void updateLayoutRecursive(DrawableHitObject hitObject)
@ -232,8 +251,9 @@ namespace osu.Game.Rulesets.UI.Scrolling
{ {
updateLayoutRecursive(obj); updateLayoutRecursive(obj);
// Nested hitobjects don't need to scroll, but they do need accurate positions // Nested hitobjects don't need to scroll, but they do need accurate positions and start lifetime
updatePosition(obj, hitObject.HitObject.StartTime); updatePosition(obj, hitObject.HitObject.StartTime);
setComputedLifetimeStart(obj.Entry);
} }
} }

View File

@ -38,6 +38,8 @@ namespace osu.Game.Rulesets.UI.Scrolling
/// </summary> /// </summary>
public virtual Vector2 ScreenSpacePositionAtTime(double time) => HitObjectContainer.ScreenSpacePositionAtTime(time); public virtual Vector2 ScreenSpacePositionAtTime(double time) => HitObjectContainer.ScreenSpacePositionAtTime(time);
protected sealed override HitObjectContainer CreateHitObjectContainer() => new ScrollingHitObjectContainer(); protected sealed override HitObjectContainer CreateHitObjectContainer() => CreateScrollingHitObjectContainer();
protected virtual ScrollingHitObjectContainer CreateScrollingHitObjectContainer() => new ScrollingHitObjectContainer();
} }
} }

View File

@ -4,11 +4,12 @@
#nullable disable #nullable disable
using System; using System;
using System.Linq; using System.Diagnostics;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Caching; using osu.Framework.Caching;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Logging;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts; using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts;
@ -34,8 +35,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
[Resolved] [Resolved]
private OsuColour colours { get; set; } private OsuColour colours { get; set; }
private static readonly int highest_divisor = BindableBeatDivisor.PREDEFINED_DIVISORS.Last();
public TimelineTickDisplay() public TimelineTickDisplay()
{ {
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
@ -80,20 +79,19 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
{ {
base.Update(); base.Update();
if (timeline != null) if (timeline == null || DrawWidth <= 0) return;
(float, float) newRange = (
(ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopLeft).X - PointVisualisation.MAX_WIDTH * 2) / DrawWidth * Content.RelativeChildSize.X,
(ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopRight).X + PointVisualisation.MAX_WIDTH * 2) / DrawWidth * Content.RelativeChildSize.X);
if (visibleRange != newRange)
{ {
var newRange = ( visibleRange = newRange;
(ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopLeft).X - PointVisualisation.MAX_WIDTH * 2) / DrawWidth * Content.RelativeChildSize.X,
(ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopRight).X + PointVisualisation.MAX_WIDTH * 2) / DrawWidth * Content.RelativeChildSize.X);
if (visibleRange != newRange) // actual regeneration only needs to occur if we've passed one of the known next min/max tick boundaries.
{ if (nextMinTick == null || nextMaxTick == null || (visibleRange.min < nextMinTick || visibleRange.max > nextMaxTick))
visibleRange = newRange; tickCache.Invalidate();
// actual regeneration only needs to occur if we've passed one of the known next min/max tick boundaries.
if (nextMinTick == null || nextMaxTick == null || (visibleRange.min < nextMinTick || visibleRange.max > nextMaxTick))
tickCache.Invalidate();
}
} }
if (!tickCache.IsValid) if (!tickCache.IsValid)
@ -151,6 +149,20 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
} }
} }
if (Children.Count > 512)
{
// There should always be a sanely small number of ticks rendered.
// If this assertion triggers, either the zoom logic is broken or a beatmap is
// probably doing weird things...
//
// Let's hope the latter never happens.
// If it does, we can choose to either fix it or ignore it as an outlier.
string message = $"Timeline is rendering many ticks ({Children.Count})";
Logger.Log(message);
Debug.Fail(message);
}
int usedDrawables = drawableIndex; int usedDrawables = drawableIndex;
// save a few drawables beyond the currently used for edge cases. // save a few drawables beyond the currently used for edge cases.

View File

@ -56,7 +56,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
protected ZoomableScrollContainer() protected ZoomableScrollContainer()
: base(Direction.Horizontal) : base(Direction.Horizontal)
{ {
base.Content.Add(zoomedContent = new Container { RelativeSizeAxes = Axes.Y }); base.Content.Add(zoomedContent = new Container
{
RelativeSizeAxes = Axes.Y,
// We must hide content until SetupZoom is called.
// If not, a child component that relies on its DrawWidth (via RelativeSizeAxes) may see a very incorrect value
// momentarily, as noticed in the TimelineTickDisplay, which would render thousands of ticks incorrectly.
Alpha = 0,
});
AddLayout(zoomedContentWidthCache); AddLayout(zoomedContentWidthCache);
} }
@ -94,6 +101,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
maxZoom = maximum; maxZoom = maximum;
CurrentZoom = zoomTarget = initial; CurrentZoom = zoomTarget = initial;
isZoomSetUp = true; isZoomSetUp = true;
zoomedContent.Show();
} }
/// <summary> /// <summary>
@ -118,9 +127,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
CurrentZoom = zoomTarget = newZoom; CurrentZoom = zoomTarget = newZoom;
} }
protected override void Update() protected override void UpdateAfterChildren()
{ {
base.Update(); base.UpdateAfterChildren();
if (!zoomedContentWidthCache.IsValid) if (!zoomedContentWidthCache.IsValid)
updateZoomedContentWidth(); updateZoomedContentWidth();

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using System.Threading; using System.Threading;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
@ -16,7 +14,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components
public class MatchLeaderboard : Leaderboard<MatchLeaderboardScope, APIUserScoreAggregate> public class MatchLeaderboard : Leaderboard<MatchLeaderboardScope, APIUserScoreAggregate>
{ {
[Resolved(typeof(Room), nameof(Room.RoomID))] [Resolved(typeof(Room), nameof(Room.RoomID))]
private Bindable<long?> roomId { get; set; } private Bindable<long?> roomId { get; set; } = null!;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
@ -33,7 +31,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components
protected override bool IsOnlineScope => true; protected override bool IsOnlineScope => true;
protected override APIRequest FetchScores(CancellationToken cancellationToken) protected override APIRequest? FetchScores(CancellationToken cancellationToken)
{ {
if (roomId.Value == null) if (roomId.Value == null)
return null; return null;

View File

@ -54,6 +54,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
{ {
private const float disabled_alpha = 0.2f; private const float disabled_alpha = 0.2f;
public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks;
public Action? SettingsApplied; public Action? SettingsApplied;
public OsuTextBox NameField = null!; public OsuTextBox NameField = null!;
@ -424,7 +426,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
private void hideError() => ErrorText.FadeOut(50); private void hideError() => ErrorText.FadeOut(50);
private void onSuccess(Room room) private void onSuccess(Room room) => Schedule(() =>
{ {
Debug.Assert(applyingSettingsOperation != null); Debug.Assert(applyingSettingsOperation != null);
@ -432,9 +434,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
applyingSettingsOperation.Dispose(); applyingSettingsOperation.Dispose();
applyingSettingsOperation = null; applyingSettingsOperation = null;
} });
private void onError(string text) private void onError(string text) => Schedule(() =>
{ {
Debug.Assert(applyingSettingsOperation != null); Debug.Assert(applyingSettingsOperation != null);
@ -455,7 +457,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
applyingSettingsOperation.Dispose(); applyingSettingsOperation.Dispose();
applyingSettingsOperation = null; applyingSettingsOperation = null;
} });
} }
public class CreateOrUpdateButton : TriangleButton public class CreateOrUpdateButton : TriangleButton

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using System; using System;
using osu.Framework.Timing; using osu.Framework.Timing;
using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer;

Some files were not shown because too many files have changed in this diff Show More