1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 07:42:57 +08:00

Merge branch 'master' into beat-divisor-better-defaults

This commit is contained in:
Bartłomiej Dach 2023-06-08 20:35:03 +02:00
commit 4b3b22f046
No known key found for this signature in database
156 changed files with 1174 additions and 767 deletions

View File

@ -2,7 +2,7 @@ blank_issues_enabled: false
contact_links:
- name: Help
url: https://github.com/ppy/osu/discussions/categories/q-a
about: osu! not working as you'd expect? Not sure it's a bug? Check the Q&A section!
about: osu! not working or performing as you'd expect? Not sure it's a bug? Check the Q&A section!
- name: Suggestions or feature request
url: https://github.com/ppy/osu/discussions/categories/ideas
about: Got something you think should change or be added? Search for or start a new discussion!

View File

@ -11,7 +11,7 @@
<AndroidManifestMerger>manifestmerger.jar</AndroidManifestMerger>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Framework.Android" Version="2023.521.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2023.608.0" />
</ItemGroup>
<ItemGroup>
<AndroidManifestOverlay Include="$(MSBuildThisFileDirectory)osu.Android\Properties\AndroidManifestOverlay.xml" />

View File

@ -0,0 +1,180 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Caching;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Screens.Edit;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Catch.Edit
{
/// <summary>
/// A grid which displays coloured beat divisor lines in proximity to the selection or placement cursor.
/// </summary>
/// <remarks>
/// This class heavily borrows from osu!mania's implementation (ManiaBeatSnapGrid).
/// If further changes are to be made, they should also be applied there.
/// If the scale of the changes are large enough, abstracting may be a good path.
/// </remarks>
public partial class CatchBeatSnapGrid : Component
{
private const double visible_range = 750;
/// <summary>
/// The range of time values of the current selection.
/// </summary>
public (double start, double end)? SelectionTimeRange
{
set
{
if (value == selectionTimeRange)
return;
selectionTimeRange = value;
lineCache.Invalidate();
}
}
[Resolved]
private EditorBeatmap beatmap { get; set; } = null!;
[Resolved]
private OsuColour colours { get; set; } = null!;
[Resolved]
private BindableBeatDivisor beatDivisor { get; set; } = null!;
private readonly Cached lineCache = new Cached();
private (double start, double end)? selectionTimeRange;
private ScrollingHitObjectContainer lineContainer = null!;
[BackgroundDependencyLoader]
private void load(HitObjectComposer composer)
{
lineContainer = new ScrollingHitObjectContainer();
((CatchPlayfield)composer.Playfield).UnderlayElements.Add(lineContainer);
beatDivisor.BindValueChanged(_ => createLines(), true);
}
protected override void Update()
{
base.Update();
if (!lineCache.IsValid)
{
lineCache.Validate();
createLines();
}
}
private readonly Stack<DrawableGridLine> availableLines = new Stack<DrawableGridLine>();
private void createLines()
{
foreach (var line in lineContainer.Objects.OfType<DrawableGridLine>())
availableLines.Push(line);
lineContainer.Clear();
if (selectionTimeRange == null)
return;
var range = selectionTimeRange.Value;
var timingPoint = beatmap.ControlPointInfo.TimingPointAt(range.start - visible_range);
double time = timingPoint.Time;
int beat = 0;
// progress time until in the visible range.
while (time < range.start - visible_range)
{
time += timingPoint.BeatLength / beatDivisor.Value;
beat++;
}
while (time < range.end + visible_range)
{
var nextTimingPoint = beatmap.ControlPointInfo.TimingPointAt(time);
// switch to the next timing point if we have reached it.
if (nextTimingPoint.Time > timingPoint.Time)
{
beat = 0;
time = nextTimingPoint.Time;
timingPoint = nextTimingPoint;
}
Color4 colour = BindableBeatDivisor.GetColourFor(
BindableBeatDivisor.GetDivisorForBeatIndex(beat, beatDivisor.Value), colours);
if (!availableLines.TryPop(out var line))
line = new DrawableGridLine();
line.HitObject.StartTime = time;
line.Colour = colour;
lineContainer.Add(line);
beat++;
time += timingPoint.BeatLength / beatDivisor.Value;
}
// required to update ScrollingHitObjectContainer's cache.
lineContainer.UpdateSubTree();
foreach (var line in lineContainer.Objects.OfType<DrawableGridLine>())
{
time = line.HitObject.StartTime;
if (time >= range.start && time <= range.end)
line.Alpha = 1;
else
{
double timeSeparation = time < range.start ? range.start - time : time - range.end;
line.Alpha = (float)Math.Max(0, 1 - timeSeparation / visible_range);
}
}
}
private partial class DrawableGridLine : DrawableHitObject
{
public DrawableGridLine()
: base(new HitObject())
{
RelativeSizeAxes = Axes.X;
Height = 2;
AddInternal(new Box { RelativeSizeAxes = Axes.Both });
}
[BackgroundDependencyLoader]
private void load()
{
Origin = Anchor.BottomLeft;
Anchor = Anchor.BottomLeft;
}
protected override void UpdateInitialTransforms()
{
// don't perform any fading we are handling that ourselves.
LifetimeEnd = HitObject.StartTime + visible_range;
}
}
}
}

View File

@ -33,6 +33,8 @@ namespace osu.Game.Rulesets.Catch.Edit
private InputManager inputManager = null!;
private CatchBeatSnapGrid beatSnapGrid = null!;
private readonly BindableDouble timeRangeMultiplier = new BindableDouble(1)
{
MinValue = 1,
@ -65,6 +67,8 @@ namespace osu.Game.Rulesets.Catch.Edit
Catcher.BASE_DASH_SPEED, -Catcher.BASE_DASH_SPEED,
Catcher.BASE_WALK_SPEED, -Catcher.BASE_WALK_SPEED,
}));
AddInternal(beatSnapGrid = new CatchBeatSnapGrid());
}
protected override void LoadComplete()
@ -74,6 +78,29 @@ namespace osu.Game.Rulesets.Catch.Edit
inputManager = GetContainingInputManager();
}
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
if (BlueprintContainer.CurrentTool is SelectTool)
{
if (EditorBeatmap.SelectedHitObjects.Any())
{
beatSnapGrid.SelectionTimeRange = (EditorBeatmap.SelectedHitObjects.Min(h => h.StartTime), EditorBeatmap.SelectedHitObjects.Max(h => h.GetEndTime()));
}
else
beatSnapGrid.SelectionTimeRange = null;
}
else
{
var result = FindSnappedPositionAndTime(inputManager.CurrentState.Mouse.Position);
if (result.Time is double time)
beatSnapGrid.SelectionTimeRange = (time, time);
else
beatSnapGrid.SelectionTimeRange = null;
}
}
protected override double ReadCurrentDistanceSnap(HitObject before, HitObject after)
{
// osu!catch's distance snap implementation is limited, in that a custom spacing cannot be specified.

View File

@ -3,6 +3,7 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawables;
@ -41,6 +42,8 @@ namespace osu.Game.Rulesets.Catch.UI
internal CatcherArea CatcherArea { get; private set; } = null!;
public Container UnderlayElements { get; private set; } = null!;
private readonly IBeatmapDifficultyInfo difficulty;
public CatchPlayfield(IBeatmapDifficultyInfo difficulty)
@ -62,6 +65,10 @@ namespace osu.Game.Rulesets.Catch.UI
AddRangeInternal(new[]
{
UnderlayElements = new Container
{
RelativeSizeAxes = Axes.Both,
},
droppedObjectContainer,
Catcher.CreateProxiedContent(),
HitObjectContainer.CreateProxy(),

View File

@ -21,18 +21,29 @@ namespace osu.Game.Rulesets.Mania.Configuration
{
base.InitialiseDefaults();
SetDefault(ManiaRulesetSetting.ScrollTime, 1500.0, DrawableManiaRuleset.MIN_TIME_RANGE, DrawableManiaRuleset.MAX_TIME_RANGE, 5);
SetDefault(ManiaRulesetSetting.ScrollSpeed, 8, 1, 40);
SetDefault(ManiaRulesetSetting.ScrollDirection, ManiaScrollingDirection.Down);
SetDefault(ManiaRulesetSetting.TimingBasedNoteColouring, false);
#pragma warning disable CS0618
// Although obsolete, this is still required to populate the bindable from the database in case migration is required.
SetDefault<double?>(ManiaRulesetSetting.ScrollTime, null);
if (Get<double?>(ManiaRulesetSetting.ScrollTime) is double scrollTime)
{
SetValue(ManiaRulesetSetting.ScrollSpeed, (int)Math.Round(DrawableManiaRuleset.MAX_TIME_RANGE / scrollTime));
SetValue<double?>(ManiaRulesetSetting.ScrollTime, null);
}
#pragma warning restore CS0618
}
public override TrackedSettings CreateTrackedSettings() => new TrackedSettings
{
new TrackedSetting<double>(ManiaRulesetSetting.ScrollTime,
scrollTime => new SettingDescription(
rawValue: scrollTime,
new TrackedSetting<int>(ManiaRulesetSetting.ScrollSpeed,
speed => new SettingDescription(
rawValue: speed,
name: RulesetSettingsStrings.ScrollSpeed,
value: RulesetSettingsStrings.ScrollSpeedTooltip(scrollTime, (int)Math.Round(DrawableManiaRuleset.MAX_TIME_RANGE / scrollTime))
value: RulesetSettingsStrings.ScrollSpeedTooltip((int)DrawableManiaRuleset.ComputeScrollTime(speed), speed)
)
)
};
@ -40,7 +51,9 @@ namespace osu.Game.Rulesets.Mania.Configuration
public enum ManiaRulesetSetting
{
[Obsolete("Use ScrollSpeed instead.")] // Can be removed 2023-11-30
ScrollTime,
ScrollSpeed,
ScrollDirection,
TimingBasedNoteColouring
}

View File

@ -129,7 +129,7 @@ namespace osu.Game.Rulesets.Mania.Edit
}
Color4 colour = BindableBeatDivisor.GetColourFor(
BindableBeatDivisor.GetDivisorForBeatIndex(Math.Max(1, beat), beatDivisor.Value), colours);
BindableBeatDivisor.GetDivisorForBeatIndex(beat, beatDivisor.Value), colours);
foreach (var grid in grids)
{

View File

@ -389,41 +389,23 @@ namespace osu.Game.Rulesets.Mania
return base.GetDisplayNameForHitResult(result);
}
public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => new[]
public override StatisticItem[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => new[]
{
new StatisticRow
new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score, playableBeatmap)
{
Columns = new[]
{
new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score, playableBeatmap)
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y
}),
}
},
new StatisticRow
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y
}),
new StatisticItem("Timing Distribution", () => new HitEventTimingDistributionGraph(score.HitEvents)
{
Columns = new[]
{
new StatisticItem("Timing Distribution", () => new HitEventTimingDistributionGraph(score.HitEvents)
{
RelativeSizeAxes = Axes.X,
Height = 250
}, true),
}
},
new StatisticRow
RelativeSizeAxes = Axes.X,
Height = 250
}, true),
new StatisticItem(string.Empty, () => new SimpleStatisticTable(3, new SimpleStatisticItem[]
{
Columns = new[]
{
new StatisticItem(string.Empty, () => new SimpleStatisticTable(3, new SimpleStatisticItem[]
{
new AverageHitError(score.HitEvents),
new UnstableRate(score.HitEvents)
}), true)
}
}
new AverageHitError(score.HitEvents),
new UnstableRate(score.HitEvents)
}), true)
};
public override IRulesetFilterCriteria CreateRulesetFilterCriteria()

View File

