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:
commit
4b3b22f046
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -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!
|
||||
|
@ -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" />
|
||||
|
180
osu.Game.Rulesets.Catch/Edit/CatchBeatSnapGrid.cs
Normal file
180
osu.Game.Rulesets.Catch/Edit/CatchBeatSnapGrid.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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.
|
||||
|
@ -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(),
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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()
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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", () =>
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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.";
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
};
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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:
|
||||
|
@ -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),
|
||||
|
@ -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)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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");
|
||||
}
|
||||
|
||||
|
@ -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")}", () =>
|
||||
{
|
||||
|
@ -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>();
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -129,7 +129,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
Playlist =
|
||||
{
|
||||
new MultiplayerPlaylistItem(playlistItem),
|
||||
TestMultiplayerClient.CreateMultiplayerPlaylistItem(playlistItem),
|
||||
},
|
||||
Users = { localUser },
|
||||
Host = localUser,
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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]
|
||||
|
@ -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"))
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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));
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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"/>.
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -160,7 +160,7 @@ namespace osu.Game.Beatmaps
|
||||
}
|
||||
}
|
||||
|
||||
protected override Texture GetBackground()
|
||||
public override Texture GetBackground()
|
||||
{
|
||||
if (string.IsNullOrEmpty(Metadata?.BackgroundFile))
|
||||
return null;
|
||||
|
@ -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"
|
||||
},
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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"/>,
|
||||
|
@ -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)
|
||||
|
@ -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>
|
||||
{
|
||||
|
@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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,
|
||||
|
||||
|
@ -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}";
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user