@ -1,7 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
@ -34,10 +33,10 @@ namespace osu.Game.Rulesets.Mania
LabelText = RulesetSettingsStrings.ScrollingDirection,
Current = config.GetBindable<ManiaScrollingDirection>(ManiaRulesetSetting.ScrollDirection)
},
new SettingsSlider<double, ManiaScrollSlider>
new SettingsSlider<int, ManiaScrollSlider>
{
LabelText = RulesetSettingsStrings.ScrollSpeed,
Current = config.GetBindable<double>(ManiaRulesetSetting.ScrollTime),
Current = config.GetBindable<int>(ManiaRulesetSetting.ScrollSpeed),
KeyboardStep = 5
},
new SettingsCheckbox
@ -48,9 +47,9 @@ namespace osu.Game.Rulesets.Mania
};
}
private partial class ManiaScrollSlider : RoundedSliderBar<double>
private partial class ManiaScrollSlider : RoundedSliderBar<int>
{
public override LocalisableString TooltipText => RulesetSettingsStrings.ScrollSpeedTooltip(Current.Value, (int)Math.Round(DrawableManiaRuleset.MAX_TIME_RANGE / Current.Value));
public override LocalisableString TooltipText => RulesetSettingsStrings.ScrollSpeedTooltip((int)DrawableManiaRuleset.ComputeScrollTime(Current.Value), Current.Value);
}
}
}

View File

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

View File

@ -245,7 +245,10 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
// As the note is being held, adjust the size of the sizing container. This has two effects:
// 1. The contained masking container will mask the body and ticks.
// 2. The head note will move along with the new "head position" in the container.
if (Head.IsHit && releaseTime == null && DrawHeight > 0)
//
// As per stable, this should not apply for early hits, waiting until the object starts to touch the
// judgement area first.
if (Head.IsHit && releaseTime == null && DrawHeight > 0 && Time.Current >= HitObject.StartTime)
{
// How far past the hit target this hold note is.
float yOffset = Direction.Value == ScrollingDirection.Up ? -Y : Y;

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
namespace osu.Game.Rulesets.Mania.Objects
{
public class HeadNote : Note

View File

@ -18,8 +18,8 @@ namespace osu.Game.Rulesets.Mania.Scoring
protected override double ComputeTotalScore(double comboProgress, double accuracyProgress, double bonusPortion)
{
return 200000 * comboProgress
+ 800000 * Math.Pow(Accuracy.Value, 2 + 2 * Accuracy.Value) * accuracyProgress
return 10000 * comboProgress
+ 990000 * Math.Pow(Accuracy.Value, 2 + 2 * Accuracy.Value) * accuracyProgress
+ bonusPortion;
}

View File

@ -139,11 +139,11 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
case 3:
switch (columnIndex)
{
case 0: return colour_pink;
case 0: return colour_green;
case 1: return colour_orange;
case 1: return colour_special_column;
case 2: return colour_yellow;
case 2: return colour_cyan;
default: throw new ArgumentOutOfRangeException();
}
@ -185,11 +185,11 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
case 1: return colour_orange;
case 2: return colour_yellow;
case 2: return colour_green;
case 3: return colour_cyan;
case 4: return colour_purple;
case 4: return colour_orange;
case 5: return colour_pink;
@ -201,17 +201,17 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
{
case 0: return colour_pink;
case 1: return colour_cyan;
case 1: return colour_orange;
case 2: return colour_pink;
case 3: return colour_special_column;
case 4: return colour_green;
case 4: return colour_pink;
case 5: return colour_cyan;
case 5: return colour_orange;
case 6: return colour_green;
case 6: return colour_pink;
default: throw new ArgumentOutOfRangeException();
}
@ -225,9 +225,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
case 2: return colour_orange;
case 3: return colour_yellow;
case 3: return colour_green;
case 4: return colour_yellow;
case 4: return colour_cyan;
case 5: return colour_orange;
@ -273,9 +273,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
case 3: return colour_yellow;
case 4: return colour_cyan;
case 4: return colour_green;
case 5: return colour_green;
case 5: return colour_cyan;
case 6: return colour_yellow;

View File

@ -11,6 +11,7 @@ using osu.Framework.Graphics.Pooling;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Framework.Platform;
using osu.Game.Extensions;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
@ -39,7 +40,11 @@ namespace osu.Game.Rulesets.Mania.UI
public readonly Bindable<ManiaAction> Action = new Bindable<ManiaAction>();
public readonly ColumnHitObjectArea HitObjectArea;
internal readonly Container BackgroundContainer = new Container { RelativeSizeAxes = Axes.Both };
internal readonly Container TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both };
private DrawablePool<PoolableHitExplosion> hitExplosionPool;
private readonly OrderedHitPolicy hitPolicy;
public Container UnderlayElements => HitObjectArea.UnderlayElements;
@ -76,30 +81,31 @@ namespace osu.Game.Rulesets.Mania.UI
skin.SourceChanged += onSourceChanged;
onSourceChanged();
Drawable background = new SkinnableDrawable(new ManiaSkinComponentLookup(ManiaSkinComponents.ColumnBackground), _ => new DefaultColumnBackground())
{
RelativeSizeAxes = Axes.Both,
};
InternalChildren = new[]
InternalChildren = new Drawable[]
{
hitExplosionPool = new DrawablePool<PoolableHitExplosion>(5),
sampleTriggerSource = new GameplaySampleTriggerSource(HitObjectContainer),
// For input purposes, the background is added at the highest depth, but is then proxied back below all other elements
background.CreateProxy(),
HitObjectArea,
keyArea = new SkinnableDrawable(new ManiaSkinComponentLookup(ManiaSkinComponents.KeyArea), _ => new DefaultKeyArea())
{
RelativeSizeAxes = Axes.Both,
},
background,
// For input purposes, the background is added at the highest depth, but is then proxied back below all other elements externally
// (see `Stage.columnBackgrounds`).
BackgroundContainer,
TopLevelContainer,
new ColumnTouchInputArea(this)
};
applyGameWideClock(background);
applyGameWideClock(keyArea);
var background = new SkinnableDrawable(new ManiaSkinComponentLookup(ManiaSkinComponents.ColumnBackground), _ => new DefaultColumnBackground())
{
RelativeSizeAxes = Axes.Both,
};
background.ApplyGameWideClock(host);
keyArea.ApplyGameWideClock(host);
BackgroundContainer.Add(background);
TopLevelContainer.Add(HitObjectArea.Explosions.CreateProxy());
RegisterPool<Note, DrawableNote>(10, 50);
@ -107,18 +113,6 @@ namespace osu.Game.Rulesets.Mania.UI
RegisterPool<HeadNote, DrawableHoldNoteHead>(10, 50);
RegisterPool<TailNote, DrawableHoldNoteTail>(10, 50);
RegisterPool<HoldNoteTick, DrawableHoldNoteTick>(50, 250);
// Some elements don't handle rewind correctly and fixing them is non-trivial.
// In the future we need a better solution to this, but as a temporary work-around, give these components the game-wide
// clock so they don't need to worry about rewind.
// This only works because they handle OnPressed/OnReleased which results in a correct state while rewinding.
//
// This is kinda dodgy (and will cause weirdness when pausing gameplay) but is better than completely broken rewind.
void applyGameWideClock(Drawable drawable)
{
drawable.Clock = host.UpdateThread.Clock;
drawable.ProcessCustomClock = false;
}
}
private void onSourceChanged()

View File

@ -33,12 +33,12 @@ namespace osu.Game.Rulesets.Mania.UI
public partial class DrawableManiaRuleset : DrawableScrollingRuleset<ManiaHitObject>
{
/// <summary>
/// The minimum time range. This occurs at a <see cref="relativeTimeRange"/> of 40.
/// The minimum time range. This occurs at a <see cref="ManiaRulesetSetting.ScrollSpeed"/> of 40.
/// </summary>
public const double MIN_TIME_RANGE = 290;
/// <summary>
/// The maximum time range. This occurs at a <see cref="relativeTimeRange"/> of 1.
/// The maximum time range. This occurs with a <see cref="ManiaRulesetSetting.ScrollSpeed"/> of 1.
/// </summary>
public const double MAX_TIME_RANGE = 11485;
@ -69,7 +69,8 @@ namespace osu.Game.Rulesets.Mania.UI
protected override ScrollVisualisationMethod VisualisationMethod => scrollMethod;
private readonly Bindable<ManiaScrollingDirection> configDirection = new Bindable<ManiaScrollingDirection>();
private readonly BindableDouble configTimeRange = new BindableDouble();
private readonly BindableInt configScrollSpeed = new BindableInt();
private double smoothTimeRange;
// Stores the current speed adjustment active in gameplay.
private readonly Track speedAdjustmentTrack = new TrackVirtual(0);
@ -78,6 +79,9 @@ namespace osu.Game.Rulesets.Mania.UI
: base(ruleset, beatmap, mods)
{
BarLines = new BarLineGenerator<BarLine>(Beatmap).BarLines;
TimeRange.MinValue = 1;
TimeRange.MaxValue = MAX_TIME_RANGE;
}
[BackgroundDependencyLoader]
@ -104,30 +108,28 @@ namespace osu.Game.Rulesets.Mania.UI
Config.BindWith(ManiaRulesetSetting.ScrollDirection, configDirection);
configDirection.BindValueChanged(direction => Direction.Value = (ScrollingDirection)direction.NewValue, true);
Config.BindWith(ManiaRulesetSetting.ScrollTime, configTimeRange);
TimeRange.MinValue = configTimeRange.MinValue;
TimeRange.MaxValue = configTimeRange.MaxValue;
Config.BindWith(ManiaRulesetSetting.ScrollSpeed, configScrollSpeed);
configScrollSpeed.BindValueChanged(speed => this.TransformTo(nameof(smoothTimeRange), ComputeScrollTime(speed.NewValue), 200, Easing.OutQuint));
TimeRange.Value = smoothTimeRange = ComputeScrollTime(configScrollSpeed.Value);
}
protected override void AdjustScrollSpeed(int amount)
{
this.TransformTo(nameof(relativeTimeRange), relativeTimeRange + amount, 200, Easing.OutQuint);
}
private double relativeTimeRange
{
get => MAX_TIME_RANGE / configTimeRange.Value;
set => configTimeRange.Value = MAX_TIME_RANGE / value;
}
protected override void AdjustScrollSpeed(int amount) => configScrollSpeed.Value += amount;
protected override void Update()
{
base.Update();
updateTimeRange();
}
private void updateTimeRange() => TimeRange.Value = configTimeRange.Value * speedAdjustmentTrack.AggregateTempo.Value * speedAdjustmentTrack.AggregateFrequency.Value;
private void updateTimeRange() => TimeRange.Value = smoothTimeRange * speedAdjustmentTrack.AggregateTempo.Value * speedAdjustmentTrack.AggregateFrequency.Value;
/// <summary>
/// Computes a scroll time (in milliseconds) from a scroll speed in the range of 1-40.
/// </summary>
/// <param name="scrollSpeed">The scroll speed.</param>
/// <returns>The scroll time.</returns>
public static double ComputeScrollTime(int scrollSpeed) => MAX_TIME_RANGE / scrollSpeed;
public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new ManiaPlayfieldAdjustmentContainer();

View File

@ -60,6 +60,7 @@ namespace osu.Game.Rulesets.Mania.UI
RelativeSizeAxes = Axes.Y;
AutoSizeAxes = Axes.X;
Container columnBackgrounds;
Container topLevelContainer;
InternalChildren = new Drawable[]
@ -77,9 +78,10 @@ namespace osu.Game.Rulesets.Mania.UI
{
RelativeSizeAxes = Axes.Both
},
columnFlow = new ColumnFlow<Column>(definition)
columnBackgrounds = new Container
{
RelativeSizeAxes = Axes.Y,
Name = "Column backgrounds",
RelativeSizeAxes = Axes.Both,
},
new Container
{
@ -98,6 +100,10 @@ namespace osu.Game.Rulesets.Mania.UI
RelativeSizeAxes = Axes.Y,
}
},
columnFlow = new ColumnFlow<Column>(definition)
{
RelativeSizeAxes = Axes.Y,
},
new SkinnableDrawable(new ManiaSkinComponentLookup(ManiaSkinComponents.StageForeground), _ => null)
{
RelativeSizeAxes = Axes.Both
@ -126,6 +132,7 @@ namespace osu.Game.Rulesets.Mania.UI
};
topLevelContainer.Add(column.TopLevelContainer.CreateProxy());
columnBackgrounds.Add(column.BackgroundContainer.CreateProxy());
columnFlow.SetContentForColumn(i, column);
AddNested(column);
}

View File

@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
AddStep("place first object", () => InputManager.Click(MouseButton.Left));
AddStep("move mouse slightly", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * 0.02f, 0)));
AddStep("move mouse slightly", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * 0.01f, 0)));
AddStep("place second object", () => InputManager.Click(MouseButton.Left));
@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
AddStep("enter circle placement mode", () => InputManager.Key(Key.Number2));
AddStep("move mouse slightly", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * 0.235f, 0)));
AddStep("move mouse slightly", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * 0.205f, 0)));
AddStep("place second object", () => InputManager.Click(MouseButton.Left));
@ -122,7 +122,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
AddStep("begin drag", () => InputManager.PressButton(MouseButton.Left));
AddStep("move mouse slightly off centre", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * 0.02f, 0)));
AddStep("move mouse slightly off centre", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * 0.01f, 0)));
AddAssert("object 3 snapped to 1", () =>
{
@ -134,7 +134,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
return Precision.AlmostEquals(first.EndPosition, third.Position);
});
AddStep("move mouse slightly off centre", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * -0.22f, playfield.ScreenSpaceDrawQuad.Width * 0.21f)));
AddStep("move mouse slightly off centre", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * -0.21f, playfield.ScreenSpaceDrawQuad.Width * 0.205f)));
AddAssert("object 2 snapped to 1", () =>
{

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{
public enum SliderPosition

View File

@ -187,7 +187,7 @@ namespace osu.Game.Rulesets.Osu.Edit
var playfield = PlayfieldAtScreenSpacePosition(screenSpacePosition);
float snapRadius =
playfield.GamefieldToScreenSpace(new Vector2(OsuHitObject.OBJECT_RADIUS / 5)).X -
playfield.GamefieldToScreenSpace(new Vector2(OsuHitObject.OBJECT_RADIUS * 0.10f)).X -
playfield.GamefieldToScreenSpace(Vector2.Zero).X;
foreach (var b in blueprints)

View File

@ -42,14 +42,14 @@ namespace osu.Game.Rulesets.Osu.Mods
private PlayfieldAdjustmentContainer bubbleContainer = null!;
private DrawablePool<BubbleDrawable> bubblePool = null!;
private readonly Bindable<int> currentCombo = new BindableInt();
private float maxSize;
private float bubbleSize;
private double bubbleFade;
private readonly DrawablePool<BubbleDrawable> bubblePool = new DrawablePool<BubbleDrawable>(100);
public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank;
public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor)
@ -72,6 +72,7 @@ namespace osu.Game.Rulesets.Osu.Mods
bubbleContainer = drawableRuleset.CreatePlayfieldAdjustmentContainer();
drawableRuleset.Overlays.Add(bubbleContainer);
drawableRuleset.Overlays.Add(bubblePool = new DrawablePool<BubbleDrawable>(100));
}
public void ApplyToDrawableHitObject(DrawableHitObject drawableObject)

View File

@ -18,7 +18,7 @@ using static osu.Game.Input.Handlers.ReplayInputHandler;
namespace osu.Game.Rulesets.Osu.Mods
{
public class OsuModRelax : ModRelax, IUpdatableByPlayfield, IApplicableToDrawableRuleset<OsuHitObject>, IApplicableToPlayer
public class OsuModRelax : ModRelax, IUpdatableByPlayfield, IApplicableToDrawableRuleset<OsuHitObject>, IApplicableToPlayer, IHasNoTimedInputs
{
public override LocalisableString Description => @"You don't need to click. Give your clicking/tapping fingers a break from the heat of things.";

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
public interface IRequireTracking

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
namespace osu.Game.Rulesets.Osu.Objects
{
public interface ISliderProgress

View File

@ -291,56 +291,32 @@ namespace osu.Game.Rulesets.Osu
return base.GetDisplayNameForHitResult(result);
}
public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap)
public override StatisticItem[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap)
{
var timedHitEvents = score.HitEvents.Where(e => e.HitObject is HitCircle && !(e.HitObject is SliderTailCircle)).ToList();
return new[]
{
new StatisticRow
new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score, playableBeatmap)
{
Columns = new[]
{
new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score, playableBeatmap)
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y
}),
}
},
new StatisticRow
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y
}),
new StatisticItem("Timing Distribution", () => new HitEventTimingDistributionGraph(timedHitEvents)
{
Columns = new[]
{
new StatisticItem("Timing Distribution", () => new HitEventTimingDistributionGraph(timedHitEvents)
{
RelativeSizeAxes = Axes.X,
Height = 250
}, true),
}
},
new StatisticRow
RelativeSizeAxes = Axes.X,
Height = 250
}, true),
new StatisticItem("Accuracy Heatmap", () => new AccuracyHeatmap(score, playableBeatmap)
{
Columns = new[]
{
new StatisticItem("Accuracy Heatmap", () => new AccuracyHeatmap(score, playableBeatmap)
{
RelativeSizeAxes = Axes.X,
Height = 250
}, true),
}
},
new StatisticRow
RelativeSizeAxes = Axes.X,
Height = 250
}, true),
new StatisticItem(string.Empty, () => new SimpleStatisticTable(3, new SimpleStatisticItem[]
{
Columns = new[]
{
new StatisticItem(string.Empty, () => new SimpleStatisticTable(3, new SimpleStatisticItem[]
{
new AverageHitError(timedHitEvents),
new UnstableRate(timedHitEvents)
}), true)
}
}
new AverageHitError(timedHitEvents),
new UnstableRate(timedHitEvents)
}), true)
};
}

View File

@ -15,187 +15,175 @@ namespace osu.Game.Rulesets.Taiko.Tests.Judgements
[Test]
public void TestHitAllDrumRoll()
{
const double hit_time = 1000;
PerformTest(new List<ReplayFrame>
{
new TaikoReplayFrame(0),
new TaikoReplayFrame(1000, TaikoAction.LeftCentre),
new TaikoReplayFrame(1001),
new TaikoReplayFrame(1250, TaikoAction.LeftCentre),
new TaikoReplayFrame(1251),
new TaikoReplayFrame(1500, TaikoAction.LeftCentre),
new TaikoReplayFrame(1501),
new TaikoReplayFrame(1750, TaikoAction.LeftCentre),
new TaikoReplayFrame(1751),
new TaikoReplayFrame(2000, TaikoAction.LeftCentre),
new TaikoReplayFrame(2001),
}, CreateBeatmap(new DrumRoll
{
StartTime = hit_time,
Duration = 1000
}));
}, CreateBeatmap(createDrumRoll(false)));
AssertJudgementCount(3);
AssertJudgementCount(6);
AssertResult<DrumRollTick>(0, HitResult.SmallBonus);
AssertResult<DrumRollTick>(1, HitResult.SmallBonus);
AssertResult<DrumRollTick>(2, HitResult.SmallBonus);
AssertResult<DrumRollTick>(3, HitResult.SmallBonus);
AssertResult<DrumRollTick>(4, HitResult.SmallBonus);
AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
}
[Test]
public void TestHitSomeDrumRoll()
{
const double hit_time = 1000;
PerformTest(new List<ReplayFrame>
{
new TaikoReplayFrame(0),
new TaikoReplayFrame(2000, TaikoAction.LeftCentre),
new TaikoReplayFrame(2001),
}, CreateBeatmap(new DrumRoll
{
StartTime = hit_time,
Duration = 1000
}));
}, CreateBeatmap(createDrumRoll(false)));
AssertJudgementCount(3);
AssertJudgementCount(6);
AssertResult<DrumRollTick>(0, HitResult.IgnoreMiss);
AssertResult<DrumRollTick>(1, HitResult.SmallBonus);
AssertResult<DrumRollTick>(1, HitResult.IgnoreMiss);
AssertResult<DrumRollTick>(2, HitResult.IgnoreMiss);
AssertResult<DrumRollTick>(3, HitResult.IgnoreMiss);
AssertResult<DrumRollTick>(4, HitResult.SmallBonus);
AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
}
[Test]
public void TestHitNoneDrumRoll()
{
const double hit_time = 1000;
PerformTest(new List<ReplayFrame>
{
new TaikoReplayFrame(0),
}, CreateBeatmap(new DrumRoll
{
StartTime = hit_time,
Duration = 1000
}));
}, CreateBeatmap(createDrumRoll(false)));
AssertJudgementCount(3);
AssertJudgementCount(6);
AssertResult<DrumRollTick>(0, HitResult.IgnoreMiss);
AssertResult<DrumRollTick>(1, HitResult.IgnoreMiss);
AssertResult<DrumRollTick>(2, HitResult.IgnoreMiss);
AssertResult<DrumRollTick>(3, HitResult.IgnoreMiss);
AssertResult<DrumRollTick>(4, HitResult.IgnoreMiss);
AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
}
[Test]
public void TestHitAllStrongDrumRollWithOneKey()
{
const double hit_time = 1000;
PerformTest(new List<ReplayFrame>
{
new TaikoReplayFrame(0),
new TaikoReplayFrame(1000, TaikoAction.LeftCentre),
new TaikoReplayFrame(1001),
new TaikoReplayFrame(1250, TaikoAction.LeftCentre),
new TaikoReplayFrame(1251),
new TaikoReplayFrame(1500, TaikoAction.LeftCentre),
new TaikoReplayFrame(1501),
new TaikoReplayFrame(1750, TaikoAction.LeftCentre),
new TaikoReplayFrame(1751),
new TaikoReplayFrame(2000, TaikoAction.LeftCentre),
new TaikoReplayFrame(2001),
}, CreateBeatmap(new DrumRoll
}, CreateBeatmap(createDrumRoll(true)));
AssertJudgementCount(12);
for (int i = 0; i < 5; i++)
{
StartTime = hit_time,
Duration = 1000,
IsStrong = true
}));
AssertJudgementCount(6);
AssertResult<DrumRollTick>(0, HitResult.SmallBonus);
AssertResult<StrongNestedHitObject>(0, HitResult.LargeBonus);
AssertResult<DrumRollTick>(1, HitResult.SmallBonus);
AssertResult<StrongNestedHitObject>(1, HitResult.LargeBonus);
AssertResult<DrumRollTick>(i, HitResult.SmallBonus);
AssertResult<StrongNestedHitObject>(i, HitResult.LargeBonus);
}
AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
AssertResult<StrongNestedHitObject>(2, HitResult.IgnoreHit);
AssertResult<StrongNestedHitObject>(5, HitResult.IgnoreHit);
}
[Test]
public void TestHitSomeStrongDrumRollWithOneKey()
{
const double hit_time = 1000;
PerformTest(new List<ReplayFrame>
{
new TaikoReplayFrame(0),
new TaikoReplayFrame(2000, TaikoAction.LeftCentre),
new TaikoReplayFrame(2001),
}, CreateBeatmap(new DrumRoll
{
StartTime = hit_time,
Duration = 1000,
IsStrong = true
}));
}, CreateBeatmap(createDrumRoll(true)));
AssertJudgementCount(6);
AssertJudgementCount(12);
AssertResult<DrumRollTick>(0, HitResult.IgnoreMiss);
AssertResult<StrongNestedHitObject>(0, HitResult.IgnoreMiss);
AssertResult<DrumRollTick>(1, HitResult.SmallBonus);
AssertResult<StrongNestedHitObject>(1, HitResult.LargeBonus);
AssertResult<DrumRollTick>(4, HitResult.SmallBonus);
AssertResult<StrongNestedHitObject>(4, HitResult.LargeBonus);
AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
AssertResult<StrongNestedHitObject>(2, HitResult.IgnoreHit);
AssertResult<StrongNestedHitObject>(5, HitResult.IgnoreHit);
}
[Test]
public void TestHitAllStrongDrumRollWithBothKeys()
{
const double hit_time = 1000;
PerformTest(new List<ReplayFrame>
{
new TaikoReplayFrame(0),
new TaikoReplayFrame(1000, TaikoAction.LeftCentre, TaikoAction.RightCentre),
new TaikoReplayFrame(1001),
new TaikoReplayFrame(1250, TaikoAction.LeftCentre, TaikoAction.RightCentre),
new TaikoReplayFrame(1251),
new TaikoReplayFrame(1500, TaikoAction.LeftCentre, TaikoAction.RightCentre),
new TaikoReplayFrame(1501),
new TaikoReplayFrame(1750, TaikoAction.LeftCentre, TaikoAction.RightCentre),
new TaikoReplayFrame(1751),
new TaikoReplayFrame(2000, TaikoAction.LeftCentre, TaikoAction.RightCentre),
new TaikoReplayFrame(2001),
}, CreateBeatmap(new DrumRoll
}, CreateBeatmap(createDrumRoll(true)));
AssertJudgementCount(12);
for (int i = 0; i < 5; i++)
{
StartTime = hit_time,
Duration = 1000,
IsStrong = true
}));
AssertJudgementCount(6);
AssertResult<DrumRollTick>(0, HitResult.SmallBonus);
AssertResult<StrongNestedHitObject>(0, HitResult.LargeBonus);
AssertResult<DrumRollTick>(1, HitResult.SmallBonus);
AssertResult<StrongNestedHitObject>(1, HitResult.LargeBonus);
AssertResult<DrumRollTick>(i, HitResult.SmallBonus);
AssertResult<StrongNestedHitObject>(i, HitResult.LargeBonus);
}
AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
AssertResult<StrongNestedHitObject>(2, HitResult.IgnoreHit);
AssertResult<StrongNestedHitObject>(5, HitResult.IgnoreHit);
}
[Test]
public void TestHitSomeStrongDrumRollWithBothKeys()
{
const double hit_time = 1000;
PerformTest(new List<ReplayFrame>
{
new TaikoReplayFrame(0),
new TaikoReplayFrame(2000, TaikoAction.LeftCentre, TaikoAction.RightCentre),
new TaikoReplayFrame(2001),
}, CreateBeatmap(new DrumRoll
{
StartTime = hit_time,
Duration = 1000,
IsStrong = true
}));
}, CreateBeatmap(createDrumRoll(true)));
AssertJudgementCount(6);
AssertJudgementCount(12);
AssertResult<DrumRollTick>(0, HitResult.IgnoreMiss);
AssertResult<StrongNestedHitObject>(0, HitResult.IgnoreMiss);
AssertResult<DrumRollTick>(1, HitResult.SmallBonus);
AssertResult<StrongNestedHitObject>(1, HitResult.LargeBonus);
AssertResult<DrumRollTick>(4, HitResult.SmallBonus);
AssertResult<StrongNestedHitObject>(4, HitResult.LargeBonus);
AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
AssertResult<StrongNestedHitObject>(2, HitResult.IgnoreHit);
AssertResult<StrongNestedHitObject>(5, HitResult.IgnoreHit);
}
private DrumRoll createDrumRoll(bool strong) => new DrumRoll
{
StartTime = 1000,
Duration = 1000,
IsStrong = strong
};
}
}

View File

@ -92,6 +92,14 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
}).ToList();
}
// TODO: stable makes the last tick of a drumroll non-required when the next object is too close.
// This probably needs to be reimplemented:
//
// List<HitObject> hitobjects = hitObjectManager.hitObjects;
// int ind = hitobjects.IndexOf(this);
// if (i < hitobjects.Count - 1 && hitobjects[i + 1].HittableStartTime - (EndTime + (int)TickSpacing) <= (int)TickSpacing)
// lastTickHittable = false;
return converted;
}
@ -133,7 +141,6 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
StartTime = obj.StartTime,
Samples = obj.Samples,
Duration = taikoDuration,
TickRate = beatmap.Difficulty.SliderTickRate == 3 ? 3 : 4,
SliderVelocity = obj is IHasSliderVelocity velocityData ? velocityData.SliderVelocity : 1
};
}

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using osu.Game.Rulesets.Objects.Types;
using System.Threading;
using osu.Framework.Bindables;
@ -69,6 +67,8 @@ namespace osu.Game.Rulesets.Taiko.Objects
double scoringDistance = base_distance * difficulty.SliderMultiplier * SliderVelocity;
Velocity = scoringDistance / timingPoint.BeatLength;
TickRate = difficulty.SliderTickRate == 3 ? 3 : 4;
tickSpacing = timingPoint.BeatLength / TickRate;
}

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
namespace osu.Game.Rulesets.Taiko.Objects
{
/// <summary>

View File

@ -1,9 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
@ -18,7 +16,9 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon
public partial class ArgonHitExplosion : CompositeDrawable, IAnimatableHitExplosion
{
private readonly TaikoSkinComponents component;
private readonly Circle outer;
private readonly Circle inner;
public ArgonHitExplosion(TaikoSkinComponents component)
{
@ -34,13 +34,9 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Colour = ColourInfo.GradientVertical(
new Color4(255, 227, 236, 255),
new Color4(255, 198, 211, 255)
),
Masking = true,
},
new Circle
inner = new Circle
{
Name = "Inner circle",
Anchor = Anchor.Centre,
@ -48,12 +44,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon
RelativeSizeAxes = Axes.Both,
Colour = Color4.White,
Size = new Vector2(0.85f),
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Colour = new Color4(255, 132, 191, 255).Opacity(0.5f),
Radius = 45,
},
Masking = true,
},
};
@ -63,6 +53,16 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon
{
this.FadeOut();
bool isRim = (drawableHitObject.HitObject as Hit)?.Type == HitType.Rim;
outer.Colour = isRim ? ArgonInputDrum.RIM_HIT_GRADIENT : ArgonInputDrum.CENTRE_HIT_GRADIENT;
inner.EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Colour = (isRim ? ArgonInputDrum.RIM_HIT_GLOW : ArgonInputDrum.CENTRE_HIT_GLOW).Opacity(0.5f),
Radius = 45,
};
switch (component)
{
case TaikoSkinComponents.TaikoExplosionGreat:

View File

@ -19,6 +19,20 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon
{
public partial class ArgonInputDrum : AspectContainer
{
public static readonly ColourInfo RIM_HIT_GRADIENT = ColourInfo.GradientHorizontal(
new Color4(227, 248, 255, 255),
new Color4(198, 245, 255, 255)
);
public static readonly Colour4 RIM_HIT_GLOW = new Color4(126, 215, 253, 255);
public static readonly ColourInfo CENTRE_HIT_GRADIENT = ColourInfo.GradientHorizontal(
new Color4(255, 227, 236, 255),
new Color4(255, 198, 211, 255)
);
public static readonly Colour4 CENTRE_HIT_GLOW = new Color4(255, 147, 199, 255);
private const float rim_size = 0.3f;
public ArgonInputDrum()
@ -141,14 +155,11 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon
Anchor = anchor,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Colour = ColourInfo.GradientHorizontal(
new Color4(227, 248, 255, 255),
new Color4(198, 245, 255, 255)
),
Colour = RIM_HIT_GRADIENT,
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Colour = new Color4(126, 215, 253, 170),
Colour = RIM_HIT_GLOW.Opacity(0.66f),
Radius = 50,
},
Alpha = 0,
@ -166,14 +177,11 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon
Anchor = anchor,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Colour = ColourInfo.GradientHorizontal(
new Color4(255, 227, 236, 255),
new Color4(255, 198, 211, 255)
),
Colour = CENTRE_HIT_GRADIENT,
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Colour = new Color4(255, 147, 199, 255),
Colour = CENTRE_HIT_GLOW,
Radius = 50,
},
Size = new Vector2(1 - rim_size),

View File

@ -229,45 +229,27 @@ namespace osu.Game.Rulesets.Taiko
return base.GetDisplayNameForHitResult(result);
}
public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap)
public override StatisticItem[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap)
{
var timedHitEvents = score.HitEvents.Where(e => e.HitObject is Hit).ToList();
return new[]
{
new StatisticRow
new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score, playableBeatmap)
{
Columns = new[]
{
new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score, playableBeatmap)
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y
}),
}
},
new StatisticRow
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y
}),
new StatisticItem("Timing Distribution", () => new HitEventTimingDistributionGraph(timedHitEvents)
{
Columns = new[]
{
new StatisticItem("Timing Distribution", () => new HitEventTimingDistributionGraph(timedHitEvents)
{
RelativeSizeAxes = Axes.X,
Height = 250
}, true),
}
},
new StatisticRow
RelativeSizeAxes = Axes.X,
Height = 250
}, true),
new StatisticItem(string.Empty, () => new SimpleStatisticTable(3, new SimpleStatisticItem[]
{
Columns = new[]
{
new StatisticItem(string.Empty, () => new SimpleStatisticTable(3, new SimpleStatisticItem[]
{
new AverageHitError(timedHitEvents),
new UnstableRate(timedHitEvents)
}), true)
}
}
new AverageHitError(timedHitEvents),
new UnstableRate(timedHitEvents)
}), true)
};
}
}

View File

@ -231,7 +231,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
protected override IBeatmap GetBeatmap() => beatmap;
protected override Texture GetBackground() => throw new NotImplementedException();
public override Texture GetBackground() => throw new NotImplementedException();
protected override Track GetBeatmapTrack() => throw new NotImplementedException();

View File

@ -131,7 +131,7 @@ namespace osu.Game.Tests.Editing.Checks
var mock = new Mock<IWorkingBeatmap>();
mock.SetupGet(w => w.Beatmap).Returns(beatmap);
mock.SetupGet(w => w.Background).Returns(background);
mock.Setup(w => w.GetBackground()).Returns(background);
mock.Setup(w => w.GetStream(It.IsAny<string>())).Returns(stream);
return mock;

View File

@ -73,7 +73,5 @@ namespace osu.Game.Tests.Rulesets
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => null;
public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => null;
}
#nullable enable
}
}

View File

@ -286,7 +286,7 @@ namespace osu.Game.Tests.Visual.Background
this.renderer = renderer;
}
protected override Texture GetBackground() => renderer.CreateTexture(1, 1);
public override Texture GetBackground() => renderer.CreateTexture(1, 1);
}
private partial class TestWorkingBeatmapWithStoryboard : TestWorkingBeatmap

View File

@ -264,8 +264,9 @@ namespace osu.Game.Tests.Visual.Collections
assertCollectionName(1, "First");
}
[Test]
public void TestCollectionRenamedOnTextChange()
[TestCase(false)]
[TestCase(true)]
public void TestCollectionRenamedOnTextChange(bool commitWithEnter)
{
BeatmapCollection first = null!;
DrawableCollectionListItem firstItem = null!;
@ -293,9 +294,19 @@ namespace osu.Game.Tests.Visual.Collections
AddStep("change first collection name", () =>
{
firstItem.ChildrenOfType<TextBox>().First().Text = "First";
InputManager.Key(Key.Enter);
});
if (commitWithEnter)
AddStep("commit via enter", () => InputManager.Key(Key.Enter));
else
{
AddStep("commit via click away", () =>
{
InputManager.MoveMouseTo(firstItem.ScreenSpaceDrawQuad.TopLeft - new Vector2(10));
InputManager.Click(MouseButton.Left);
});
}
AddUntilStep("collection has new name", () => first.Name == "First");
}

View File

@ -49,9 +49,9 @@ namespace osu.Game.Tests.Visual.Editing
[Test]
public void TestBindableBeatDivisor()
{
AddRepeatStep("move previous", () => bindableBeatDivisor.Previous(), 2);
AddRepeatStep("move previous", () => bindableBeatDivisor.SelectPrevious(), 2);
AddAssert("divisor is 4", () => bindableBeatDivisor.Value == 4);
AddRepeatStep("move next", () => bindableBeatDivisor.Next(), 1);
AddRepeatStep("move next", () => bindableBeatDivisor.SelectNext(), 1);
AddAssert("divisor is 12", () => bindableBeatDivisor.Value == 8);
}
@ -99,16 +99,22 @@ namespace osu.Game.Tests.Visual.Editing
public void TestBeatChevronNavigation()
{
switchBeatSnap(1);
assertBeatSnap(16);
switchBeatSnap(-4);
assertBeatSnap(1);
switchBeatSnap(3);
assertBeatSnap(8);
switchBeatSnap(-1);
switchBeatSnap(3);
assertBeatSnap(16);
switchBeatSnap(-2);
assertBeatSnap(4);
switchBeatSnap(-3);
assertBeatSnap(16);
assertBeatSnap(1);
}
[Test]
@ -210,7 +216,7 @@ namespace osu.Game.Tests.Visual.Editing
}, Math.Abs(direction));
private void assertBeatSnap(int expected) => AddAssert($"beat snap is {expected}",
() => bindableBeatDivisor.Value == expected);
() => bindableBeatDivisor.Value, () => Is.EqualTo(expected));
private void switchPresets(int direction) => AddRepeatStep($"move presets {(direction > 0 ? "forward" : "backward")}", () =>
{

View File

@ -209,10 +209,14 @@ namespace osu.Game.Tests.Visual.Editing
public override void TearDownSteps()
{
base.TearDownSteps();
AddStep("delete imported", () =>
AddStep("delete imported", () => Realm.Write(r =>
{
beatmaps.Delete(importedBeatmapSet);
});
// delete from realm directly rather than via `BeatmapManager` to avoid cross-test pollution
// (`BeatmapManager.Delete()` uses soft deletion, which can lead to beatmap reuse between test cases).
r.RemoveAll<BeatmapMetadata>();
r.RemoveAll<BeatmapInfo>();
r.RemoveAll<BeatmapSetInfo>();
}));
}
}
}

View File

@ -8,6 +8,8 @@ using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Scoring;
using osu.Game.Screens.Play.PlayerSettings;
using osu.Game.Tests.Visual.Ranking;
@ -49,6 +51,21 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("No calibration button", () => !offsetControl.ChildrenOfType<SettingsButton>().Any());
}
[Test]
public void TestModRemovingTimedInputs()
{
AddStep("Set score with mod removing timed inputs", () =>
{
offsetControl.ReferenceScore.Value = new ScoreInfo
{
HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(10),
Mods = new Mod[] { new OsuModRelax() }
};
});
AddAssert("No calibration button", () => !offsetControl.ChildrenOfType<SettingsButton>().Any());
}
[Test]
public void TestCalibrationFromZero()
{

View File

@ -129,7 +129,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
Playlist =
{
new MultiplayerPlaylistItem(playlistItem),
TestMultiplayerClient.CreateMultiplayerPlaylistItem(playlistItem),
},
Users = { localUser },
Host = localUser,

View File

@ -906,7 +906,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
enterGameplay();
AddStep("join other user", () => multiplayerClient.AddUser(new APIUser { Id = 1234 }));
AddStep("add item as other user", () => multiplayerClient.AddUserPlaylistItem(1234, new MultiplayerPlaylistItem(
AddStep("add item as other user", () => multiplayerClient.AddUserPlaylistItem(1234, TestMultiplayerClient.CreateMultiplayerPlaylistItem(
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
@ -938,7 +938,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
enterGameplay();
AddStep("join other user", () => multiplayerClient.AddUser(new APIUser { Id = 1234 }));
AddStep("add item as other user", () => multiplayerClient.AddUserPlaylistItem(1234, new MultiplayerPlaylistItem(
AddStep("add item as other user", () => multiplayerClient.AddUserPlaylistItem(1234, TestMultiplayerClient.CreateMultiplayerPlaylistItem(
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,

View File

@ -215,7 +215,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
/// </summary>
private void addItemStep(bool expired = false, int? userId = null) => AddStep("add item", () =>
{
MultiplayerClient.AddUserPlaylistItem(userId ?? API.LocalUser.Value.OnlineID, new MultiplayerPlaylistItem(new PlaylistItem(importedBeatmap)
MultiplayerClient.AddUserPlaylistItem(userId ?? API.LocalUser.Value.OnlineID, TestMultiplayerClient.CreateMultiplayerPlaylistItem(new PlaylistItem(importedBeatmap)
{
Expired = expired,
PlayedAt = DateTimeOffset.Now

View File

@ -130,7 +130,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("add playlist item", () =>
{
MultiplayerPlaylistItem item = new MultiplayerPlaylistItem(new PlaylistItem(importedBeatmap));
MultiplayerPlaylistItem item = TestMultiplayerClient.CreateMultiplayerPlaylistItem(new PlaylistItem(importedBeatmap));
MultiplayerClient.AddUserPlaylistItem(userId(), item).WaitSafely();

View File

@ -40,7 +40,7 @@ namespace osu.Game.Tests.Visual.Navigation
AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0)));
AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse);
AddStep("test gameplay", () => ((Editor)Game.ScreenStack.CurrentScreen).TestGameplay());
AddStep("test gameplay", () => getEditor().TestGameplay());
AddUntilStep("wait for player", () =>
{
@ -141,6 +141,37 @@ namespace osu.Game.Tests.Visual.Navigation
AddUntilStep("wait for editor exit", () => Game.ScreenStack.CurrentScreen is not Editor);
}
private EditorBeatmap getEditorBeatmap() => ((Editor)Game.ScreenStack.CurrentScreen).ChildrenOfType<EditorBeatmap>().Single();
[Test]
public void TestLastTimestampRememberedOnExit()
{
BeatmapSetInfo beatmapSet = null!;
AddStep("import test beatmap", () => Game.BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely());
AddStep("retrieve beatmap", () => beatmapSet = Game.BeatmapManager.QueryBeatmapSet(set => !set.Protected).AsNonNull().Value.Detach());
AddStep("present beatmap", () => Game.PresentBeatmap(beatmapSet));
AddUntilStep("wait for song select",
() => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet)
&& Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect
&& songSelect.IsLoaded);
AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0)));
AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse);
AddStep("seek to arbitrary time", () => getEditor().ChildrenOfType<EditorClock>().First().Seek(1234));
AddUntilStep("time is correct", () => getEditor().ChildrenOfType<EditorClock>().First().CurrentTime, () => Is.EqualTo(1234));
AddStep("exit editor", () => InputManager.Key(Key.Escape));
AddUntilStep("wait for editor exit", () => Game.ScreenStack.CurrentScreen is not Editor);
AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit());
AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse);
AddUntilStep("time is correct", () => getEditor().ChildrenOfType<EditorClock>().First().CurrentTime, () => Is.EqualTo(1234));
}
private EditorBeatmap getEditorBeatmap() => getEditor().ChildrenOfType<EditorBeatmap>().Single();
private Editor getEditor() => (Editor)Game.ScreenStack.CurrentScreen;
}
}

View File

@ -17,6 +17,7 @@ using osu.Game.Beatmaps;
using osu.Game.Collections;
using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API;
using osu.Game.Online.Leaderboards;
using osu.Game.Overlays;
using osu.Game.Overlays.BeatmapListing;
@ -539,6 +540,11 @@ namespace osu.Game.Tests.Visual.Navigation
AddStep("open room", () => multiplayerComponents.ChildrenOfType<LoungeSubScreen>().Single().Open());
AddStep("press back button", () => Game.ChildrenOfType<BackButton>().First().Action());
AddWaitStep("wait two frames", 2);
AddStep("exit lounge", () => Game.ScreenStack.Exit());
// `TestMultiplayerComponents` registers a request handler in its BDL, but never unregisters it.
// to prevent the handler living for longer than it should be, clean up manually.
AddStep("clean up multiplayer request handler", () => ((DummyAPIAccess)API).HandleRequest = null);
}
[Test]

View File

@ -174,78 +174,33 @@ namespace osu.Game.Tests.Visual.Ranking
private class TestRulesetAllStatsRequireHitEvents : TestRuleset
{
public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap)
public override StatisticItem[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => new[]
{
return new[]
{
new StatisticRow
{
Columns = new[]
{
new StatisticItem("Statistic Requiring Hit Events 1",
() => CreatePlaceholderStatistic("Placeholder statistic. Requires hit events"), true)
}
},
new StatisticRow
{
Columns = new[]
{
new StatisticItem("Statistic Requiring Hit Events 2",
() => CreatePlaceholderStatistic("Placeholder statistic. Requires hit events"), true)
}
}
};
}
new StatisticItem("Statistic Requiring Hit Events 1", () => CreatePlaceholderStatistic("Placeholder statistic. Requires hit events"), true),
new StatisticItem("Statistic Requiring Hit Events 2", () => CreatePlaceholderStatistic("Placeholder statistic. Requires hit events"), true)
};
}
private class TestRulesetNoStatsRequireHitEvents : TestRuleset
{
public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap)
public override StatisticItem[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap)
{
return new[]
{
new StatisticRow
{
Columns = new[]
{
new StatisticItem("Statistic Not Requiring Hit Events 1",
() => CreatePlaceholderStatistic("Placeholder statistic. Does not require hit events"))
}
},
new StatisticRow
{
Columns = new[]
{
new StatisticItem("Statistic Not Requiring Hit Events 2",
() => CreatePlaceholderStatistic("Placeholder statistic. Does not require hit events"))
}
}
new StatisticItem("Statistic Not Requiring Hit Events 1", () => CreatePlaceholderStatistic("Placeholder statistic. Does not require hit events")),
new StatisticItem("Statistic Not Requiring Hit Events 2", () => CreatePlaceholderStatistic("Placeholder statistic. Does not require hit events"))
};
}
}
private class TestRulesetMixed : TestRuleset
{
public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap)
public override StatisticItem[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap)
{
return new[]
{
new StatisticRow
{
Columns = new[]
{
new StatisticItem("Statistic Requiring Hit Events",
() => CreatePlaceholderStatistic("Placeholder statistic. Requires hit events"), true)
}
},
new StatisticRow
{
Columns = new[]
{
new StatisticItem("Statistic Not Requiring Hit Events",
() => CreatePlaceholderStatistic("Placeholder statistic. Does not require hit events"))
}
}
new StatisticItem("Statistic Requiring Hit Events", () => CreatePlaceholderStatistic("Placeholder statistic. Requires hit events"), true),
new StatisticItem("Statistic Not Requiring Hit Events", () => CreatePlaceholderStatistic("Placeholder statistic. Does not require hit events"))
};
}
}

View File

@ -80,6 +80,24 @@ namespace osu.Game.Tests.Visual.UserInterface
});
}
[Test]
public void TestCorrectScrollToWhenContentLoads()
{
AddRepeatStep("add many sections", () => append(1f), 3);
AddStep("add section with delayed load content", () =>
{
container.Add(new TestDelayedLoadSection("delayed"));
});
AddStep("add final section", () => append(0.5f));
AddStep("scroll to final section", () => container.ScrollTo(container.Children.Last()));
AddUntilStep("correct section selected", () => container.SelectedSection.Value == container.Children.Last());
AddUntilStep("wait for scroll to section", () => container.ScreenSpaceDrawQuad.AABBFloat.Contains(container.Children.Last().ScreenSpaceDrawQuad.AABBFloat));
}
[Test]
public void TestSelection()
{
@ -196,6 +214,33 @@ namespace osu.Game.Tests.Visual.UserInterface
InputManager.ScrollVerticalBy(direction);
}
private partial class TestDelayedLoadSection : TestSection
{
public TestDelayedLoadSection(string label)
: base(label)
{
BackgroundColour = default_colour;
Width = 300;
AutoSizeAxes = Axes.Y;
}
protected override void LoadComplete()
{
base.LoadComplete();
Box box;
Add(box = new Box
{
Alpha = 0.01f,
RelativeSizeAxes = Axes.X,
});
// Emulate an operation that will be inhibited by IsMaskedAway.
box.ResizeHeightTo(2000, 50);
}
}
private partial class TestSection : TestBox
{
public bool Selected

View File

@ -51,7 +51,7 @@ namespace osu.Game.Tests
protected override IBeatmap GetBeatmap() => beatmap;
protected override Texture GetBackground() => null;
public override Texture GetBackground() => null;
protected override Waveform GetWaveform() => new Waveform(trackStore.GetStream(firstAudioFile));

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
namespace osu.Game.Tournament.IPC
{
public enum TourneyState

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using osu.Game.Tournament.Models;
namespace osu.Game.Tournament.Screens.Ladder.Components

View File

@ -171,6 +171,11 @@ namespace osu.Game.Beatmaps
public double TimelineZoom { get; set; } = 1.0;
/// <summary>
/// The time in milliseconds when last exiting the editor with this beatmap loaded.
/// </summary>
public double? EditorTimestamp { get; set; }
[Ignored]
public CountdownType Countdown { get; set; } = CountdownType.Normal;

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
namespace osu.Game.Beatmaps
{
public enum DifficultyRating

View File

@ -23,8 +23,9 @@ namespace osu.Game.Beatmaps.Drawables
[BackgroundDependencyLoader]
private void load()
{
if (working.Background != null)
Texture = working.Background;
var background = working.GetBackground();
if (background != null)
Texture = background;
}
}
}

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
namespace osu.Game.Beatmaps.Drawables.Cards
{
/// <summary>

View File

@ -52,7 +52,7 @@ namespace osu.Game.Beatmaps
protected override IBeatmap GetBeatmap() => new Beatmap();
protected override Texture GetBackground() => textures?.Get(@"Backgrounds/bg4");
public override Texture GetBackground() => textures?.Get(@"Backgrounds/bg4");
protected override Track GetBeatmapTrack() => GetVirtualTrack();

View File

@ -43,7 +43,7 @@ namespace osu.Game.Beatmaps
}
protected override IBeatmap GetBeatmap() => beatmap;
protected override Texture GetBackground() => throw new NotImplementedException();
public override Texture GetBackground() => throw new NotImplementedException();
protected override Track GetBeatmapTrack() => throw new NotImplementedException();
protected internal override ISkin GetSkin() => throw new NotImplementedException();
public override Stream GetStream(string storagePath) => throw new NotImplementedException();

View File

@ -47,7 +47,7 @@ namespace osu.Game.Beatmaps
/// <summary>
/// Retrieves the background for this <see cref="IWorkingBeatmap"/>.
/// </summary>
Texture Background { get; }
Texture GetBackground();
/// <summary>
/// Retrieves the <see cref="Waveform"/> for the <see cref="Track"/> of this <see cref="IWorkingBeatmap"/>.

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
namespace osu.Game.Beatmaps.Legacy
{
internal enum LegacyOrigins

View File

@ -34,8 +34,6 @@ namespace osu.Game.Beatmaps
public Storyboard Storyboard => storyboard.Value;
public Texture Background => GetBackground(); // Texture uses ref counting, so we want to return a new instance every usage.
public ISkin Skin => skin.Value;
private AudioManager audioManager { get; }
@ -67,7 +65,7 @@ namespace osu.Game.Beatmaps
protected virtual Storyboard GetStoryboard() => new Storyboard { BeatmapInfo = BeatmapInfo };
protected abstract IBeatmap GetBeatmap();
protected abstract Texture GetBackground();
public abstract Texture GetBackground();
protected abstract Track GetBeatmapTrack();
/// <summary>

View File

@ -160,7 +160,7 @@ namespace osu.Game.Beatmaps
}
}
protected override Texture GetBackground()
public override Texture GetBackground()
{
if (string.IsNullOrEmpty(Metadata?.BackgroundFile))
return null;

View File

@ -86,6 +86,7 @@ namespace osu.Game.Collections
RelativeSizeAxes = Axes.Both,
Size = Vector2.One,
CornerRadius = item_height / 2,
CommitOnFocusLost = true,
PlaceholderText = collection.IsManaged ? string.Empty : "Create a new collection"
},
}

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
namespace osu.Game.Configuration
{
public enum IntroSequence

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
namespace osu.Game.Configuration
{
public enum ReleaseStream

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
namespace osu.Game.Configuration
{
public enum ToolbarClockDisplayMode

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
namespace osu.Game.Database
{
/// <summary>

View File

@ -71,8 +71,9 @@ namespace osu.Game.Database
/// 24 2022-08-22 Added MaximumStatistics to ScoreInfo.
/// 25 2022-09-18 Remove skins to add with new naming.
/// 26 2023-02-05 Added BeatmapHash to ScoreInfo.
/// 27 2023-06-06 Added EditorTimestamp to BeatmapInfo.
/// </summary>
private const int schema_version = 26;
private const int schema_version = 27;
/// <summary>
/// Lock object which is held during <see cref="BlockAllOperations"/> sections, blocking realm retrieval during blocking periods.

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
using osu.Framework.Platform;
using osuTK;
namespace osu.Game.Extensions
@ -43,5 +44,20 @@ namespace osu.Game.Extensions
/// <returns>The delta vector in Parent's coordinates.</returns>
public static Vector2 ScreenSpaceDeltaToParentSpace(this Drawable drawable, Vector2 delta) =>
drawable.Parent.ToLocalSpace(drawable.Parent.ToScreenSpace(Vector2.Zero) + delta);
/// <summary>
/// Some elements don't handle rewind correctly and fixing them is non-trivial.
/// In the future we need a better solution to this, but as a temporary work-around, give these components the game-wide
/// clock so they don't need to worry about rewind.
///
/// This only works if input handling components handle OnPressed/OnReleased which results in a correct state while rewinding.
///
/// This is kinda dodgy (and will cause weirdness when pausing gameplay) but is better than completely broken rewind.
/// </summary>
public static void ApplyGameWideClock(this Drawable drawable, GameHost host)
{
drawable.Clock = host.UpdateThread.Clock;
drawable.ProcessCustomClock = false;
}
}
}

View File

@ -21,7 +21,12 @@ namespace osu.Game.Extensions
/// This is required as enum member names are not allowed to contain hyphens.
/// </remarks>
public static string ToCultureCode(this Language language)
=> language.ToString().Replace("_", "-");
{
if (language == Language.zh_hant)
return @"zh-tw";
return language.ToString().Replace("_", "-");
}
/// <summary>
/// Attempts to parse the supplied <paramref name="cultureCode"/> to a <see cref="Language"/> value.
@ -30,7 +35,15 @@ namespace osu.Game.Extensions
/// <param name="language">The parsed <see cref="Language"/>. Valid only if the return value of the method is <see langword="true" />.</param>
/// <returns>Whether the parsing succeeded.</returns>
public static bool TryParseCultureCode(string cultureCode, out Language language)
=> Enum.TryParse(cultureCode.Replace("-", "_"), out language);
{
if (cultureCode == @"zh-tw")
{
language = Language.zh_hant;
return true;
}
return Enum.TryParse(cultureCode.Replace("-", "_"), out language);
}
/// <summary>
/// Parses the <see cref="Language"/> that is specified in <paramref name="frameworkLocale"/>,

View File

@ -24,7 +24,7 @@ namespace osu.Game.Graphics.Backgrounds
[BackgroundDependencyLoader]
private void load(LargeTextureStore textures)
{
Sprite.Texture = Beatmap?.Background ?? textures.Get(fallbackTextureName);
Sprite.Texture = Beatmap?.GetBackground() ?? textures.Get(fallbackTextureName);
}
public override bool Equals(Background other)

View File

@ -1,17 +1,16 @@
// 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.
#nullable disable
using System;
using System.Diagnostics;
using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Layout;
using osu.Framework.Logging;
using osu.Framework.Threading;
using osu.Framework.Utils;
namespace osu.Game.Graphics.Containers
@ -23,11 +22,35 @@ namespace osu.Game.Graphics.Containers
public partial class SectionsContainer<T> : Container<T>
where T : Drawable
{
public Bindable<T> SelectedSection { get; } = new Bindable<T>();
public Bindable<T?> SelectedSection { get; } = new Bindable<T?>();
private T lastClickedSection;
private T? lastClickedSection;
public Drawable ExpandableHeader
protected override Container<T> Content => scrollContentContainer;
private readonly UserTrackingScrollContainer scrollContainer;
private readonly Container headerBackgroundContainer;
private readonly MarginPadding originalSectionsMargin;
private Drawable? fixedHeader;
private Drawable? footer;
private Drawable? headerBackground;
private FlowContainer<T> scrollContentContainer = null!;
private float? headerHeight, footerHeight;
private float? lastKnownScroll;
/// <summary>
/// The percentage of the container to consider the centre-point for deciding the active section (and scrolling to a requested section).
/// </summary>
private const float scroll_y_centre = 0.1f;
private Drawable? expandableHeader;
public Drawable? ExpandableHeader
{
get => expandableHeader;
set
@ -42,11 +65,12 @@ namespace osu.Game.Graphics.Containers
if (value == null) return;
AddInternal(expandableHeader);
lastKnownScroll = null;
}
}
public Drawable FixedHeader
public Drawable? FixedHeader
{
get => fixedHeader;
set
@ -63,7 +87,7 @@ namespace osu.Game.Graphics.Containers
}
}
public Drawable Footer
public Drawable? Footer
{
get => footer;
set
@ -75,16 +99,17 @@ namespace osu.Game.Graphics.Containers
footer = value;
if (value == null) return;
if (footer == null) return;
footer.Anchor |= Anchor.y2;
footer.Origin |= Anchor.y2;
scrollContainer.Add(footer);
lastKnownScroll = null;
}
}
public Drawable HeaderBackground
public Drawable? HeaderBackground
{
get => headerBackground;
set
@ -102,23 +127,6 @@ namespace osu.Game.Graphics.Containers
}
}
protected override Container<T> Content => scrollContentContainer;
private readonly UserTrackingScrollContainer scrollContainer;
private readonly Container headerBackgroundContainer;
private readonly MarginPadding originalSectionsMargin;
private Drawable expandableHeader, fixedHeader, footer, headerBackground;
private FlowContainer<T> scrollContentContainer;
private float? headerHeight, footerHeight;
private float? lastKnownScroll;
/// <summary>
/// The percentage of the container to consider the centre-point for deciding the active section (and scrolling to a requested section).
/// </summary>
private const float scroll_y_centre = 0.1f;
public SectionsContainer()
{
AddRangeInternal(new Drawable[]
@ -150,31 +158,63 @@ namespace osu.Game.Graphics.Containers
footerHeight = null;
}
private ScheduledDelegate? scrollToTargetDelegate;
public void ScrollTo(Drawable target)
{
Logger.Log($"Scrolling to {target}..");
lastKnownScroll = null;
// implementation similar to ScrollIntoView but a bit more nuanced.
float top = scrollContainer.GetChildPosInContent(target);
float scrollTarget = getScrollTargetForDrawable(target);
float bottomScrollExtent = scrollContainer.ScrollableExtent;
float scrollTarget = top - scrollContainer.DisplayableContent * scroll_y_centre;
if (scrollTarget > bottomScrollExtent)
if (scrollTarget > scrollContainer.ScrollableExtent)
scrollContainer.ScrollToEnd();
else
scrollContainer.ScrollTo(scrollTarget);
if (target is T section)
lastClickedSection = section;
// Content may load in as a scroll occurs, changing the scroll target we need to aim for.
// This scheduled operation ensures that we keep trying until actually arriving at the target.
scrollToTargetDelegate?.Cancel();
scrollToTargetDelegate = Scheduler.AddDelayed(() =>
{
if (scrollContainer.UserScrolling)
{
Logger.Log("Scroll operation interrupted by user scroll");
scrollToTargetDelegate?.Cancel();
scrollToTargetDelegate = null;
return;
}
if (Precision.AlmostEquals(scrollContainer.Current, scrollTarget, 1))
{
Logger.Log($"Finished scrolling to {target}!");
scrollToTargetDelegate?.Cancel();
scrollToTargetDelegate = null;
return;
}
if (!Precision.AlmostEquals(getScrollTargetForDrawable(target), scrollTarget, 1))
{
Logger.Log($"Reattempting scroll to {target} due to change in position");
ScrollTo(target);
}
}, 50, true);
}
private float getScrollTargetForDrawable(Drawable target)
{
// implementation similar to ScrollIntoView but a bit more nuanced.
return scrollContainer.GetChildPosInContent(target) - scrollContainer.DisplayableContent * scroll_y_centre;
}
public void ScrollToTop() => scrollContainer.ScrollTo(0);
[NotNull]
protected virtual UserTrackingScrollContainer CreateScrollContainer() => new UserTrackingScrollContainer();
[NotNull]
protected virtual FlowContainer<T> CreateScrollContentContainer() =>
new FillFlowContainer<T>
{

View File

@ -19,6 +19,7 @@ using osu.Framework.Platform;
using osu.Framework.Threading;
using osu.Game.Configuration;
using osu.Game.Input.Bindings;
using osu.Game.Online.Multiplayer;
using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
using SixLabors.ImageSharp;
@ -69,7 +70,7 @@ namespace osu.Game.Graphics
{
case GlobalAction.TakeScreenshot:
shutter.Play();
TakeScreenshotAsync();
TakeScreenshotAsync().FireAndForget();
return true;
}
@ -86,70 +87,75 @@ namespace osu.Game.Graphics
{
Interlocked.Increment(ref screenShotTasks);
if (!captureMenuCursor.Value)
try
{
cursorVisibility.Value = false;
// We need to wait for at most 3 draw nodes to be drawn, following which we can be assured at least one DrawNode has been generated/drawn with the set value
const int frames_to_wait = 3;
int framesWaited = 0;
using (var framesWaitedEvent = new ManualResetEventSlim(false))
if (!captureMenuCursor.Value)
{
ScheduledDelegate waitDelegate = host.DrawThread.Scheduler.AddDelayed(() =>
cursorVisibility.Value = false;
// We need to wait for at most 3 draw nodes to be drawn, following which we can be assured at least one DrawNode has been generated/drawn with the set value
const int frames_to_wait = 3;
int framesWaited = 0;
using (var framesWaitedEvent = new ManualResetEventSlim(false))
{
if (framesWaited++ >= frames_to_wait)
// ReSharper disable once AccessToDisposedClosure
framesWaitedEvent.Set();
}, 10, true);
ScheduledDelegate waitDelegate = host.DrawThread.Scheduler.AddDelayed(() =>
{
if (framesWaited++ >= frames_to_wait)
// ReSharper disable once AccessToDisposedClosure
framesWaitedEvent.Set();
}, 10, true);
if (!framesWaitedEvent.Wait(1000))
throw new TimeoutException("Screenshot data did not arrive in a timely fashion");
if (!framesWaitedEvent.Wait(1000))
throw new TimeoutException("Screenshot data did not arrive in a timely fashion");
waitDelegate.Cancel();
waitDelegate.Cancel();
}
}
using (var image = await host.TakeScreenshotAsync().ConfigureAwait(false))
{
host.GetClipboard()?.SetImage(image);
(string filename, var stream) = getWritableStream();
if (filename == null) return;
using (stream)
{
switch (screenshotFormat.Value)
{
case ScreenshotFormat.Png:
await image.SaveAsPngAsync(stream).ConfigureAwait(false);
break;
case ScreenshotFormat.Jpg:
const int jpeg_quality = 92;
await image.SaveAsJpegAsync(stream, new JpegEncoder { Quality = jpeg_quality }).ConfigureAwait(false);
break;
default:
throw new InvalidOperationException($"Unknown enum member {nameof(ScreenshotFormat)} {screenshotFormat.Value}.");
}
}
notificationOverlay.Post(new SimpleNotification
{
Text = $"Screenshot {filename} saved!",
Activated = () =>
{
storage.PresentFileExternally(filename);
return true;
}
});
}
}
using (var image = await host.TakeScreenshotAsync().ConfigureAwait(false))
finally
{
if (Interlocked.Decrement(ref screenShotTasks) == 0 && cursorVisibility.Value == false)
if (Interlocked.Decrement(ref screenShotTasks) == 0)
cursorVisibility.Value = true;
host.GetClipboard()?.SetImage(image);
(string filename, var stream) = getWritableStream();
if (filename == null) return;
using (stream)
{
switch (screenshotFormat.Value)
{
case ScreenshotFormat.Png:
await image.SaveAsPngAsync(stream).ConfigureAwait(false);
break;
case ScreenshotFormat.Jpg:
const int jpeg_quality = 92;
await image.SaveAsJpegAsync(stream, new JpegEncoder { Quality = jpeg_quality }).ConfigureAwait(false);
break;
default:
throw new InvalidOperationException($"Unknown enum member {nameof(ScreenshotFormat)} {screenshotFormat.Value}.");
}
}
notificationOverlay.Post(new SimpleNotification
{
Text = $"Screenshot {filename} saved!",
Activated = () =>
{
storage.PresentFileExternally(filename);
return true;
}
});
}
});

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
namespace osu.Game.Graphics.UserInterface
{
public enum MenuItemType

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
namespace osu.Game.Graphics.UserInterface
{
public enum SelectionState

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
namespace osu.Game.Graphics.UserInterface
{
/// <summary>

View File

@ -101,6 +101,10 @@ namespace osu.Game.Input.Bindings
new KeyBinding(new[] { InputKey.Control, InputKey.J }, GlobalAction.EditorFlipVertically),
new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.MouseWheelDown }, GlobalAction.EditorDecreaseDistanceSpacing),
new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.MouseWheelUp }, GlobalAction.EditorIncreaseDistanceSpacing),
// Framework automatically converts wheel up/down to left/right when shift is held.
// See https://github.com/ppy/osu-framework/blob/master/osu.Framework/Input/StateChanges/MouseScrollRelativeInput.cs#L37-L38.
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.MouseWheelRight }, GlobalAction.EditorCyclePreviousBeatSnapDivisor),
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.MouseWheelLeft }, GlobalAction.EditorCycleNextBeatSnapDivisor),
};
public IEnumerable<KeyBinding> InGameKeyBindings => new[]
@ -355,6 +359,12 @@ namespace osu.Game.Input.Bindings
ToggleProfile,
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorCloneSelection))]
EditorCloneSelection
EditorCloneSelection,
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorCyclePreviousBeatSnapDivisor))]
EditorCyclePreviousBeatSnapDivisor,
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorCycleNextBeatSnapDivisor))]
EditorCycleNextBeatSnapDivisor,
}
}

View File

@ -279,6 +279,16 @@ namespace osu.Game.Localisation
/// </summary>
public static LocalisableString EditorDecreaseDistanceSpacing => new TranslatableString(getKey(@"editor_decrease_distance_spacing"), @"Decrease distance spacing");
/// <summary>
/// "Cycle previous beat snap divisor"
/// </summary>
public static LocalisableString EditorCyclePreviousBeatSnapDivisor => new TranslatableString(getKey(@"editor_cycle_previous_beat_snap_divisor"), @"Cycle previous beat snap divisor");
/// <summary>
/// "Cycle next beat snap divisor"
/// </summary>
public static LocalisableString EditorCycleNextBeatSnapDivisor => new TranslatableString(getKey(@"editor_cycle_next_snap_divisor"), @"Cycle next beat snap divisor");
/// <summary>
/// "Toggle skin editor"
/// </summary>

View File

@ -22,6 +22,9 @@ namespace osu.Game.Localisation
[Description(@"Български")]
bg,
[Description(@"Català")]
ca,
[Description(@"Česky")]
cs,
@ -37,12 +40,27 @@ namespace osu.Game.Localisation
[Description(@"español")]
es,
// TODO: Requires Arabic glyphs to be added to resources (and possibly also RTL support).
// [Description(@"فارسی")]
// fa_ir,
[Description(@"Suomi")]
fi,
// TODO: Doesn't work as appropriate satellite assemblies aren't copied from resources (see: https://github.com/ppy/osu/discussions/18851#discussioncomment-3042170)
// [Description(@"Filipino")]
// fil,
[Description(@"français")]
fr,
// TODO: Requires Hebrew glyphs to be added to resources (and possibly also RTL support).
// [Description(@"עברית")]
// he,
[Description(@"Hrvatski")]
hr_hr,
[Description(@"Magyar")]
hu,
@ -58,6 +76,15 @@ namespace osu.Game.Localisation
[Description(@"한국어")]
ko,
[Description(@"Lietuvių")]
lt,
[Description(@"Latviešu")]
lv_lv,
[Description(@"Melayu")]
ms_my,
[Description(@"Nederlands")]
nl,
@ -79,12 +106,28 @@ namespace osu.Game.Localisation
[Description(@"Русский")]
ru,
// TODO: Requires Sinhala glyphs to be added to resources.
// Additionally, no translations available yet.
// [Description(@"සිංහල")]
// si_lk,
[Description(@"Slovenčina")]
sk,
[Description(@"Slovenščina")]
sl,
[Description(@"Српски")]
sr,
[Description(@"Svenska")]
sv,
// Tajik has no associated localisations yet, and is not supported on Windows versions <10.
// TODO: update language mapping in osu-resources to redirect tg-TJ to tg-Cyrl-TJ (which is supported on earlier Windows versions)
// [Description(@"Тоҷикӣ")]
// tg_tj,
[Description(@"ไทย")]
th,

View File

@ -82,7 +82,7 @@ namespace osu.Game.Localisation
/// <summary>
/// "{0}ms (speed {1})"
/// </summary>
public static LocalisableString ScrollSpeedTooltip(double arg0, int arg1) => new TranslatableString(getKey(@"ruleset"), @"{0}ms (speed {1})", arg0, arg1);
public static LocalisableString ScrollSpeedTooltip(int scrollTime, int scrollSpeed) => new TranslatableString(getKey(@"ruleset"), @"{0}ms (speed {1})", scrollTime, scrollSpeed);
private static string getKey(string key) => $@"{prefix}:{key}";
}

View File

@ -18,6 +18,7 @@ using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Logging;
using osu.Game.Configuration;
using osu.Game.Localisation;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Notifications;
@ -28,6 +29,7 @@ namespace osu.Game.Online.API
{
public partial class APIAccess : Component, IAPIProvider
{
private readonly OsuGameBase game;
private readonly OsuConfigManager config;
private readonly string versionHash;
@ -52,6 +54,8 @@ namespace osu.Game.Online.API
public IBindableList<APIUser> Friends => friends;
public IBindable<UserActivity> Activity => activity;
public Language Language => game.CurrentLanguage.Value;
private Bindable<APIUser> localUser { get; } = new Bindable<APIUser>(createGuestUser());
private BindableList<APIUser> friends { get; } = new BindableList<APIUser>();
@ -64,8 +68,9 @@ namespace osu.Game.Online.API
private readonly Logger log;
public APIAccess(OsuConfigManager config, EndpointConfiguration endpointConfiguration, string versionHash)
public APIAccess(OsuGameBase game, OsuConfigManager config, EndpointConfiguration endpointConfiguration, string versionHash)
{
this.game = game;
this.config = config;
this.versionHash = versionHash;

View File

@ -9,6 +9,7 @@ using JetBrains.Annotations;
using Newtonsoft.Json;
using osu.Framework.IO.Network;
using osu.Framework.Logging;
using osu.Game.Extensions;
using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Online.API
@ -116,10 +117,11 @@ namespace osu.Game.Online.API
WebRequest.Failed += Fail;
WebRequest.AllowRetryOnTimeout = false;
WebRequest.AddHeader("x-api-version", API.APIVersion.ToString(CultureInfo.InvariantCulture));
WebRequest.AddHeader(@"Accept-Language", API.Language.ToCultureCode());
WebRequest.AddHeader(@"x-api-version", API.APIVersion.ToString(CultureInfo.InvariantCulture));
if (!string.IsNullOrEmpty(API.AccessToken))
WebRequest.AddHeader("Authorization", $"Bearer {API.AccessToken}");
WebRequest.AddHeader(@"Authorization", $@"Bearer {API.AccessToken}");
if (isFailing) return;

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
namespace osu.Game.Online.API
{
public enum APIRequestCompletionState

View File

@ -8,6 +8,7 @@ using System.Threading;
using System.Threading.Tasks;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Localisation;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Notifications;
using osu.Game.Tests;
@ -29,6 +30,8 @@ namespace osu.Game.Online.API
public Bindable<UserActivity> Activity { get; } = new Bindable<UserActivity>();
public Language Language => Language.en;
public string AccessToken => "token";
public bool IsLoggedIn => State.Value == APIState.Online;

View File

@ -4,6 +4,7 @@
using System;
using System.Threading.Tasks;
using osu.Framework.Bindables;
using osu.Game.Localisation;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Notifications;
using osu.Game.Users;
@ -27,6 +28,11 @@ namespace osu.Game.Online.API
/// </summary>
IBindable<UserActivity> Activity { get; }
/// <summary>
/// The language supplied by this provider to API requests.
/// </summary>
Language Language { get; }
/// <summary>
/// Retrieve the OAuth access token.
/// </summary>

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
namespace osu.Game.Online.Chat
{
public enum ChannelType

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
namespace osu.Game.Online.Leaderboards
{
public enum LeaderboardState

View File

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

View File

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

View File

@ -783,7 +783,7 @@ namespace osu.Game.Online.Multiplayer
RoomUpdated?.Invoke();
}
private PlaylistItem createPlaylistItem(MultiplayerPlaylistItem item) => new PlaylistItem(new APIBeatmap { OnlineID = item.BeatmapID })
private PlaylistItem createPlaylistItem(MultiplayerPlaylistItem item) => new PlaylistItem(new APIBeatmap { OnlineID = item.BeatmapID, StarRating = item.StarRating })
{
ID = item.ID,
OwnerID = item.OwnerID,

View File

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

View File

@ -53,22 +53,12 @@ namespace osu.Game.Online.Rooms
[Key(9)]
public DateTimeOffset? PlayedAt { get; set; }
[Key(10)]
public double StarRating { get; set; }
[SerializationConstructor]
public MultiplayerPlaylistItem()
{
}
public MultiplayerPlaylistItem(PlaylistItem item)
{
ID = item.ID;
OwnerID = item.OwnerID;
BeatmapID = item.Beatmap.OnlineID;
BeatmapChecksum = item.Beatmap.MD5Hash;
RulesetID = item.RulesetID;
RequiredMods = item.RequiredMods.ToArray();
AllowedMods = item.AllowedMods.ToArray();
Expired = item.Expired;
PlaylistOrder = item.PlaylistOrder ?? 0;
PlayedAt = item.PlayedAt;
}
}
}

View File

@ -91,7 +91,7 @@ namespace osu.Game.Online.Rooms
}
public PlaylistItem(MultiplayerPlaylistItem item)
: this(new APIBeatmap { OnlineID = item.BeatmapID })
: this(new APIBeatmap { OnlineID = item.BeatmapID, StarRating = item.StarRating })
{
ID = item.ID;
OwnerID = item.OwnerID;

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
namespace osu.Game.Online.Spectator
{
public enum SpectatedUserState

View File

@ -1136,12 +1136,22 @@ namespace osu.Game
if (entry.Level == LogLevel.Error)
{
Schedule(() => Notifications.Post(new SimpleNotification
Schedule(() =>
{
Text = $"Encountered tablet error: \"{message}\"",
Icon = FontAwesome.Solid.PenSquare,
IconColour = Colours.RedDark,
}));
Notifications.Post(new SimpleNotification
{
Text = $"Disabling tablet support due to error: \"{message}\"",
Icon = FontAwesome.Solid.PenSquare,
IconColour = Colours.RedDark,
});
// We only have one tablet handler currently.
// The loop here is weakly guarding against a future where more than one is added.
// If this is ever the case, this logic needs adjustment as it should probably only
// disable the relevant tablet handler rather than all.
foreach (var tabletHandler in Host.AvailableInputHandlers.OfType<ITabletHandler>())
tabletHandler.Enabled.Value = false;
});
}
else if (notifyOnWarning)
{

View File

@ -14,6 +14,7 @@ using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Bindables;
using osu.Framework.Configuration;
using osu.Framework.Development;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
@ -27,6 +28,7 @@ using osu.Framework.Input.Handlers.Mouse;
using osu.Framework.Input.Handlers.Tablet;
using osu.Framework.Input.Handlers.Touch;
using osu.Framework.IO.Stores;
using osu.Framework.Localisation;
using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Framework.Timing;
@ -36,11 +38,13 @@ using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Beatmaps.Formats;
using osu.Game.Configuration;
using osu.Game.Database;
using osu.Game.Extensions;
using osu.Game.Graphics;
using osu.Game.Graphics.Cursor;
using osu.Game.Input;
using osu.Game.Input.Bindings;
using osu.Game.IO;
using osu.Game.Localisation;
using osu.Game.Online;
using osu.Game.Online.API;
using osu.Game.Online.Chat;
@ -157,6 +161,11 @@ namespace osu.Game
protected Storage Storage { get; set; }
/// <summary>
/// The language in which the game is currently displayed in.
/// </summary>
public Bindable<Language> CurrentLanguage { get; } = new Bindable<Language>();
protected Bindable<WorkingBeatmap> Beatmap { get; private set; } // cached via load() method
/// <summary>
@ -216,6 +225,10 @@ namespace osu.Game
private readonly BindableNumber<double> globalTrackVolumeAdjust = new BindableNumber<double>(global_track_volume_adjust);
private Bindable<string> frameworkLocale = null!;
private IBindable<LocalisationParameters> localisationParameters = null!;
/// <summary>
/// Number of unhandled exceptions to allow before aborting execution.
/// </summary>
@ -238,7 +251,7 @@ namespace osu.Game
}
[BackgroundDependencyLoader]
private void load(ReadableKeyCombinationProvider keyCombinationProvider)
private void load(ReadableKeyCombinationProvider keyCombinationProvider, FrameworkConfigManager frameworkConfig)
{
try
{
@ -283,7 +296,15 @@ namespace osu.Game
MessageFormatter.WebsiteRootUrl = endpoints.WebsiteRootUrl;
dependencies.CacheAs(API ??= new APIAccess(LocalConfig, endpoints, VersionHash));
frameworkLocale = frameworkConfig.GetBindable<string>(FrameworkSetting.Locale);
frameworkLocale.BindValueChanged(_ => updateLanguage());
localisationParameters = Localisation.CurrentParameters.GetBoundCopy();
localisationParameters.BindValueChanged(_ => updateLanguage(), true);
CurrentLanguage.BindValueChanged(val => frameworkLocale.Value = val.NewValue.ToCultureCode());
dependencies.CacheAs(API ??= new APIAccess(this, LocalConfig, endpoints, VersionHash));
var defaultBeatmap = new DummyWorkingBeatmap(Audio, Textures);
@ -394,6 +415,8 @@ namespace osu.Game
Beatmap.BindValueChanged(onBeatmapChanged);
}
private void updateLanguage() => CurrentLanguage.Value = LanguageExtensions.GetLanguageFor(frameworkLocale.Value, localisationParameters.Value);
private void addFilesWarning()
{
var realmStore = new RealmFileStore(realm, Storage);

View File

@ -56,11 +56,11 @@ namespace osu.Game.Overlays.Chat
[Resolved]
private OverlayColourProvider? colourProvider { get; set; }
private readonly OsuSpriteText drawableTimestamp;
private OsuSpriteText drawableTimestamp = null!;
private readonly DrawableChatUsername drawableUsername;
private DrawableChatUsername drawableUsername = null!;
private readonly LinkFlowContainer drawableContentFlow;
private LinkFlowContainer drawableContentFlow = null!;
private readonly Bindable<bool> prefer24HourTime = new Bindable<bool>();
@ -69,8 +69,16 @@ namespace osu.Game.Overlays.Chat
public ChatLine(Message message)
{
Message = message;
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
}
[BackgroundDependencyLoader]
private void load(OsuConfigManager configManager)
{
configManager.BindWith(OsuSetting.Prefer24HourTime, prefer24HourTime);
prefer24HourTime.BindValueChanged(_ => updateTimestamp());
InternalChild = new GridContainer
{
@ -103,7 +111,6 @@ namespace osu.Game.Overlays.Chat
Origin = Anchor.TopRight,
Anchor = Anchor.TopRight,
Margin = new MarginPadding { Horizontal = Spacing },
ReportRequested = this.ShowPopover,
},
drawableContentFlow = new LinkFlowContainer(styleMessageContent)
{
@ -115,13 +122,6 @@ namespace osu.Game.Overlays.Chat
};
}
[BackgroundDependencyLoader]
private void load(OsuConfigManager configManager)
{
configManager.BindWith(OsuSetting.Prefer24HourTime, prefer24HourTime);
prefer24HourTime.BindValueChanged(_ => updateTimestamp());
}
protected override void LoadComplete()
{
base.LoadComplete();
@ -130,6 +130,17 @@ namespace osu.Game.Overlays.Chat
updateMessageContent();
FinishTransforms(true);
if (this.FindClosestParent<PopoverContainer>() != null)
{
// This guards against cases like in-game chat where there's no available popover container.
// There may be a future where a global one becomes available, at which point this code may be unnecessary.
//
// See:
// https://github.com/ppy/osu/pull/23698
// https://github.com/ppy/osu/pull/14554
drawableUsername.ReportRequested = this.ShowPopover;
}
}
public Popover GetPopover() => new ReportChatPopover(message);

View File

@ -13,7 +13,6 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Framework.Threading;
using osu.Game.Extensions;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
@ -68,13 +67,12 @@ namespace osu.Game.Overlays.FirstRunSetup
private partial class LanguageSelectionFlow : FillFlowContainer
{
private Bindable<string> frameworkLocale = null!;
private IBindable<LocalisationParameters> localisationParameters = null!;
private Bindable<Language> language = null!;
private ScheduledDelegate? updateSelectedDelegate;
[BackgroundDependencyLoader]
private void load(FrameworkConfigManager frameworkConfig, LocalisationManager localisation)
private void load(OsuGameBase game)
{
Direction = FillDirection.Full;
Spacing = new Vector2(5);
@ -82,25 +80,18 @@ namespace osu.Game.Overlays.FirstRunSetup
ChildrenEnumerable = Enum.GetValues<Language>()
.Select(l => new LanguageButton(l)
{
Action = () => frameworkLocale.Value = l.ToCultureCode()
Action = () => language.Value = l,
});
frameworkLocale = frameworkConfig.GetBindable<string>(FrameworkSetting.Locale);
frameworkLocale.BindValueChanged(_ => onLanguageChange());
localisationParameters = localisation.CurrentParameters.GetBoundCopy();
localisationParameters.BindValueChanged(_ => onLanguageChange(), true);
}
private void onLanguageChange()
{
var language = LanguageExtensions.GetLanguageFor(frameworkLocale.Value, localisationParameters.Value);
// Changing language may cause a short period of blocking the UI thread while the new glyphs are loaded.
// Scheduling ensures the button animation plays smoothly after any blocking operation completes.
// Note that a delay is required (the alternative would be a double-schedule; delay feels better).
updateSelectedDelegate?.Cancel();
updateSelectedDelegate = Scheduler.AddDelayed(() => updateSelectedStates(language), 50);
language = game.CurrentLanguage.GetBoundCopy();
language.BindValueChanged(v =>
{
// Changing language may cause a short period of blocking the UI thread while the new glyphs are loaded.
// Scheduling ensures the button animation plays smoothly after any blocking operation completes.
// Note that a delay is required (the alternative would be a double-schedule; delay feels better).
updateSelectedDelegate?.Cancel();
updateSelectedDelegate = Scheduler.AddDelayed(() => updateSelectedStates(v.NewValue), 50);
}, true);
}
private void updateSelectedStates(Language language)

View File

@ -415,7 +415,7 @@ namespace osu.Game.Overlays
[BackgroundDependencyLoader]
private void load(LargeTextureStore textures)
{
sprite.Texture = beatmap?.Background ?? textures.Get(@"Backgrounds/bg4");
sprite.Texture = beatmap?.GetBackground() ?? textures.Get(@"Backgrounds/bg4");
}
}

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
namespace osu.Game.Overlays
{
public enum OverlayActivation

View File

@ -2,35 +2,27 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Extensions;
using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.General
{
public partial class LanguageSettings : SettingsSubsection
{
private SettingsDropdown<Language> languageSelection = null!;
private Bindable<string> frameworkLocale = null!;
private IBindable<LocalisationParameters> localisationParameters = null!;
protected override LocalisableString Header => GeneralSettingsStrings.LanguageHeader;
[BackgroundDependencyLoader]
private void load(FrameworkConfigManager frameworkConfig, OsuConfigManager config, LocalisationManager localisation)
private void load(OsuGameBase game, OsuConfigManager config, FrameworkConfigManager frameworkConfig)
{
frameworkLocale = frameworkConfig.GetBindable<string>(FrameworkSetting.Locale);
localisationParameters = localisation.CurrentParameters.GetBoundCopy();
Children = new Drawable[]
{
languageSelection = new SettingsEnumDropdown<Language>
new SettingsEnumDropdown<Language>
{
LabelText = GeneralSettingsStrings.LanguageDropdown,
Current = game.CurrentLanguage,
},
new SettingsCheckbox
{
@ -43,14 +35,6 @@ namespace osu.Game.Overlays.Settings.Sections.General
Current = config.GetBindable<bool>(OsuSetting.Prefer24HourTime)
},
};
frameworkLocale.BindValueChanged(_ => updateSelection());
localisationParameters.BindValueChanged(_ => updateSelection(), true);
languageSelection.Current.BindValueChanged(val => frameworkLocale.Value = val.NewValue.ToCultureCode());
}
private void updateSelection() =>
languageSelection.Current.Value = LanguageExtensions.GetLanguageFor(frameworkLocale.Value, localisationParameters.Value);
}
}

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