mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 07:07:45 +08:00
Merge branch 'master' into diffcalc-total-scorev1
This commit is contained in:
commit
0844a21a51
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -2,7 +2,7 @@ blank_issues_enabled: false
|
|||||||
contact_links:
|
contact_links:
|
||||||
- name: Help
|
- name: Help
|
||||||
url: https://github.com/ppy/osu/discussions/categories/q-a
|
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
|
- name: Suggestions or feature request
|
||||||
url: https://github.com/ppy/osu/discussions/categories/ideas
|
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!
|
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>
|
<AndroidManifestMerger>manifestmerger.jar</AndroidManifestMerger>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2023.521.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2023.608.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<AndroidManifestOverlay Include="$(MSBuildThisFileDirectory)osu.Android\Properties\AndroidManifestOverlay.xml" />
|
<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 InputManager inputManager = null!;
|
||||||
|
|
||||||
|
private CatchBeatSnapGrid beatSnapGrid = null!;
|
||||||
|
|
||||||
private readonly BindableDouble timeRangeMultiplier = new BindableDouble(1)
|
private readonly BindableDouble timeRangeMultiplier = new BindableDouble(1)
|
||||||
{
|
{
|
||||||
MinValue = 1,
|
MinValue = 1,
|
||||||
@ -65,6 +67,8 @@ namespace osu.Game.Rulesets.Catch.Edit
|
|||||||
Catcher.BASE_DASH_SPEED, -Catcher.BASE_DASH_SPEED,
|
Catcher.BASE_DASH_SPEED, -Catcher.BASE_DASH_SPEED,
|
||||||
Catcher.BASE_WALK_SPEED, -Catcher.BASE_WALK_SPEED,
|
Catcher.BASE_WALK_SPEED, -Catcher.BASE_WALK_SPEED,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
AddInternal(beatSnapGrid = new CatchBeatSnapGrid());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
@ -74,6 +78,29 @@ namespace osu.Game.Rulesets.Catch.Edit
|
|||||||
inputManager = GetContainingInputManager();
|
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)
|
protected override double ReadCurrentDistanceSnap(HitObject before, HitObject after)
|
||||||
{
|
{
|
||||||
// osu!catch's distance snap implementation is limited, in that a custom spacing cannot be specified.
|
// 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.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||||
@ -41,6 +42,8 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
internal CatcherArea CatcherArea { get; private set; } = null!;
|
internal CatcherArea CatcherArea { get; private set; } = null!;
|
||||||
|
|
||||||
|
public Container UnderlayElements { get; private set; } = null!;
|
||||||
|
|
||||||
private readonly IBeatmapDifficultyInfo difficulty;
|
private readonly IBeatmapDifficultyInfo difficulty;
|
||||||
|
|
||||||
public CatchPlayfield(IBeatmapDifficultyInfo difficulty)
|
public CatchPlayfield(IBeatmapDifficultyInfo difficulty)
|
||||||
@ -62,6 +65,10 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
AddRangeInternal(new[]
|
AddRangeInternal(new[]
|
||||||
{
|
{
|
||||||
|
UnderlayElements = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
droppedObjectContainer,
|
droppedObjectContainer,
|
||||||
Catcher.CreateProxiedContent(),
|
Catcher.CreateProxiedContent(),
|
||||||
HitObjectContainer.CreateProxy(),
|
HitObjectContainer.CreateProxy(),
|
||||||
|
@ -21,18 +21,29 @@ namespace osu.Game.Rulesets.Mania.Configuration
|
|||||||
{
|
{
|
||||||
base.InitialiseDefaults();
|
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.ScrollDirection, ManiaScrollingDirection.Down);
|
||||||
SetDefault(ManiaRulesetSetting.TimingBasedNoteColouring, false);
|
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
|
public override TrackedSettings CreateTrackedSettings() => new TrackedSettings
|
||||||
{
|
{
|
||||||
new TrackedSetting<double>(ManiaRulesetSetting.ScrollTime,
|
new TrackedSetting<int>(ManiaRulesetSetting.ScrollSpeed,
|
||||||
scrollTime => new SettingDescription(
|
speed => new SettingDescription(
|
||||||
rawValue: scrollTime,
|
rawValue: speed,
|
||||||
name: RulesetSettingsStrings.ScrollSpeed,
|
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
|
public enum ManiaRulesetSetting
|
||||||
{
|
{
|
||||||
|
[Obsolete("Use ScrollSpeed instead.")] // Can be removed 2023-11-30
|
||||||
ScrollTime,
|
ScrollTime,
|
||||||
|
ScrollSpeed,
|
||||||
ScrollDirection,
|
ScrollDirection,
|
||||||
TimingBasedNoteColouring
|
TimingBasedNoteColouring
|
||||||
}
|
}
|
||||||
|
@ -129,7 +129,7 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
}
|
}
|
||||||
|
|
||||||
Color4 colour = BindableBeatDivisor.GetColourFor(
|
Color4 colour = BindableBeatDivisor.GetColourFor(
|
||||||
BindableBeatDivisor.GetDivisorForBeatIndex(Math.Max(1, beat), beatDivisor.Value), colours);
|
BindableBeatDivisor.GetDivisorForBeatIndex(beat, beatDivisor.Value), colours);
|
||||||
|
|
||||||
foreach (var grid in grids)
|
foreach (var grid in grids)
|
||||||
{
|
{
|
||||||
|
@ -389,41 +389,23 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
return base.GetDisplayNameForHitResult(result);
|
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[]
|
RelativeSizeAxes = Axes.X,
|
||||||
{
|
AutoSizeAxes = Axes.Y
|
||||||
new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score, playableBeatmap)
|
}),
|
||||||
{
|
new StatisticItem("Timing Distribution", () => new HitEventTimingDistributionGraph(score.HitEvents)
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
AutoSizeAxes = Axes.Y
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new StatisticRow
|
|
||||||
{
|
{
|
||||||
Columns = new[]
|
RelativeSizeAxes = Axes.X,
|
||||||
{
|
Height = 250
|
||||||
new StatisticItem("Timing Distribution", () => new HitEventTimingDistributionGraph(score.HitEvents)
|
}, true),
|
||||||
{
|
new StatisticItem(string.Empty, () => new SimpleStatisticTable(3, new SimpleStatisticItem[]
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
Height = 250
|
|
||||||
}, true),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new StatisticRow
|
|
||||||
{
|
{
|
||||||
Columns = new[]
|
new AverageHitError(score.HitEvents),
|
||||||
{
|
new UnstableRate(score.HitEvents)
|
||||||
new StatisticItem(string.Empty, () => new SimpleStatisticTable(3, new SimpleStatisticItem[]
|
}), true)
|
||||||
{
|
|
||||||
new AverageHitError(score.HitEvents),
|
|
||||||
new UnstableRate(score.HitEvents)
|
|
||||||
}), true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public override IRulesetFilterCriteria CreateRulesetFilterCriteria()
|
public override IRulesetFilterCriteria CreateRulesetFilterCriteria()
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
@ -34,10 +33,10 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
LabelText = RulesetSettingsStrings.ScrollingDirection,
|
LabelText = RulesetSettingsStrings.ScrollingDirection,
|
||||||
Current = config.GetBindable<ManiaScrollingDirection>(ManiaRulesetSetting.ScrollDirection)
|
Current = config.GetBindable<ManiaScrollingDirection>(ManiaRulesetSetting.ScrollDirection)
|
||||||
},
|
},
|
||||||
new SettingsSlider<double, ManiaScrollSlider>
|
new SettingsSlider<int, ManiaScrollSlider>
|
||||||
{
|
{
|
||||||
LabelText = RulesetSettingsStrings.ScrollSpeed,
|
LabelText = RulesetSettingsStrings.ScrollSpeed,
|
||||||
Current = config.GetBindable<double>(ManiaRulesetSetting.ScrollTime),
|
Current = config.GetBindable<int>(ManiaRulesetSetting.ScrollSpeed),
|
||||||
KeyboardStep = 5
|
KeyboardStep = 5
|
||||||
},
|
},
|
||||||
new SettingsCheckbox
|
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.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Objects;
|
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:
|
// 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.
|
// 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.
|
// 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.
|
// How far past the hit target this hold note is.
|
||||||
float yOffset = Direction.Value == ScrollingDirection.Up ? -Y : Y;
|
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.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Objects
|
namespace osu.Game.Rulesets.Mania.Objects
|
||||||
{
|
{
|
||||||
public class HeadNote : Note
|
public class HeadNote : Note
|
||||||
|
@ -18,8 +18,8 @@ namespace osu.Game.Rulesets.Mania.Scoring
|
|||||||
|
|
||||||
protected override double ComputeTotalScore(double comboProgress, double accuracyProgress, double bonusPortion)
|
protected override double ComputeTotalScore(double comboProgress, double accuracyProgress, double bonusPortion)
|
||||||
{
|
{
|
||||||
return 200000 * comboProgress
|
return 10000 * comboProgress
|
||||||
+ 800000 * Math.Pow(Accuracy.Value, 2 + 2 * Accuracy.Value) * accuracyProgress
|
+ 990000 * Math.Pow(Accuracy.Value, 2 + 2 * Accuracy.Value) * accuracyProgress
|
||||||
+ bonusPortion;
|
+ bonusPortion;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,11 +139,11 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
case 3:
|
case 3:
|
||||||
switch (columnIndex)
|
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();
|
default: throw new ArgumentOutOfRangeException();
|
||||||
}
|
}
|
||||||
@ -185,11 +185,11 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
|
|
||||||
case 1: return colour_orange;
|
case 1: return colour_orange;
|
||||||
|
|
||||||
case 2: return colour_yellow;
|
case 2: return colour_green;
|
||||||
|
|
||||||
case 3: return colour_cyan;
|
case 3: return colour_cyan;
|
||||||
|
|
||||||
case 4: return colour_purple;
|
case 4: return colour_orange;
|
||||||
|
|
||||||
case 5: return colour_pink;
|
case 5: return colour_pink;
|
||||||
|
|
||||||
@ -201,17 +201,17 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
{
|
{
|
||||||
case 0: return colour_pink;
|
case 0: return colour_pink;
|
||||||
|
|
||||||
case 1: return colour_cyan;
|
case 1: return colour_orange;
|
||||||
|
|
||||||
case 2: return colour_pink;
|
case 2: return colour_pink;
|
||||||
|
|
||||||
case 3: return colour_special_column;
|
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();
|
default: throw new ArgumentOutOfRangeException();
|
||||||
}
|
}
|
||||||
@ -225,9 +225,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
|
|
||||||
case 2: return colour_orange;
|
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;
|
case 5: return colour_orange;
|
||||||
|
|
||||||
@ -273,9 +273,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
|
|
||||||
case 3: return colour_yellow;
|
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;
|
case 6: return colour_yellow;
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ using osu.Framework.Graphics.Pooling;
|
|||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
|
using osu.Game.Extensions;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
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 Bindable<ManiaAction> Action = new Bindable<ManiaAction>();
|
||||||
|
|
||||||
public readonly ColumnHitObjectArea HitObjectArea;
|
public readonly ColumnHitObjectArea HitObjectArea;
|
||||||
|
|
||||||
|
internal readonly Container BackgroundContainer = new Container { RelativeSizeAxes = Axes.Both };
|
||||||
|
|
||||||
internal readonly Container TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both };
|
internal readonly Container TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both };
|
||||||
|
|
||||||
private DrawablePool<PoolableHitExplosion> hitExplosionPool;
|
private DrawablePool<PoolableHitExplosion> hitExplosionPool;
|
||||||
private readonly OrderedHitPolicy hitPolicy;
|
private readonly OrderedHitPolicy hitPolicy;
|
||||||
public Container UnderlayElements => HitObjectArea.UnderlayElements;
|
public Container UnderlayElements => HitObjectArea.UnderlayElements;
|
||||||
@ -76,30 +81,31 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
skin.SourceChanged += onSourceChanged;
|
skin.SourceChanged += onSourceChanged;
|
||||||
onSourceChanged();
|
onSourceChanged();
|
||||||
|
|
||||||
Drawable background = new SkinnableDrawable(new ManiaSkinComponentLookup(ManiaSkinComponents.ColumnBackground), _ => new DefaultColumnBackground())
|
InternalChildren = new Drawable[]
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
};
|
|
||||||
|
|
||||||
InternalChildren = new[]
|
|
||||||
{
|
{
|
||||||
hitExplosionPool = new DrawablePool<PoolableHitExplosion>(5),
|
hitExplosionPool = new DrawablePool<PoolableHitExplosion>(5),
|
||||||
sampleTriggerSource = new GameplaySampleTriggerSource(HitObjectContainer),
|
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,
|
HitObjectArea,
|
||||||
keyArea = new SkinnableDrawable(new ManiaSkinComponentLookup(ManiaSkinComponents.KeyArea), _ => new DefaultKeyArea())
|
keyArea = new SkinnableDrawable(new ManiaSkinComponentLookup(ManiaSkinComponents.KeyArea), _ => new DefaultKeyArea())
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
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,
|
TopLevelContainer,
|
||||||
new ColumnTouchInputArea(this)
|
new ColumnTouchInputArea(this)
|
||||||
};
|
};
|
||||||
|
|
||||||
applyGameWideClock(background);
|
var background = new SkinnableDrawable(new ManiaSkinComponentLookup(ManiaSkinComponents.ColumnBackground), _ => new DefaultColumnBackground())
|
||||||
applyGameWideClock(keyArea);
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
};
|
||||||
|
|
||||||
|
background.ApplyGameWideClock(host);
|
||||||
|
keyArea.ApplyGameWideClock(host);
|
||||||
|
|
||||||
|
BackgroundContainer.Add(background);
|
||||||
TopLevelContainer.Add(HitObjectArea.Explosions.CreateProxy());
|
TopLevelContainer.Add(HitObjectArea.Explosions.CreateProxy());
|
||||||
|
|
||||||
RegisterPool<Note, DrawableNote>(10, 50);
|
RegisterPool<Note, DrawableNote>(10, 50);
|
||||||
@ -107,18 +113,6 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
RegisterPool<HeadNote, DrawableHoldNoteHead>(10, 50);
|
RegisterPool<HeadNote, DrawableHoldNoteHead>(10, 50);
|
||||||
RegisterPool<TailNote, DrawableHoldNoteTail>(10, 50);
|
RegisterPool<TailNote, DrawableHoldNoteTail>(10, 50);
|
||||||
RegisterPool<HoldNoteTick, DrawableHoldNoteTick>(50, 250);
|
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()
|
private void onSourceChanged()
|
||||||
|
@ -33,12 +33,12 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
public partial class DrawableManiaRuleset : DrawableScrollingRuleset<ManiaHitObject>
|
public partial class DrawableManiaRuleset : DrawableScrollingRuleset<ManiaHitObject>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
public const double MIN_TIME_RANGE = 290;
|
public const double MIN_TIME_RANGE = 290;
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
public const double MAX_TIME_RANGE = 11485;
|
public const double MAX_TIME_RANGE = 11485;
|
||||||
|
|
||||||
@ -69,7 +69,8 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
protected override ScrollVisualisationMethod VisualisationMethod => scrollMethod;
|
protected override ScrollVisualisationMethod VisualisationMethod => scrollMethod;
|
||||||
|
|
||||||
private readonly Bindable<ManiaScrollingDirection> configDirection = new Bindable<ManiaScrollingDirection>();
|
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.
|
// Stores the current speed adjustment active in gameplay.
|
||||||
private readonly Track speedAdjustmentTrack = new TrackVirtual(0);
|
private readonly Track speedAdjustmentTrack = new TrackVirtual(0);
|
||||||
@ -78,6 +79,9 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
: base(ruleset, beatmap, mods)
|
: base(ruleset, beatmap, mods)
|
||||||
{
|
{
|
||||||
BarLines = new BarLineGenerator<BarLine>(Beatmap).BarLines;
|
BarLines = new BarLineGenerator<BarLine>(Beatmap).BarLines;
|
||||||
|
|
||||||
|
TimeRange.MinValue = 1;
|
||||||
|
TimeRange.MaxValue = MAX_TIME_RANGE;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -104,30 +108,28 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
Config.BindWith(ManiaRulesetSetting.ScrollDirection, configDirection);
|
Config.BindWith(ManiaRulesetSetting.ScrollDirection, configDirection);
|
||||||
configDirection.BindValueChanged(direction => Direction.Value = (ScrollingDirection)direction.NewValue, true);
|
configDirection.BindValueChanged(direction => Direction.Value = (ScrollingDirection)direction.NewValue, true);
|
||||||
|
|
||||||
Config.BindWith(ManiaRulesetSetting.ScrollTime, configTimeRange);
|
Config.BindWith(ManiaRulesetSetting.ScrollSpeed, configScrollSpeed);
|
||||||
TimeRange.MinValue = configTimeRange.MinValue;
|
configScrollSpeed.BindValueChanged(speed => this.TransformTo(nameof(smoothTimeRange), ComputeScrollTime(speed.NewValue), 200, Easing.OutQuint));
|
||||||
TimeRange.MaxValue = configTimeRange.MaxValue;
|
|
||||||
|
TimeRange.Value = smoothTimeRange = ComputeScrollTime(configScrollSpeed.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void AdjustScrollSpeed(int amount)
|
protected override void AdjustScrollSpeed(int amount) => configScrollSpeed.Value += 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 Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
updateTimeRange();
|
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();
|
public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new ManiaPlayfieldAdjustmentContainer();
|
||||||
|
|
||||||
|
@ -60,6 +60,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
RelativeSizeAxes = Axes.Y;
|
RelativeSizeAxes = Axes.Y;
|
||||||
AutoSizeAxes = Axes.X;
|
AutoSizeAxes = Axes.X;
|
||||||
|
|
||||||
|
Container columnBackgrounds;
|
||||||
Container topLevelContainer;
|
Container topLevelContainer;
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
@ -77,9 +78,10 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
},
|
},
|
||||||
columnFlow = new ColumnFlow<Column>(definition)
|
columnBackgrounds = new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Y,
|
Name = "Column backgrounds",
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
},
|
},
|
||||||
new Container
|
new Container
|
||||||
{
|
{
|
||||||
@ -98,6 +100,10 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
RelativeSizeAxes = Axes.Y,
|
RelativeSizeAxes = Axes.Y,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
columnFlow = new ColumnFlow<Column>(definition)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
},
|
||||||
new SkinnableDrawable(new ManiaSkinComponentLookup(ManiaSkinComponents.StageForeground), _ => null)
|
new SkinnableDrawable(new ManiaSkinComponentLookup(ManiaSkinComponents.StageForeground), _ => null)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
@ -126,6 +132,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
};
|
};
|
||||||
|
|
||||||
topLevelContainer.Add(column.TopLevelContainer.CreateProxy());
|
topLevelContainer.Add(column.TopLevelContainer.CreateProxy());
|
||||||
|
columnBackgrounds.Add(column.BackgroundContainer.CreateProxy());
|
||||||
columnFlow.SetContentForColumn(i, column);
|
columnFlow.SetContentForColumn(i, column);
|
||||||
AddNested(column);
|
AddNested(column);
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
|
|
||||||
AddStep("place first object", () => InputManager.Click(MouseButton.Left));
|
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));
|
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("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));
|
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("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", () =>
|
AddAssert("object 3 snapped to 1", () =>
|
||||||
{
|
{
|
||||||
@ -134,7 +134,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
return Precision.AlmostEquals(first.EndPosition, third.Position);
|
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", () =>
|
AddAssert("object 2 snapped to 1", () =>
|
||||||
{
|
{
|
||||||
|
@ -1,22 +1,41 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Extensions.ObjectExtensions;
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Skinning.Default;
|
using osu.Game.Rulesets.Osu.Skinning.Default;
|
||||||
using osu.Game.Rulesets.Osu.UI;
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||||
{
|
{
|
||||||
public partial class TestSceneOsuModAutoplay : OsuModTestScene
|
public partial class TestSceneOsuModAutoplay : OsuModTestScene
|
||||||
{
|
{
|
||||||
|
protected override bool AllowFail => true;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCursorPositionStoredToJudgement()
|
||||||
|
{
|
||||||
|
CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Autoplay = true,
|
||||||
|
PassCondition = () =>
|
||||||
|
Player.ScoreProcessor.JudgedHits >= 1
|
||||||
|
&& Player.ScoreProcessor.HitEvents.Any(e => e.Position != null)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestSpmUnaffectedByRateAdjust()
|
public void TestSpmUnaffectedByRateAdjust()
|
||||||
=> runSpmTest(new OsuModDaycore
|
=> runSpmTest(new OsuModDaycore
|
||||||
@ -32,6 +51,36 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
|||||||
FinalRate = { Value = 1.3 }
|
FinalRate = { Value = 1.3 }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPerfectScoreOnShortSliderWithRepeat()
|
||||||
|
{
|
||||||
|
AddStep("set score to standardised", () => LocalConfig.SetValue(OsuSetting.ScoreDisplayMode, ScoringMode.Standardised));
|
||||||
|
|
||||||
|
CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Autoplay = true,
|
||||||
|
Beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new Slider
|
||||||
|
{
|
||||||
|
StartTime = 500,
|
||||||
|
Position = new Vector2(256, 192),
|
||||||
|
Path = new SliderPath(new[]
|
||||||
|
{
|
||||||
|
new PathControlPoint(),
|
||||||
|
new PathControlPoint(new Vector2(0, 6.25f))
|
||||||
|
}),
|
||||||
|
RepeatCount = 1,
|
||||||
|
SliderVelocity = 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
PassCondition = () => Player.ScoreProcessor.TotalScore.Value == 1_000_000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void runSpmTest(Mod mod)
|
private void runSpmTest(Mod mod)
|
||||||
{
|
{
|
||||||
SpinnerSpmCalculator? spmCalculator = null;
|
SpinnerSpmCalculator? spmCalculator = null;
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||||
{
|
{
|
||||||
public enum SliderPosition
|
public enum SliderPosition
|
||||||
|
@ -187,7 +187,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
var playfield = PlayfieldAtScreenSpacePosition(screenSpacePosition);
|
var playfield = PlayfieldAtScreenSpacePosition(screenSpacePosition);
|
||||||
|
|
||||||
float snapRadius =
|
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;
|
playfield.GamefieldToScreenSpace(Vector2.Zero).X;
|
||||||
|
|
||||||
foreach (var b in blueprints)
|
foreach (var b in blueprints)
|
||||||
|
@ -42,14 +42,14 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
private PlayfieldAdjustmentContainer bubbleContainer = null!;
|
private PlayfieldAdjustmentContainer bubbleContainer = null!;
|
||||||
|
|
||||||
|
private DrawablePool<BubbleDrawable> bubblePool = null!;
|
||||||
|
|
||||||
private readonly Bindable<int> currentCombo = new BindableInt();
|
private readonly Bindable<int> currentCombo = new BindableInt();
|
||||||
|
|
||||||
private float maxSize;
|
private float maxSize;
|
||||||
private float bubbleSize;
|
private float bubbleSize;
|
||||||
private double bubbleFade;
|
private double bubbleFade;
|
||||||
|
|
||||||
private readonly DrawablePool<BubbleDrawable> bubblePool = new DrawablePool<BubbleDrawable>(100);
|
|
||||||
|
|
||||||
public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank;
|
public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank;
|
||||||
|
|
||||||
public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor)
|
public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor)
|
||||||
@ -72,6 +72,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
bubbleContainer = drawableRuleset.CreatePlayfieldAdjustmentContainer();
|
bubbleContainer = drawableRuleset.CreatePlayfieldAdjustmentContainer();
|
||||||
|
|
||||||
drawableRuleset.Overlays.Add(bubbleContainer);
|
drawableRuleset.Overlays.Add(bubbleContainer);
|
||||||
|
drawableRuleset.Overlays.Add(bubblePool = new DrawablePool<BubbleDrawable>(100));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ApplyToDrawableHitObject(DrawableHitObject drawableObject)
|
public void ApplyToDrawableHitObject(DrawableHitObject drawableObject)
|
||||||
|
@ -18,7 +18,7 @@ using static osu.Game.Input.Handlers.ReplayInputHandler;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Mods
|
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.";
|
public override LocalisableString Description => @"You don't need to click. Give your clicking/tapping fingers a break from the heat of things.";
|
||||||
|
|
||||||
|
@ -75,18 +75,24 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
|
tailContainer = new Container<DrawableSliderTail> { RelativeSizeAxes = Axes.Both };
|
||||||
|
|
||||||
AddRangeInternal(new Drawable[]
|
AddRangeInternal(new Drawable[]
|
||||||
{
|
{
|
||||||
shakeContainer = new ShakeContainer
|
shakeContainer = new ShakeContainer
|
||||||
{
|
{
|
||||||
ShakeDuration = 30,
|
ShakeDuration = 30,
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Children = new Drawable[]
|
Children = new[]
|
||||||
{
|
{
|
||||||
Body = new SkinnableDrawable(new OsuSkinComponentLookup(OsuSkinComponents.SliderBody), _ => new DefaultSliderBody(), confineMode: ConfineMode.NoScaling),
|
Body = new SkinnableDrawable(new OsuSkinComponentLookup(OsuSkinComponents.SliderBody), _ => new DefaultSliderBody(), confineMode: ConfineMode.NoScaling),
|
||||||
tailContainer = new Container<DrawableSliderTail> { RelativeSizeAxes = Axes.Both },
|
// proxied here so that the tail is drawn under repeats/ticks - legacy skins rely on this
|
||||||
|
tailContainer.CreateProxy(),
|
||||||
tickContainer = new Container<DrawableSliderTick> { RelativeSizeAxes = Axes.Both },
|
tickContainer = new Container<DrawableSliderTick> { RelativeSizeAxes = Axes.Both },
|
||||||
repeatContainer = new Container<DrawableSliderRepeat> { RelativeSizeAxes = Axes.Both },
|
repeatContainer = new Container<DrawableSliderRepeat> { RelativeSizeAxes = Axes.Both },
|
||||||
|
// actual tail container is placed here to ensure that tail hitobjects are processed after ticks/repeats.
|
||||||
|
// this is required for the correct operation of Score V2.
|
||||||
|
tailContainer,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// slider head is not included in shake as it handles hit detection, and handles its own shaking.
|
// slider head is not included in shake as it handles hit detection, and handles its own shaking.
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||||
{
|
{
|
||||||
public interface IRequireTracking
|
public interface IRequireTracking
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Objects
|
namespace osu.Game.Rulesets.Osu.Objects
|
||||||
{
|
{
|
||||||
public interface ISliderProgress
|
public interface ISliderProgress
|
||||||
|
@ -291,56 +291,32 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
return base.GetDisplayNameForHitResult(result);
|
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();
|
var timedHitEvents = score.HitEvents.Where(e => e.HitObject is HitCircle && !(e.HitObject is SliderTailCircle)).ToList();
|
||||||
|
|
||||||
return new[]
|
return new[]
|
||||||
{
|
{
|
||||||
new StatisticRow
|
new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score, playableBeatmap)
|
||||||
{
|
{
|
||||||
Columns = new[]
|
RelativeSizeAxes = Axes.X,
|
||||||
{
|
AutoSizeAxes = Axes.Y
|
||||||
new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score, playableBeatmap)
|
}),
|
||||||
{
|
new StatisticItem("Timing Distribution", () => new HitEventTimingDistributionGraph(timedHitEvents)
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
AutoSizeAxes = Axes.Y
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new StatisticRow
|
|
||||||
{
|
{
|
||||||
Columns = new[]
|
RelativeSizeAxes = Axes.X,
|
||||||
{
|
Height = 250
|
||||||
new StatisticItem("Timing Distribution", () => new HitEventTimingDistributionGraph(timedHitEvents)
|
}, true),
|
||||||
{
|
new StatisticItem("Accuracy Heatmap", () => new AccuracyHeatmap(score, playableBeatmap)
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
Height = 250
|
|
||||||
}, true),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new StatisticRow
|
|
||||||
{
|
{
|
||||||
Columns = new[]
|
RelativeSizeAxes = Axes.X,
|
||||||
{
|
Height = 250
|
||||||
new StatisticItem("Accuracy Heatmap", () => new AccuracyHeatmap(score, playableBeatmap)
|
}, true),
|
||||||
{
|
new StatisticItem(string.Empty, () => new SimpleStatisticTable(3, new SimpleStatisticItem[]
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
Height = 250
|
|
||||||
}, true),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new StatisticRow
|
|
||||||
{
|
{
|
||||||
Columns = new[]
|
new AverageHitError(timedHitEvents),
|
||||||
{
|
new UnstableRate(timedHitEvents)
|
||||||
new StatisticItem(string.Empty, () => new SimpleStatisticTable(3, new SimpleStatisticItem[]
|
}), true)
|
||||||
{
|
|
||||||
new AverageHitError(timedHitEvents),
|
|
||||||
new UnstableRate(timedHitEvents)
|
|
||||||
}), true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using osu.Game.Rulesets.Judgements;
|
||||||
|
using osu.Game.Rulesets.Osu.Judgements;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Scoring
|
namespace osu.Game.Rulesets.Osu.Scoring
|
||||||
@ -13,6 +15,9 @@ namespace osu.Game.Rulesets.Osu.Scoring
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override HitEvent CreateHitEvent(JudgementResult result)
|
||||||
|
=> base.CreateHitEvent(result).With((result as OsuHitCircleJudgementResult)?.CursorPositionAtHit);
|
||||||
|
|
||||||
protected override double ComputeTotalScore(double comboProgress, double accuracyProgress, double bonusPortion)
|
protected override double ComputeTotalScore(double comboProgress, double accuracyProgress, double bonusPortion)
|
||||||
{
|
{
|
||||||
return 700000 * comboProgress
|
return 700000 * comboProgress
|
||||||
|
@ -48,21 +48,26 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
|||||||
|
|
||||||
private Bindable<bool> configHitLighting = null!;
|
private Bindable<bool> configHitLighting = null!;
|
||||||
|
|
||||||
|
private static readonly Vector2 circle_size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private DrawableHitObject drawableObject { get; set; } = null!;
|
private DrawableHitObject drawableObject { get; set; } = null!;
|
||||||
|
|
||||||
public ArgonMainCirclePiece(bool withOuterFill)
|
public ArgonMainCirclePiece(bool withOuterFill)
|
||||||
{
|
{
|
||||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
Size = circle_size;
|
||||||
|
|
||||||
Anchor = Anchor.Centre;
|
Anchor = Anchor.Centre;
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
outerFill = new Circle // renders white outer border and dark fill
|
outerFill = new Circle // renders dark fill
|
||||||
{
|
{
|
||||||
Size = Size,
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
// Slightly inset to prevent bleeding outside the ring
|
||||||
|
Size = circle_size - new Vector2(1),
|
||||||
Alpha = withOuterFill ? 1 : 0,
|
Alpha = withOuterFill ? 1 : 0,
|
||||||
},
|
},
|
||||||
outerGradient = new Circle // renders the outer bright gradient
|
outerGradient = new Circle // renders the outer bright gradient
|
||||||
@ -88,7 +93,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
|||||||
Masking = true,
|
Masking = true,
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Size = Size,
|
Size = circle_size,
|
||||||
Child = new KiaiFlash
|
Child = new KiaiFlash
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
@ -15,187 +15,175 @@ namespace osu.Game.Rulesets.Taiko.Tests.Judgements
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestHitAllDrumRoll()
|
public void TestHitAllDrumRoll()
|
||||||
{
|
{
|
||||||
const double hit_time = 1000;
|
|
||||||
|
|
||||||
PerformTest(new List<ReplayFrame>
|
PerformTest(new List<ReplayFrame>
|
||||||
{
|
{
|
||||||
new TaikoReplayFrame(0),
|
new TaikoReplayFrame(0),
|
||||||
new TaikoReplayFrame(1000, TaikoAction.LeftCentre),
|
new TaikoReplayFrame(1000, TaikoAction.LeftCentre),
|
||||||
new TaikoReplayFrame(1001),
|
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(2000, TaikoAction.LeftCentre),
|
||||||
new TaikoReplayFrame(2001),
|
new TaikoReplayFrame(2001),
|
||||||
}, CreateBeatmap(new DrumRoll
|
}, CreateBeatmap(createDrumRoll(false)));
|
||||||
{
|
|
||||||
StartTime = hit_time,
|
|
||||||
Duration = 1000
|
|
||||||
}));
|
|
||||||
|
|
||||||
AssertJudgementCount(3);
|
AssertJudgementCount(6);
|
||||||
AssertResult<DrumRollTick>(0, HitResult.SmallBonus);
|
AssertResult<DrumRollTick>(0, HitResult.SmallBonus);
|
||||||
AssertResult<DrumRollTick>(1, 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);
|
AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestHitSomeDrumRoll()
|
public void TestHitSomeDrumRoll()
|
||||||
{
|
{
|
||||||
const double hit_time = 1000;
|
|
||||||
|
|
||||||
PerformTest(new List<ReplayFrame>
|
PerformTest(new List<ReplayFrame>
|
||||||
{
|
{
|
||||||
new TaikoReplayFrame(0),
|
new TaikoReplayFrame(0),
|
||||||
new TaikoReplayFrame(2000, TaikoAction.LeftCentre),
|
new TaikoReplayFrame(2000, TaikoAction.LeftCentre),
|
||||||
new TaikoReplayFrame(2001),
|
new TaikoReplayFrame(2001),
|
||||||
}, CreateBeatmap(new DrumRoll
|
}, CreateBeatmap(createDrumRoll(false)));
|
||||||
{
|
|
||||||
StartTime = hit_time,
|
|
||||||
Duration = 1000
|
|
||||||
}));
|
|
||||||
|
|
||||||
AssertJudgementCount(3);
|
AssertJudgementCount(6);
|
||||||
AssertResult<DrumRollTick>(0, HitResult.IgnoreMiss);
|
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);
|
AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestHitNoneDrumRoll()
|
public void TestHitNoneDrumRoll()
|
||||||
{
|
{
|
||||||
const double hit_time = 1000;
|
|
||||||
|
|
||||||
PerformTest(new List<ReplayFrame>
|
PerformTest(new List<ReplayFrame>
|
||||||
{
|
{
|
||||||
new TaikoReplayFrame(0),
|
new TaikoReplayFrame(0),
|
||||||
}, CreateBeatmap(new DrumRoll
|
}, CreateBeatmap(createDrumRoll(false)));
|
||||||
{
|
|
||||||
StartTime = hit_time,
|
|
||||||
Duration = 1000
|
|
||||||
}));
|
|
||||||
|
|
||||||
AssertJudgementCount(3);
|
AssertJudgementCount(6);
|
||||||
AssertResult<DrumRollTick>(0, HitResult.IgnoreMiss);
|
AssertResult<DrumRollTick>(0, HitResult.IgnoreMiss);
|
||||||
AssertResult<DrumRollTick>(1, 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);
|
AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestHitAllStrongDrumRollWithOneKey()
|
public void TestHitAllStrongDrumRollWithOneKey()
|
||||||
{
|
{
|
||||||
const double hit_time = 1000;
|
|
||||||
|
|
||||||
PerformTest(new List<ReplayFrame>
|
PerformTest(new List<ReplayFrame>
|
||||||
{
|
{
|
||||||
new TaikoReplayFrame(0),
|
new TaikoReplayFrame(0),
|
||||||
new TaikoReplayFrame(1000, TaikoAction.LeftCentre),
|
new TaikoReplayFrame(1000, TaikoAction.LeftCentre),
|
||||||
new TaikoReplayFrame(1001),
|
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(2000, TaikoAction.LeftCentre),
|
||||||
new TaikoReplayFrame(2001),
|
new TaikoReplayFrame(2001),
|
||||||
}, CreateBeatmap(new DrumRoll
|
}, CreateBeatmap(createDrumRoll(true)));
|
||||||
|
|
||||||
|
AssertJudgementCount(12);
|
||||||
|
|
||||||
|
for (int i = 0; i < 5; i++)
|
||||||
{
|
{
|
||||||
StartTime = hit_time,
|
AssertResult<DrumRollTick>(i, HitResult.SmallBonus);
|
||||||
Duration = 1000,
|
AssertResult<StrongNestedHitObject>(i, HitResult.LargeBonus);
|
||||||
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<DrumRoll>(0, HitResult.IgnoreHit);
|
AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
|
||||||
AssertResult<StrongNestedHitObject>(2, HitResult.IgnoreHit);
|
AssertResult<StrongNestedHitObject>(5, HitResult.IgnoreHit);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestHitSomeStrongDrumRollWithOneKey()
|
public void TestHitSomeStrongDrumRollWithOneKey()
|
||||||
{
|
{
|
||||||
const double hit_time = 1000;
|
|
||||||
|
|
||||||
PerformTest(new List<ReplayFrame>
|
PerformTest(new List<ReplayFrame>
|
||||||
{
|
{
|
||||||
new TaikoReplayFrame(0),
|
new TaikoReplayFrame(0),
|
||||||
new TaikoReplayFrame(2000, TaikoAction.LeftCentre),
|
new TaikoReplayFrame(2000, TaikoAction.LeftCentre),
|
||||||
new TaikoReplayFrame(2001),
|
new TaikoReplayFrame(2001),
|
||||||
}, CreateBeatmap(new DrumRoll
|
}, CreateBeatmap(createDrumRoll(true)));
|
||||||
{
|
|
||||||
StartTime = hit_time,
|
|
||||||
Duration = 1000,
|
|
||||||
IsStrong = true
|
|
||||||
}));
|
|
||||||
|
|
||||||
AssertJudgementCount(6);
|
AssertJudgementCount(12);
|
||||||
|
|
||||||
AssertResult<DrumRollTick>(0, HitResult.IgnoreMiss);
|
AssertResult<DrumRollTick>(0, HitResult.IgnoreMiss);
|
||||||
AssertResult<StrongNestedHitObject>(0, HitResult.IgnoreMiss);
|
AssertResult<StrongNestedHitObject>(0, HitResult.IgnoreMiss);
|
||||||
|
|
||||||
AssertResult<DrumRollTick>(1, HitResult.SmallBonus);
|
AssertResult<DrumRollTick>(4, HitResult.SmallBonus);
|
||||||
AssertResult<StrongNestedHitObject>(1, HitResult.LargeBonus);
|
AssertResult<StrongNestedHitObject>(4, HitResult.LargeBonus);
|
||||||
|
|
||||||
AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
|
AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
|
||||||
AssertResult<StrongNestedHitObject>(2, HitResult.IgnoreHit);
|
AssertResult<StrongNestedHitObject>(5, HitResult.IgnoreHit);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestHitAllStrongDrumRollWithBothKeys()
|
public void TestHitAllStrongDrumRollWithBothKeys()
|
||||||
{
|
{
|
||||||
const double hit_time = 1000;
|
|
||||||
|
|
||||||
PerformTest(new List<ReplayFrame>
|
PerformTest(new List<ReplayFrame>
|
||||||
{
|
{
|
||||||
new TaikoReplayFrame(0),
|
new TaikoReplayFrame(0),
|
||||||
new TaikoReplayFrame(1000, TaikoAction.LeftCentre, TaikoAction.RightCentre),
|
new TaikoReplayFrame(1000, TaikoAction.LeftCentre, TaikoAction.RightCentre),
|
||||||
new TaikoReplayFrame(1001),
|
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(2000, TaikoAction.LeftCentre, TaikoAction.RightCentre),
|
||||||
new TaikoReplayFrame(2001),
|
new TaikoReplayFrame(2001),
|
||||||
}, CreateBeatmap(new DrumRoll
|
}, CreateBeatmap(createDrumRoll(true)));
|
||||||
|
|
||||||
|
AssertJudgementCount(12);
|
||||||
|
|
||||||
|
for (int i = 0; i < 5; i++)
|
||||||
{
|
{
|
||||||
StartTime = hit_time,
|
AssertResult<DrumRollTick>(i, HitResult.SmallBonus);
|
||||||
Duration = 1000,
|
AssertResult<StrongNestedHitObject>(i, HitResult.LargeBonus);
|
||||||
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<DrumRoll>(0, HitResult.IgnoreHit);
|
AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
|
||||||
AssertResult<StrongNestedHitObject>(2, HitResult.IgnoreHit);
|
AssertResult<StrongNestedHitObject>(5, HitResult.IgnoreHit);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestHitSomeStrongDrumRollWithBothKeys()
|
public void TestHitSomeStrongDrumRollWithBothKeys()
|
||||||
{
|
{
|
||||||
const double hit_time = 1000;
|
|
||||||
|
|
||||||
PerformTest(new List<ReplayFrame>
|
PerformTest(new List<ReplayFrame>
|
||||||
{
|
{
|
||||||
new TaikoReplayFrame(0),
|
new TaikoReplayFrame(0),
|
||||||
new TaikoReplayFrame(2000, TaikoAction.LeftCentre, TaikoAction.RightCentre),
|
new TaikoReplayFrame(2000, TaikoAction.LeftCentre, TaikoAction.RightCentre),
|
||||||
new TaikoReplayFrame(2001),
|
new TaikoReplayFrame(2001),
|
||||||
}, CreateBeatmap(new DrumRoll
|
}, CreateBeatmap(createDrumRoll(true)));
|
||||||
{
|
|
||||||
StartTime = hit_time,
|
|
||||||
Duration = 1000,
|
|
||||||
IsStrong = true
|
|
||||||
}));
|
|
||||||
|
|
||||||
AssertJudgementCount(6);
|
AssertJudgementCount(12);
|
||||||
|
|
||||||
AssertResult<DrumRollTick>(0, HitResult.IgnoreMiss);
|
AssertResult<DrumRollTick>(0, HitResult.IgnoreMiss);
|
||||||
AssertResult<StrongNestedHitObject>(0, HitResult.IgnoreMiss);
|
AssertResult<StrongNestedHitObject>(0, HitResult.IgnoreMiss);
|
||||||
|
|
||||||
AssertResult<DrumRollTick>(1, HitResult.SmallBonus);
|
AssertResult<DrumRollTick>(4, HitResult.SmallBonus);
|
||||||
AssertResult<StrongNestedHitObject>(1, HitResult.LargeBonus);
|
AssertResult<StrongNestedHitObject>(4, HitResult.LargeBonus);
|
||||||
|
|
||||||
AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
|
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();
|
}).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;
|
return converted;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,7 +141,6 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
|||||||
StartTime = obj.StartTime,
|
StartTime = obj.StartTime,
|
||||||
Samples = obj.Samples,
|
Samples = obj.Samples,
|
||||||
Duration = taikoDuration,
|
Duration = taikoDuration,
|
||||||
TickRate = beatmap.Difficulty.SliderTickRate == 3 ? 3 : 4,
|
|
||||||
SliderVelocity = obj is IHasSliderVelocity velocityData ? velocityData.SliderVelocity : 1
|
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.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
@ -69,6 +67,8 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
|||||||
double scoringDistance = base_distance * difficulty.SliderMultiplier * SliderVelocity;
|
double scoringDistance = base_distance * difficulty.SliderMultiplier * SliderVelocity;
|
||||||
Velocity = scoringDistance / timingPoint.BeatLength;
|
Velocity = scoringDistance / timingPoint.BeatLength;
|
||||||
|
|
||||||
|
TickRate = difficulty.SliderTickRate == 3 ? 3 : 4;
|
||||||
|
|
||||||
tickSpacing = timingPoint.BeatLength / TickRate;
|
tickSpacing = timingPoint.BeatLength / TickRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Objects
|
namespace osu.Game.Rulesets.Taiko.Objects
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon
|
|||||||
|
|
||||||
private const double pre_beat_transition_time = 80;
|
private const double pre_beat_transition_time = 80;
|
||||||
|
|
||||||
private const float flash_opacity = 0.3f;
|
private const float kiai_flash_opacity = 0.15f;
|
||||||
|
|
||||||
private ColourInfo accentColour;
|
private ColourInfo accentColour;
|
||||||
|
|
||||||
@ -107,7 +107,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon
|
|||||||
if (drawableHitObject.State.Value == ArmedState.Idle)
|
if (drawableHitObject.State.Value == ArmedState.Idle)
|
||||||
{
|
{
|
||||||
flash
|
flash
|
||||||
.FadeTo(flash_opacity)
|
.FadeTo(kiai_flash_opacity)
|
||||||
.Then()
|
.Then()
|
||||||
.FadeOut(timingPoint.BeatLength * 0.75, Easing.OutSine);
|
.FadeOut(timingPoint.BeatLength * 0.75, Easing.OutSine);
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Colour;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Effects;
|
using osu.Framework.Graphics.Effects;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
@ -18,7 +16,9 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon
|
|||||||
public partial class ArgonHitExplosion : CompositeDrawable, IAnimatableHitExplosion
|
public partial class ArgonHitExplosion : CompositeDrawable, IAnimatableHitExplosion
|
||||||
{
|
{
|
||||||
private readonly TaikoSkinComponents component;
|
private readonly TaikoSkinComponents component;
|
||||||
|
|
||||||
private readonly Circle outer;
|
private readonly Circle outer;
|
||||||
|
private readonly Circle inner;
|
||||||
|
|
||||||
public ArgonHitExplosion(TaikoSkinComponents component)
|
public ArgonHitExplosion(TaikoSkinComponents component)
|
||||||
{
|
{
|
||||||
@ -34,13 +34,9 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon
|
|||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = ColourInfo.GradientVertical(
|
|
||||||
new Color4(255, 227, 236, 255),
|
|
||||||
new Color4(255, 198, 211, 255)
|
|
||||||
),
|
|
||||||
Masking = true,
|
Masking = true,
|
||||||
},
|
},
|
||||||
new Circle
|
inner = new Circle
|
||||||
{
|
{
|
||||||
Name = "Inner circle",
|
Name = "Inner circle",
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
@ -48,12 +44,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = Color4.White,
|
Colour = Color4.White,
|
||||||
Size = new Vector2(0.85f),
|
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,
|
Masking = true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -63,6 +53,16 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon
|
|||||||
{
|
{
|
||||||
this.FadeOut();
|
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)
|
switch (component)
|
||||||
{
|
{
|
||||||
case TaikoSkinComponents.TaikoExplosionGreat:
|
case TaikoSkinComponents.TaikoExplosionGreat:
|
||||||
|
@ -19,6 +19,20 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon
|
|||||||
{
|
{
|
||||||
public partial class ArgonInputDrum : AspectContainer
|
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;
|
private const float rim_size = 0.3f;
|
||||||
|
|
||||||
public ArgonInputDrum()
|
public ArgonInputDrum()
|
||||||
@ -141,14 +155,11 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon
|
|||||||
Anchor = anchor,
|
Anchor = anchor,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = ColourInfo.GradientHorizontal(
|
Colour = RIM_HIT_GRADIENT,
|
||||||
new Color4(227, 248, 255, 255),
|
|
||||||
new Color4(198, 245, 255, 255)
|
|
||||||
),
|
|
||||||
EdgeEffect = new EdgeEffectParameters
|
EdgeEffect = new EdgeEffectParameters
|
||||||
{
|
{
|
||||||
Type = EdgeEffectType.Glow,
|
Type = EdgeEffectType.Glow,
|
||||||
Colour = new Color4(126, 215, 253, 170),
|
Colour = RIM_HIT_GLOW.Opacity(0.66f),
|
||||||
Radius = 50,
|
Radius = 50,
|
||||||
},
|
},
|
||||||
Alpha = 0,
|
Alpha = 0,
|
||||||
@ -166,14 +177,11 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon
|
|||||||
Anchor = anchor,
|
Anchor = anchor,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = ColourInfo.GradientHorizontal(
|
Colour = CENTRE_HIT_GRADIENT,
|
||||||
new Color4(255, 227, 236, 255),
|
|
||||||
new Color4(255, 198, 211, 255)
|
|
||||||
),
|
|
||||||
EdgeEffect = new EdgeEffectParameters
|
EdgeEffect = new EdgeEffectParameters
|
||||||
{
|
{
|
||||||
Type = EdgeEffectType.Glow,
|
Type = EdgeEffectType.Glow,
|
||||||
Colour = new Color4(255, 147, 199, 255),
|
Colour = CENTRE_HIT_GLOW,
|
||||||
Radius = 50,
|
Radius = 50,
|
||||||
},
|
},
|
||||||
Size = new Vector2(1 - rim_size),
|
Size = new Vector2(1 - rim_size),
|
||||||
|
@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Default
|
|||||||
|
|
||||||
private const double pre_beat_transition_time = 80;
|
private const double pre_beat_transition_time = 80;
|
||||||
|
|
||||||
private const float flash_opacity = 0.3f;
|
private const float kiai_flash_opacity = 0.15f;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private DrawableHitObject drawableHitObject { get; set; } = null!;
|
private DrawableHitObject drawableHitObject { get; set; } = null!;
|
||||||
@ -187,7 +187,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Default
|
|||||||
if (drawableHitObject.State.Value == ArmedState.Idle)
|
if (drawableHitObject.State.Value == ArmedState.Idle)
|
||||||
{
|
{
|
||||||
flashBox
|
flashBox
|
||||||
.FadeTo(flash_opacity)
|
.FadeTo(kiai_flash_opacity)
|
||||||
.Then()
|
.Then()
|
||||||
.FadeOut(timingPoint.BeatLength * 0.75, Easing.OutSine);
|
.FadeOut(timingPoint.BeatLength * 0.75, Easing.OutSine);
|
||||||
}
|
}
|
||||||
|
@ -229,45 +229,27 @@ namespace osu.Game.Rulesets.Taiko
|
|||||||
return base.GetDisplayNameForHitResult(result);
|
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();
|
var timedHitEvents = score.HitEvents.Where(e => e.HitObject is Hit).ToList();
|
||||||
|
|
||||||
return new[]
|
return new[]
|
||||||
{
|
{
|
||||||
new StatisticRow
|
new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score, playableBeatmap)
|
||||||
{
|
{
|
||||||
Columns = new[]
|
RelativeSizeAxes = Axes.X,
|
||||||
{
|
AutoSizeAxes = Axes.Y
|
||||||
new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score, playableBeatmap)
|
}),
|
||||||
{
|
new StatisticItem("Timing Distribution", () => new HitEventTimingDistributionGraph(timedHitEvents)
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
AutoSizeAxes = Axes.Y
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new StatisticRow
|
|
||||||
{
|
{
|
||||||
Columns = new[]
|
RelativeSizeAxes = Axes.X,
|
||||||
{
|
Height = 250
|
||||||
new StatisticItem("Timing Distribution", () => new HitEventTimingDistributionGraph(timedHitEvents)
|
}, true),
|
||||||
{
|
new StatisticItem(string.Empty, () => new SimpleStatisticTable(3, new SimpleStatisticItem[]
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
Height = 250
|
|
||||||
}, true),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new StatisticRow
|
|
||||||
{
|
{
|
||||||
Columns = new[]
|
new AverageHitError(timedHitEvents),
|
||||||
{
|
new UnstableRate(timedHitEvents)
|
||||||
new StatisticItem(string.Empty, () => new SimpleStatisticTable(3, new SimpleStatisticItem[]
|
}), 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 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 override Track GetBeatmapTrack() => throw new NotImplementedException();
|
||||||
|
|
||||||
|
@ -131,7 +131,7 @@ namespace osu.Game.Tests.Editing.Checks
|
|||||||
|
|
||||||
var mock = new Mock<IWorkingBeatmap>();
|
var mock = new Mock<IWorkingBeatmap>();
|
||||||
mock.SetupGet(w => w.Beatmap).Returns(beatmap);
|
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);
|
mock.Setup(w => w.GetStream(It.IsAny<string>())).Returns(stream);
|
||||||
|
|
||||||
return mock;
|
return mock;
|
||||||
|
@ -73,7 +73,5 @@ namespace osu.Game.Tests.Rulesets
|
|||||||
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => null;
|
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => null;
|
||||||
public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => null;
|
public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => null;
|
||||||
}
|
}
|
||||||
|
|
||||||
#nullable enable
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -286,7 +286,7 @@ namespace osu.Game.Tests.Visual.Background
|
|||||||
this.renderer = renderer;
|
this.renderer = renderer;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Texture GetBackground() => renderer.CreateTexture(1, 1);
|
public override Texture GetBackground() => renderer.CreateTexture(1, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private partial class TestWorkingBeatmapWithStoryboard : TestWorkingBeatmap
|
private partial class TestWorkingBeatmapWithStoryboard : TestWorkingBeatmap
|
||||||
|
@ -264,8 +264,9 @@ namespace osu.Game.Tests.Visual.Collections
|
|||||||
assertCollectionName(1, "First");
|
assertCollectionName(1, "First");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[TestCase(false)]
|
||||||
public void TestCollectionRenamedOnTextChange()
|
[TestCase(true)]
|
||||||
|
public void TestCollectionRenamedOnTextChange(bool commitWithEnter)
|
||||||
{
|
{
|
||||||
BeatmapCollection first = null!;
|
BeatmapCollection first = null!;
|
||||||
DrawableCollectionListItem firstItem = null!;
|
DrawableCollectionListItem firstItem = null!;
|
||||||
@ -293,9 +294,19 @@ namespace osu.Game.Tests.Visual.Collections
|
|||||||
AddStep("change first collection name", () =>
|
AddStep("change first collection name", () =>
|
||||||
{
|
{
|
||||||
firstItem.ChildrenOfType<TextBox>().First().Text = "First";
|
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");
|
AddUntilStep("collection has new name", () => first.Name == "First");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@ -23,8 +21,8 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
{
|
{
|
||||||
public partial class TestSceneBeatDivisorControl : OsuManualInputManagerTestScene
|
public partial class TestSceneBeatDivisorControl : OsuManualInputManagerTestScene
|
||||||
{
|
{
|
||||||
private BeatDivisorControl beatDivisorControl;
|
private BeatDivisorControl beatDivisorControl = null!;
|
||||||
private BindableBeatDivisor bindableBeatDivisor;
|
private BindableBeatDivisor bindableBeatDivisor = null!;
|
||||||
|
|
||||||
private SliderBar<int> tickSliderBar => beatDivisorControl.ChildrenOfType<SliderBar<int>>().Single();
|
private SliderBar<int> tickSliderBar => beatDivisorControl.ChildrenOfType<SliderBar<int>>().Single();
|
||||||
private Triangle tickMarkerHead => tickSliderBar.ChildrenOfType<Triangle>().Single();
|
private Triangle tickMarkerHead => tickSliderBar.ChildrenOfType<Triangle>().Single();
|
||||||
@ -51,9 +49,9 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestBindableBeatDivisor()
|
public void TestBindableBeatDivisor()
|
||||||
{
|
{
|
||||||
AddRepeatStep("move previous", () => bindableBeatDivisor.Previous(), 2);
|
AddRepeatStep("move previous", () => bindableBeatDivisor.SelectPrevious(), 2);
|
||||||
AddAssert("divisor is 4", () => bindableBeatDivisor.Value == 4);
|
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);
|
AddAssert("divisor is 12", () => bindableBeatDivisor.Value == 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,16 +99,22 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
public void TestBeatChevronNavigation()
|
public void TestBeatChevronNavigation()
|
||||||
{
|
{
|
||||||
switchBeatSnap(1);
|
switchBeatSnap(1);
|
||||||
|
assertBeatSnap(16);
|
||||||
|
|
||||||
|
switchBeatSnap(-4);
|
||||||
assertBeatSnap(1);
|
assertBeatSnap(1);
|
||||||
|
|
||||||
switchBeatSnap(3);
|
switchBeatSnap(3);
|
||||||
assertBeatSnap(8);
|
assertBeatSnap(8);
|
||||||
|
|
||||||
switchBeatSnap(-1);
|
switchBeatSnap(3);
|
||||||
|
assertBeatSnap(16);
|
||||||
|
|
||||||
|
switchBeatSnap(-2);
|
||||||
assertBeatSnap(4);
|
assertBeatSnap(4);
|
||||||
|
|
||||||
switchBeatSnap(-3);
|
switchBeatSnap(-3);
|
||||||
assertBeatSnap(16);
|
assertBeatSnap(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -163,9 +167,11 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
switchPresets(1);
|
switchPresets(1);
|
||||||
assertPreset(BeatDivisorType.Triplets);
|
assertPreset(BeatDivisorType.Triplets);
|
||||||
|
assertBeatSnap(6);
|
||||||
|
|
||||||
switchPresets(1);
|
switchPresets(1);
|
||||||
assertPreset(BeatDivisorType.Common);
|
assertPreset(BeatDivisorType.Common);
|
||||||
|
assertBeatSnap(4);
|
||||||
|
|
||||||
switchPresets(-1);
|
switchPresets(-1);
|
||||||
assertPreset(BeatDivisorType.Triplets);
|
assertPreset(BeatDivisorType.Triplets);
|
||||||
@ -181,6 +187,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
setDivisorViaInput(15);
|
setDivisorViaInput(15);
|
||||||
assertPreset(BeatDivisorType.Custom, 15);
|
assertPreset(BeatDivisorType.Custom, 15);
|
||||||
|
assertBeatSnap(15);
|
||||||
|
|
||||||
switchBeatSnap(-1);
|
switchBeatSnap(-1);
|
||||||
assertBeatSnap(5);
|
assertBeatSnap(5);
|
||||||
@ -190,12 +197,14 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
setDivisorViaInput(5);
|
setDivisorViaInput(5);
|
||||||
assertPreset(BeatDivisorType.Custom, 15);
|
assertPreset(BeatDivisorType.Custom, 15);
|
||||||
|
assertBeatSnap(5);
|
||||||
|
|
||||||
switchPresets(1);
|
switchPresets(1);
|
||||||
assertPreset(BeatDivisorType.Common);
|
assertPreset(BeatDivisorType.Common);
|
||||||
|
|
||||||
switchPresets(-1);
|
switchPresets(-1);
|
||||||
assertPreset(BeatDivisorType.Triplets);
|
assertPreset(BeatDivisorType.Custom, 15);
|
||||||
|
assertBeatSnap(15);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void switchBeatSnap(int direction) => AddRepeatStep($"move snap {(direction > 0 ? "forward" : "backward")}", () =>
|
private void switchBeatSnap(int direction) => AddRepeatStep($"move snap {(direction > 0 ? "forward" : "backward")}", () =>
|
||||||
@ -207,7 +216,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
}, Math.Abs(direction));
|
}, Math.Abs(direction));
|
||||||
|
|
||||||
private void assertBeatSnap(int expected) => AddAssert($"beat snap is {expected}",
|
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")}", () =>
|
private void switchPresets(int direction) => AddRepeatStep($"move presets {(direction > 0 ? "forward" : "backward")}", () =>
|
||||||
{
|
{
|
||||||
@ -219,7 +228,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
private void assertPreset(BeatDivisorType type, int? maxDivisor = null)
|
private void assertPreset(BeatDivisorType type, int? maxDivisor = null)
|
||||||
{
|
{
|
||||||
AddAssert($"preset is {type}", () => bindableBeatDivisor.ValidDivisors.Value.Type == type);
|
AddAssert($"preset is {type}", () => bindableBeatDivisor.ValidDivisors.Value.Type, () => Is.EqualTo(type));
|
||||||
|
|
||||||
if (type == BeatDivisorType.Custom)
|
if (type == BeatDivisorType.Custom)
|
||||||
{
|
{
|
||||||
@ -237,7 +246,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
InputManager.Click(MouseButton.Left);
|
InputManager.Click(MouseButton.Left);
|
||||||
});
|
});
|
||||||
|
|
||||||
BeatDivisorControl.CustomDivisorPopover popover = null;
|
BeatDivisorControl.CustomDivisorPopover? popover = null;
|
||||||
AddUntilStep("wait for popover", () => (popover = this.ChildrenOfType<BeatDivisorControl.CustomDivisorPopover>().SingleOrDefault()) != null && popover.IsLoaded);
|
AddUntilStep("wait for popover", () => (popover = this.ChildrenOfType<BeatDivisorControl.CustomDivisorPopover>().SingleOrDefault()) != null && popover.IsLoaded);
|
||||||
AddStep($"set divisor to {divisor}", () =>
|
AddStep($"set divisor to {divisor}", () =>
|
||||||
{
|
{
|
||||||
|
@ -209,10 +209,14 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
public override void TearDownSteps()
|
public override void TearDownSteps()
|
||||||
{
|
{
|
||||||
base.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.Graphics;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Overlays.Settings;
|
using osu.Game.Overlays.Settings;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Screens.Play.PlayerSettings;
|
using osu.Game.Screens.Play.PlayerSettings;
|
||||||
using osu.Game.Tests.Visual.Ranking;
|
using osu.Game.Tests.Visual.Ranking;
|
||||||
@ -49,6 +51,21 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddAssert("No calibration button", () => !offsetControl.ChildrenOfType<SettingsButton>().Any());
|
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]
|
[Test]
|
||||||
public void TestCalibrationFromZero()
|
public void TestCalibrationFromZero()
|
||||||
{
|
{
|
||||||
|
@ -129,7 +129,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
{
|
{
|
||||||
Playlist =
|
Playlist =
|
||||||
{
|
{
|
||||||
new MultiplayerPlaylistItem(playlistItem),
|
TestMultiplayerClient.CreateMultiplayerPlaylistItem(playlistItem),
|
||||||
},
|
},
|
||||||
Users = { localUser },
|
Users = { localUser },
|
||||||
Host = localUser,
|
Host = localUser,
|
||||||
|
@ -906,7 +906,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
|
|
||||||
enterGameplay();
|
enterGameplay();
|
||||||
AddStep("join other user", () => multiplayerClient.AddUser(new APIUser { Id = 1234 }));
|
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)
|
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
|
||||||
{
|
{
|
||||||
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
|
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
|
||||||
@ -938,7 +938,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
enterGameplay();
|
enterGameplay();
|
||||||
|
|
||||||
AddStep("join other user", () => multiplayerClient.AddUser(new APIUser { Id = 1234 }));
|
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)
|
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
|
||||||
{
|
{
|
||||||
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
|
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
|
||||||
|
@ -215,7 +215,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void addItemStep(bool expired = false, int? userId = null) => AddStep("add item", () =>
|
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,
|
Expired = expired,
|
||||||
PlayedAt = DateTimeOffset.Now
|
PlayedAt = DateTimeOffset.Now
|
||||||
|
@ -130,7 +130,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
|
|
||||||
AddStep("add playlist item", () =>
|
AddStep("add playlist item", () =>
|
||||||
{
|
{
|
||||||
MultiplayerPlaylistItem item = new MultiplayerPlaylistItem(new PlaylistItem(importedBeatmap));
|
MultiplayerPlaylistItem item = TestMultiplayerClient.CreateMultiplayerPlaylistItem(new PlaylistItem(importedBeatmap));
|
||||||
|
|
||||||
MultiplayerClient.AddUserPlaylistItem(userId(), item).WaitSafely();
|
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)));
|
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);
|
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", () =>
|
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);
|
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.Collections;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.Leaderboards;
|
using osu.Game.Online.Leaderboards;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.BeatmapListing;
|
using osu.Game.Overlays.BeatmapListing;
|
||||||
@ -539,6 +540,11 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
AddStep("open room", () => multiplayerComponents.ChildrenOfType<LoungeSubScreen>().Single().Open());
|
AddStep("open room", () => multiplayerComponents.ChildrenOfType<LoungeSubScreen>().Single().Open());
|
||||||
AddStep("press back button", () => Game.ChildrenOfType<BackButton>().First().Action());
|
AddStep("press back button", () => Game.ChildrenOfType<BackButton>().First().Action());
|
||||||
AddWaitStep("wait two frames", 2);
|
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]
|
[Test]
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@ -24,8 +22,8 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
{
|
{
|
||||||
private readonly APIUser streamingUser = new APIUser { Id = 2, Username = "Test user" };
|
private readonly APIUser streamingUser = new APIUser { Id = 2, Username = "Test user" };
|
||||||
|
|
||||||
private TestSpectatorClient spectatorClient;
|
private TestSpectatorClient spectatorClient = null!;
|
||||||
private CurrentlyPlayingDisplay currentlyPlaying;
|
private CurrentlyPlayingDisplay currentlyPlaying = null!;
|
||||||
|
|
||||||
[SetUpSteps]
|
[SetUpSteps]
|
||||||
public void SetUpSteps()
|
public void SetUpSteps()
|
||||||
@ -88,13 +86,13 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
"pishifat"
|
"pishifat"
|
||||||
};
|
};
|
||||||
|
|
||||||
protected override Task<APIUser> ComputeValueAsync(int lookup, CancellationToken token = default)
|
protected override Task<APIUser?> ComputeValueAsync(int lookup, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
// tests against failed lookups
|
// tests against failed lookups
|
||||||
if (lookup == 13)
|
if (lookup == 13)
|
||||||
return Task.FromResult<APIUser>(null);
|
return Task.FromResult<APIUser?>(null);
|
||||||
|
|
||||||
return Task.FromResult(new APIUser
|
return Task.FromResult<APIUser?>(new APIUser
|
||||||
{
|
{
|
||||||
Id = lookup,
|
Id = lookup,
|
||||||
Username = usernames[lookup % usernames.Length],
|
Username = usernames[lookup % usernames.Length],
|
||||||
|
@ -174,78 +174,33 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
|
|
||||||
private class TestRulesetAllStatsRequireHitEvents : TestRuleset
|
private class TestRulesetAllStatsRequireHitEvents : TestRuleset
|
||||||
{
|
{
|
||||||
public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap)
|
public override StatisticItem[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => new[]
|
||||||
{
|
{
|
||||||
return new[]
|
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)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestRulesetNoStatsRequireHitEvents : TestRuleset
|
private class TestRulesetNoStatsRequireHitEvents : TestRuleset
|
||||||
{
|
{
|
||||||
public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap)
|
public override StatisticItem[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap)
|
||||||
{
|
{
|
||||||
return new[]
|
return new[]
|
||||||
{
|
{
|
||||||
new StatisticRow
|
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"))
|
||||||
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"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestRulesetMixed : TestRuleset
|
private class TestRulesetMixed : TestRuleset
|
||||||
{
|
{
|
||||||
public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap)
|
public override StatisticItem[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap)
|
||||||
{
|
{
|
||||||
return new[]
|
return new[]
|
||||||
{
|
{
|
||||||
new StatisticRow
|
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"))
|
||||||
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"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@ -24,10 +23,10 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
{
|
{
|
||||||
public partial class TestSceneBeatmapMetadataDisplay : OsuTestScene
|
public partial class TestSceneBeatmapMetadataDisplay : OsuTestScene
|
||||||
{
|
{
|
||||||
private BeatmapMetadataDisplay display;
|
private BeatmapMetadataDisplay display = null!;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private BeatmapManager manager { get; set; }
|
private BeatmapManager manager { get; set; } = null!;
|
||||||
|
|
||||||
[Cached(typeof(BeatmapDifficultyCache))]
|
[Cached(typeof(BeatmapDifficultyCache))]
|
||||||
private readonly TestBeatmapDifficultyCache testDifficultyCache = new TestBeatmapDifficultyCache();
|
private readonly TestBeatmapDifficultyCache testDifficultyCache = new TestBeatmapDifficultyCache();
|
||||||
@ -121,7 +120,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
private partial class TestBeatmapDifficultyCache : BeatmapDifficultyCache
|
private partial class TestBeatmapDifficultyCache : BeatmapDifficultyCache
|
||||||
{
|
{
|
||||||
private TaskCompletionSource<bool> calculationBlocker;
|
private TaskCompletionSource<bool>? calculationBlocker;
|
||||||
|
|
||||||
private bool blockCalculation;
|
private bool blockCalculation;
|
||||||
|
|
||||||
@ -142,10 +141,13 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<StarDifficulty?> GetDifficultyAsync(IBeatmapInfo beatmapInfo, IRulesetInfo rulesetInfo = null, IEnumerable<Mod> mods = null, CancellationToken cancellationToken = default)
|
public override async Task<StarDifficulty?> GetDifficultyAsync(IBeatmapInfo beatmapInfo, IRulesetInfo? rulesetInfo = null, IEnumerable<Mod>? mods = null, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (blockCalculation)
|
if (blockCalculation)
|
||||||
|
{
|
||||||
|
Debug.Assert(calculationBlocker != null);
|
||||||
await calculationBlocker.Task.ConfigureAwait(false);
|
await calculationBlocker.Task.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
return await base.GetDifficultyAsync(beatmapInfo, rulesetInfo, mods, cancellationToken).ConfigureAwait(false);
|
return await base.GetDifficultyAsync(beatmapInfo, rulesetInfo, mods, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
@ -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]
|
[Test]
|
||||||
public void TestSelection()
|
public void TestSelection()
|
||||||
{
|
{
|
||||||
@ -196,6 +214,33 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
InputManager.ScrollVerticalBy(direction);
|
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
|
private partial class TestSection : TestBox
|
||||||
{
|
{
|
||||||
public bool Selected
|
public bool Selected
|
||||||
|
@ -51,7 +51,7 @@ namespace osu.Game.Tests
|
|||||||
|
|
||||||
protected override IBeatmap GetBeatmap() => beatmap;
|
protected override IBeatmap GetBeatmap() => beatmap;
|
||||||
|
|
||||||
protected override Texture GetBackground() => null;
|
public override Texture GetBackground() => null;
|
||||||
|
|
||||||
protected override Waveform GetWaveform() => new Waveform(trackStore.GetStream(firstAudioFile));
|
protected override Waveform GetWaveform() => new Waveform(trackStore.GetStream(firstAudioFile));
|
||||||
|
|
||||||
|
@ -24,6 +24,9 @@ namespace osu.Game.Tournament.Tests.Screens
|
|||||||
Add(screen = new MapPoolScreen { Width = 0.7f });
|
Add(screen = new MapPoolScreen { Width = 0.7f });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp() => Schedule(() => Ladder.SplitMapPoolByMods.Value = true);
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestFewMaps()
|
public void TestFewMaps()
|
||||||
{
|
{
|
||||||
@ -92,7 +95,7 @@ namespace osu.Game.Tournament.Tests.Screens
|
|||||||
Ladder.CurrentMatch.Value.Round.Value.Beatmaps.Clear();
|
Ladder.CurrentMatch.Value.Round.Value.Beatmaps.Clear();
|
||||||
|
|
||||||
for (int i = 0; i < 11; i++)
|
for (int i = 0; i < 11; i++)
|
||||||
addBeatmap(i > 4 ? $"M{i}" : "NM");
|
addBeatmap(i > 4 ? Ruleset.Value.CreateInstance().AllMods.ElementAt(i).Acronym : "NM");
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("reset match", () =>
|
AddStep("reset match", () =>
|
||||||
@ -118,7 +121,7 @@ namespace osu.Game.Tournament.Tests.Screens
|
|||||||
Ladder.CurrentMatch.Value.Round.Value.Beatmaps.Clear();
|
Ladder.CurrentMatch.Value.Round.Value.Beatmaps.Clear();
|
||||||
|
|
||||||
for (int i = 0; i < 12; i++)
|
for (int i = 0; i < 12; i++)
|
||||||
addBeatmap(i > 4 ? $"M{i}" : "NM");
|
addBeatmap(i > 4 ? Ruleset.Value.CreateInstance().AllMods.ElementAt(i).Acronym : "NM");
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("reset match", () =>
|
AddStep("reset match", () =>
|
||||||
@ -130,7 +133,27 @@ namespace osu.Game.Tournament.Tests.Screens
|
|||||||
assertThreeWide();
|
assertThreeWide();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addBeatmap(string mods = "nm")
|
[Test]
|
||||||
|
public void TestSplitMapPoolByMods()
|
||||||
|
{
|
||||||
|
AddStep("load many maps", () =>
|
||||||
|
{
|
||||||
|
Ladder.CurrentMatch.Value.Round.Value.Beatmaps.Clear();
|
||||||
|
|
||||||
|
for (int i = 0; i < 12; i++)
|
||||||
|
addBeatmap(i > 4 ? Ruleset.Value.CreateInstance().AllMods.ElementAt(i).Acronym : "NM");
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("disable splitting map pool by mods", () => Ladder.SplitMapPoolByMods.Value = false);
|
||||||
|
|
||||||
|
AddStep("reset match", () =>
|
||||||
|
{
|
||||||
|
Ladder.CurrentMatch.Value = new TournamentMatch();
|
||||||
|
Ladder.CurrentMatch.Value = Ladder.Matches.First();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addBeatmap(string mods = "NM")
|
||||||
{
|
{
|
||||||
Ladder.CurrentMatch.Value.Round.Value.Beatmaps.Add(new RoundBeatmap
|
Ladder.CurrentMatch.Value.Round.Value.Beatmaps.Add(new RoundBeatmap
|
||||||
{
|
{
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace osu.Game.Tournament.IPC
|
namespace osu.Game.Tournament.IPC
|
||||||
{
|
{
|
||||||
public enum TourneyState
|
public enum TourneyState
|
||||||
|
@ -42,5 +42,7 @@ namespace osu.Game.Tournament.Models
|
|||||||
};
|
};
|
||||||
|
|
||||||
public Bindable<bool> AutoProgressScreens = new BindableBool(true);
|
public Bindable<bool> AutoProgressScreens = new BindableBool(true);
|
||||||
|
|
||||||
|
public Bindable<bool> SplitMapPoolByMods = new BindableBool(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using osu.Game.Tournament.Models;
|
using osu.Game.Tournament.Models;
|
||||||
|
|
||||||
namespace osu.Game.Tournament.Screens.Ladder.Components
|
namespace osu.Game.Tournament.Screens.Ladder.Components
|
||||||
|
@ -24,7 +24,7 @@ namespace osu.Game.Tournament.Screens.MapPool
|
|||||||
{
|
{
|
||||||
public partial class MapPoolScreen : TournamentMatchScreen
|
public partial class MapPoolScreen : TournamentMatchScreen
|
||||||
{
|
{
|
||||||
private readonly FillFlowContainer<FillFlowContainer<TournamentBeatmapPanel>> mapFlows;
|
private FillFlowContainer<FillFlowContainer<TournamentBeatmapPanel>> mapFlows;
|
||||||
|
|
||||||
[Resolved(canBeNull: true)]
|
[Resolved(canBeNull: true)]
|
||||||
private TournamentSceneManager sceneManager { get; set; }
|
private TournamentSceneManager sceneManager { get; set; }
|
||||||
@ -32,12 +32,13 @@ namespace osu.Game.Tournament.Screens.MapPool
|
|||||||
private TeamColour pickColour;
|
private TeamColour pickColour;
|
||||||
private ChoiceType pickType;
|
private ChoiceType pickType;
|
||||||
|
|
||||||
private readonly OsuButton buttonRedBan;
|
private OsuButton buttonRedBan;
|
||||||
private readonly OsuButton buttonBlueBan;
|
private OsuButton buttonBlueBan;
|
||||||
private readonly OsuButton buttonRedPick;
|
private OsuButton buttonRedPick;
|
||||||
private readonly OsuButton buttonBluePick;
|
private OsuButton buttonBluePick;
|
||||||
|
|
||||||
public MapPoolScreen()
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(MatchIPCInfo ipc)
|
||||||
{
|
{
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
@ -98,15 +99,26 @@ namespace osu.Game.Tournament.Screens.MapPool
|
|||||||
Action = reset
|
Action = reset
|
||||||
},
|
},
|
||||||
new ControlPanel.Spacer(),
|
new ControlPanel.Spacer(),
|
||||||
|
new OsuCheckbox
|
||||||
|
{
|
||||||
|
LabelText = "Split display by mods",
|
||||||
|
Current = LadderInfo.SplitMapPoolByMods,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ipc.Beatmap.BindValueChanged(beatmapChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
private Bindable<bool> splitMapPoolByMods;
|
||||||
private void load(MatchIPCInfo ipc)
|
|
||||||
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
ipc.Beatmap.BindValueChanged(beatmapChanged);
|
base.LoadComplete();
|
||||||
|
|
||||||
|
splitMapPoolByMods = LadderInfo.SplitMapPoolByMods.GetBoundCopy();
|
||||||
|
splitMapPoolByMods.BindValueChanged(_ => updateDisplay());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void beatmapChanged(ValueChangedEvent<TournamentBeatmap> beatmap)
|
private void beatmapChanged(ValueChangedEvent<TournamentBeatmap> beatmap)
|
||||||
@ -213,24 +225,27 @@ namespace osu.Game.Tournament.Screens.MapPool
|
|||||||
protected override void CurrentMatchChanged(ValueChangedEvent<TournamentMatch> match)
|
protected override void CurrentMatchChanged(ValueChangedEvent<TournamentMatch> match)
|
||||||
{
|
{
|
||||||
base.CurrentMatchChanged(match);
|
base.CurrentMatchChanged(match);
|
||||||
|
updateDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateDisplay()
|
||||||
|
{
|
||||||
mapFlows.Clear();
|
mapFlows.Clear();
|
||||||
|
|
||||||
if (match.NewValue == null)
|
if (CurrentMatch.Value == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
int totalRows = 0;
|
int totalRows = 0;
|
||||||
|
|
||||||
if (match.NewValue.Round.Value != null)
|
if (CurrentMatch.Value.Round.Value != null)
|
||||||
{
|
{
|
||||||
FillFlowContainer<TournamentBeatmapPanel> currentFlow = null;
|
FillFlowContainer<TournamentBeatmapPanel> currentFlow = null;
|
||||||
string currentMod = null;
|
string currentMods = null;
|
||||||
|
|
||||||
int flowCount = 0;
|
int flowCount = 0;
|
||||||
|
|
||||||
foreach (var b in match.NewValue.Round.Value.Beatmaps)
|
foreach (var b in CurrentMatch.Value.Round.Value.Beatmaps)
|
||||||
{
|
{
|
||||||
if (currentFlow == null || currentMod != b.Mods)
|
if (currentFlow == null || (LadderInfo.SplitMapPoolByMods.Value && currentMods != b.Mods))
|
||||||
{
|
{
|
||||||
mapFlows.Add(currentFlow = new FillFlowContainer<TournamentBeatmapPanel>
|
mapFlows.Add(currentFlow = new FillFlowContainer<TournamentBeatmapPanel>
|
||||||
{
|
{
|
||||||
@ -240,7 +255,7 @@ namespace osu.Game.Tournament.Screens.MapPool
|
|||||||
AutoSizeAxes = Axes.Y
|
AutoSizeAxes = Axes.Y
|
||||||
});
|
});
|
||||||
|
|
||||||
currentMod = b.Mods;
|
currentMods = b.Mods;
|
||||||
|
|
||||||
totalRows++;
|
totalRows++;
|
||||||
flowCount = 0;
|
flowCount = 0;
|
||||||
|
@ -171,6 +171,11 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
public double TimelineZoom { get; set; } = 1.0;
|
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]
|
[Ignored]
|
||||||
public CountdownType Countdown { get; set; } = CountdownType.Normal;
|
public CountdownType Countdown { get; set; } = CountdownType.Normal;
|
||||||
|
|
||||||
|
@ -0,0 +1,95 @@
|
|||||||
|
// 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.IO;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using osu.Framework.Graphics.Textures;
|
||||||
|
using osu.Framework.IO.Stores;
|
||||||
|
using SixLabors.ImageSharp;
|
||||||
|
using SixLabors.ImageSharp.Processing;
|
||||||
|
|
||||||
|
namespace osu.Game.Beatmaps
|
||||||
|
{
|
||||||
|
// Implementation of this class is based off of `MaxDimensionLimitedTextureLoaderStore`.
|
||||||
|
// If issues are found it's worth checking to make sure similar issues exist there.
|
||||||
|
public class BeatmapPanelBackgroundTextureLoaderStore : IResourceStore<TextureUpload>
|
||||||
|
{
|
||||||
|
// The aspect ratio of SetPanelBackground at its maximum size (very tall window).
|
||||||
|
private const float minimum_display_ratio = 512 / 80f;
|
||||||
|
|
||||||
|
private readonly IResourceStore<TextureUpload>? textureStore;
|
||||||
|
|
||||||
|
public BeatmapPanelBackgroundTextureLoaderStore(IResourceStore<TextureUpload>? textureStore)
|
||||||
|
{
|
||||||
|
this.textureStore = textureStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
textureStore?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextureUpload Get(string name)
|
||||||
|
{
|
||||||
|
var textureUpload = textureStore?.Get(name);
|
||||||
|
|
||||||
|
// NRT not enabled on framework side classes (IResourceStore / TextureLoaderStore), welp.
|
||||||
|
if (textureUpload == null)
|
||||||
|
return null!;
|
||||||
|
|
||||||
|
return limitTextureUploadSize(textureUpload);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<TextureUpload> GetAsync(string name, CancellationToken cancellationToken = new CancellationToken())
|
||||||
|
{
|
||||||
|
// NRT not enabled on framework side classes (IResourceStore / TextureLoaderStore), welp.
|
||||||
|
if (textureStore == null)
|
||||||
|
return null!;
|
||||||
|
|
||||||
|
var textureUpload = await textureStore.GetAsync(name, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (textureUpload == null)
|
||||||
|
return null!;
|
||||||
|
|
||||||
|
return await Task.Run(() => limitTextureUploadSize(textureUpload), cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TextureUpload limitTextureUploadSize(TextureUpload textureUpload)
|
||||||
|
{
|
||||||
|
var image = Image.LoadPixelData(textureUpload.Data.ToArray(), textureUpload.Width, textureUpload.Height);
|
||||||
|
|
||||||
|
// The original texture upload will no longer be returned or used.
|
||||||
|
textureUpload.Dispose();
|
||||||
|
|
||||||
|
Size size = image.Size();
|
||||||
|
|
||||||
|
// Assume that panel backgrounds are always displayed using `FillMode.Fill`.
|
||||||
|
// Also assume that all backgrounds are wider than they are tall, so the
|
||||||
|
// fill is always going to be based on width.
|
||||||
|
//
|
||||||
|
// We need to include enough height to make this work for all ratio panels are displayed at.
|
||||||
|
int usableHeight = (int)Math.Ceiling(size.Width * 1 / minimum_display_ratio);
|
||||||
|
|
||||||
|
usableHeight = Math.Min(size.Height, usableHeight);
|
||||||
|
|
||||||
|
// Crop the centre region of the background for now.
|
||||||
|
Rectangle cropRectangle = new Rectangle(
|
||||||
|
0,
|
||||||
|
(size.Height - usableHeight) / 2,
|
||||||
|
size.Width,
|
||||||
|
usableHeight
|
||||||
|
);
|
||||||
|
|
||||||
|
image.Mutate(i => i.Crop(cropRectangle));
|
||||||
|
|
||||||
|
return new TextureUpload(image);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Stream? GetStream(string name) => textureStore?.GetStream(name);
|
||||||
|
|
||||||
|
public IEnumerable<string> GetAvailableResources() => textureStore?.GetAvailableResources() ?? Array.Empty<string>();
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace osu.Game.Beatmaps
|
namespace osu.Game.Beatmaps
|
||||||
{
|
{
|
||||||
public enum DifficultyRating
|
public enum DifficultyRating
|
||||||
|
@ -23,8 +23,9 @@ namespace osu.Game.Beatmaps.Drawables
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
if (working.Background != null)
|
var background = working.GetBackground();
|
||||||
Texture = working.Background;
|
if (background != null)
|
||||||
|
Texture = background;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -106,12 +106,11 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
|||||||
{
|
{
|
||||||
new Drawable[]
|
new Drawable[]
|
||||||
{
|
{
|
||||||
new OsuSpriteText
|
new TruncatingSpriteText
|
||||||
{
|
{
|
||||||
Text = new RomanisableString(BeatmapSet.TitleUnicode, BeatmapSet.Title),
|
Text = new RomanisableString(BeatmapSet.TitleUnicode, BeatmapSet.Title),
|
||||||
Font = OsuFont.Default.With(size: 22.5f, weight: FontWeight.SemiBold),
|
Font = OsuFont.Default.With(size: 22.5f, weight: FontWeight.SemiBold),
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Truncate = true
|
|
||||||
},
|
},
|
||||||
titleBadgeArea = new FillFlowContainer
|
titleBadgeArea = new FillFlowContainer
|
||||||
{
|
{
|
||||||
@ -140,21 +139,19 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
|||||||
{
|
{
|
||||||
new[]
|
new[]
|
||||||
{
|
{
|
||||||
new OsuSpriteText
|
new TruncatingSpriteText
|
||||||
{
|
{
|
||||||
Text = createArtistText(),
|
Text = createArtistText(),
|
||||||
Font = OsuFont.Default.With(size: 17.5f, weight: FontWeight.SemiBold),
|
Font = OsuFont.Default.With(size: 17.5f, weight: FontWeight.SemiBold),
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Truncate = true
|
|
||||||
},
|
},
|
||||||
Empty()
|
Empty()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new OsuSpriteText
|
new TruncatingSpriteText
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Truncate = true,
|
|
||||||
Text = BeatmapSet.Source,
|
Text = BeatmapSet.Source,
|
||||||
Shadow = false,
|
Shadow = false,
|
||||||
Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold),
|
Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold),
|
||||||
|
@ -107,12 +107,11 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
|||||||
{
|
{
|
||||||
new Drawable[]
|
new Drawable[]
|
||||||
{
|
{
|
||||||
new OsuSpriteText
|
new TruncatingSpriteText
|
||||||
{
|
{
|
||||||
Text = new RomanisableString(BeatmapSet.TitleUnicode, BeatmapSet.Title),
|
Text = new RomanisableString(BeatmapSet.TitleUnicode, BeatmapSet.Title),
|
||||||
Font = OsuFont.Default.With(size: 22.5f, weight: FontWeight.SemiBold),
|
Font = OsuFont.Default.With(size: 22.5f, weight: FontWeight.SemiBold),
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Truncate = true
|
|
||||||
},
|
},
|
||||||
titleBadgeArea = new FillFlowContainer
|
titleBadgeArea = new FillFlowContainer
|
||||||
{
|
{
|
||||||
@ -141,12 +140,11 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
|||||||
{
|
{
|
||||||
new[]
|
new[]
|
||||||
{
|
{
|
||||||
new OsuSpriteText
|
new TruncatingSpriteText
|
||||||
{
|
{
|
||||||
Text = createArtistText(),
|
Text = createArtistText(),
|
||||||
Font = OsuFont.Default.With(size: 17.5f, weight: FontWeight.SemiBold),
|
Font = OsuFont.Default.With(size: 17.5f, weight: FontWeight.SemiBold),
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Truncate = true
|
|
||||||
},
|
},
|
||||||
Empty()
|
Empty()
|
||||||
},
|
},
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace osu.Game.Beatmaps.Drawables.Cards
|
namespace osu.Game.Beatmaps.Drawables.Cards
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -52,7 +52,7 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
protected override IBeatmap GetBeatmap() => new Beatmap();
|
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();
|
protected override Track GetBeatmapTrack() => GetVirtualTrack();
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ namespace osu.Game.Beatmaps
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected override IBeatmap GetBeatmap() => beatmap;
|
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 override Track GetBeatmapTrack() => throw new NotImplementedException();
|
||||||
protected internal override ISkin GetSkin() => throw new NotImplementedException();
|
protected internal override ISkin GetSkin() => throw new NotImplementedException();
|
||||||
public override Stream GetStream(string storagePath) => throw new NotImplementedException();
|
public override Stream GetStream(string storagePath) => throw new NotImplementedException();
|
||||||
|
@ -9,13 +9,18 @@ using osu.Game.IO;
|
|||||||
|
|
||||||
namespace osu.Game.Beatmaps
|
namespace osu.Game.Beatmaps
|
||||||
{
|
{
|
||||||
public interface IBeatmapResourceProvider : IStorageResourceProvider
|
internal interface IBeatmapResourceProvider : IStorageResourceProvider
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieve a global large texture store, used for loading beatmap backgrounds.
|
/// Retrieve a global large texture store, used for loading beatmap backgrounds.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
TextureStore LargeTextureStore { get; }
|
TextureStore LargeTextureStore { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve a global large texture store, used specifically for retrieving cropped beatmap panel backgrounds.
|
||||||
|
/// </summary>
|
||||||
|
TextureStore BeatmapPanelTextureStore { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Access a global track store for retrieving beatmap tracks from.
|
/// Access a global track store for retrieving beatmap tracks from.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -32,12 +32,12 @@ namespace osu.Game.Beatmaps
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether the Beatmap has finished loading.
|
/// Whether the Beatmap has finished loading.
|
||||||
///</summary>
|
///</summary>
|
||||||
public bool BeatmapLoaded { get; }
|
bool BeatmapLoaded { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether the Track has finished loading.
|
/// Whether the Track has finished loading.
|
||||||
///</summary>
|
///</summary>
|
||||||
public bool TrackLoaded { get; }
|
bool TrackLoaded { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves the <see cref="IBeatmap"/> which this <see cref="IWorkingBeatmap"/> represents.
|
/// Retrieves the <see cref="IBeatmap"/> which this <see cref="IWorkingBeatmap"/> represents.
|
||||||
@ -47,7 +47,12 @@ namespace osu.Game.Beatmaps
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves the background for this <see cref="IWorkingBeatmap"/>.
|
/// Retrieves the background for this <see cref="IWorkingBeatmap"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Texture Background { get; }
|
Texture GetBackground();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves a cropped background for this <see cref="IWorkingBeatmap"/> used for display on panels.
|
||||||
|
/// </summary>
|
||||||
|
Texture GetPanelBackground();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves the <see cref="Waveform"/> for the <see cref="Track"/> of this <see cref="IWorkingBeatmap"/>.
|
/// Retrieves the <see cref="Waveform"/> for the <see cref="Track"/> of this <see cref="IWorkingBeatmap"/>.
|
||||||
@ -124,12 +129,12 @@ namespace osu.Game.Beatmaps
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Beings loading the contents of this <see cref="IWorkingBeatmap"/> asynchronously.
|
/// Beings loading the contents of this <see cref="IWorkingBeatmap"/> asynchronously.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void BeginAsyncLoad();
|
void BeginAsyncLoad();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Cancels the asynchronous loading of the contents of this <see cref="IWorkingBeatmap"/>.
|
/// Cancels the asynchronous loading of the contents of this <see cref="IWorkingBeatmap"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void CancelAsyncLoad();
|
void CancelAsyncLoad();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reads the correct track restart point from beatmap metadata and sets looping to enabled.
|
/// Reads the correct track restart point from beatmap metadata and sets looping to enabled.
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace osu.Game.Beatmaps.Legacy
|
namespace osu.Game.Beatmaps.Legacy
|
||||||
{
|
{
|
||||||
internal enum LegacyOrigins
|
internal enum LegacyOrigins
|
||||||
|
@ -34,8 +34,6 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
public Storyboard Storyboard => storyboard.Value;
|
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;
|
public ISkin Skin => skin.Value;
|
||||||
|
|
||||||
private AudioManager audioManager { get; }
|
private AudioManager audioManager { get; }
|
||||||
@ -67,7 +65,8 @@ namespace osu.Game.Beatmaps
|
|||||||
protected virtual Storyboard GetStoryboard() => new Storyboard { BeatmapInfo = BeatmapInfo };
|
protected virtual Storyboard GetStoryboard() => new Storyboard { BeatmapInfo = BeatmapInfo };
|
||||||
|
|
||||||
protected abstract IBeatmap GetBeatmap();
|
protected abstract IBeatmap GetBeatmap();
|
||||||
protected abstract Texture GetBackground();
|
public abstract Texture GetBackground();
|
||||||
|
public virtual Texture GetPanelBackground() => GetBackground();
|
||||||
protected abstract Track GetBeatmapTrack();
|
protected abstract Track GetBeatmapTrack();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -42,6 +42,7 @@ namespace osu.Game.Beatmaps
|
|||||||
private readonly AudioManager audioManager;
|
private readonly AudioManager audioManager;
|
||||||
private readonly IResourceStore<byte[]> resources;
|
private readonly IResourceStore<byte[]> resources;
|
||||||
private readonly LargeTextureStore largeTextureStore;
|
private readonly LargeTextureStore largeTextureStore;
|
||||||
|
private readonly LargeTextureStore beatmapPanelTextureStore;
|
||||||
private readonly ITrackStore trackStore;
|
private readonly ITrackStore trackStore;
|
||||||
private readonly IResourceStore<byte[]> files;
|
private readonly IResourceStore<byte[]> files;
|
||||||
|
|
||||||
@ -58,6 +59,7 @@ namespace osu.Game.Beatmaps
|
|||||||
this.host = host;
|
this.host = host;
|
||||||
this.files = files;
|
this.files = files;
|
||||||
largeTextureStore = new LargeTextureStore(host?.Renderer ?? new DummyRenderer(), host?.CreateTextureLoaderStore(files));
|
largeTextureStore = new LargeTextureStore(host?.Renderer ?? new DummyRenderer(), host?.CreateTextureLoaderStore(files));
|
||||||
|
beatmapPanelTextureStore = new LargeTextureStore(host?.Renderer ?? new DummyRenderer(), new BeatmapPanelBackgroundTextureLoaderStore(host?.CreateTextureLoaderStore(files)));
|
||||||
this.trackStore = trackStore;
|
this.trackStore = trackStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,6 +112,7 @@ namespace osu.Game.Beatmaps
|
|||||||
#region IResourceStorageProvider
|
#region IResourceStorageProvider
|
||||||
|
|
||||||
TextureStore IBeatmapResourceProvider.LargeTextureStore => largeTextureStore;
|
TextureStore IBeatmapResourceProvider.LargeTextureStore => largeTextureStore;
|
||||||
|
TextureStore IBeatmapResourceProvider.BeatmapPanelTextureStore => beatmapPanelTextureStore;
|
||||||
ITrackStore IBeatmapResourceProvider.Tracks => trackStore;
|
ITrackStore IBeatmapResourceProvider.Tracks => trackStore;
|
||||||
IRenderer IStorageResourceProvider.Renderer => host?.Renderer ?? new DummyRenderer();
|
IRenderer IStorageResourceProvider.Renderer => host?.Renderer ?? new DummyRenderer();
|
||||||
AudioManager IStorageResourceProvider.AudioManager => audioManager;
|
AudioManager IStorageResourceProvider.AudioManager => audioManager;
|
||||||
@ -160,7 +163,11 @@ namespace osu.Game.Beatmaps
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Texture GetBackground()
|
public override Texture GetPanelBackground() => getBackgroundFromStore(resources.BeatmapPanelTextureStore);
|
||||||
|
|
||||||
|
public override Texture GetBackground() => getBackgroundFromStore(resources.LargeTextureStore);
|
||||||
|
|
||||||
|
private Texture getBackgroundFromStore(TextureStore store)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(Metadata?.BackgroundFile))
|
if (string.IsNullOrEmpty(Metadata?.BackgroundFile))
|
||||||
return null;
|
return null;
|
||||||
@ -168,7 +175,7 @@ namespace osu.Game.Beatmaps
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
string fileStorePath = BeatmapSetInfo.GetPathForFile(Metadata.BackgroundFile);
|
string fileStorePath = BeatmapSetInfo.GetPathForFile(Metadata.BackgroundFile);
|
||||||
var texture = resources.LargeTextureStore.Get(fileStorePath);
|
var texture = store.Get(fileStorePath);
|
||||||
|
|
||||||
if (texture == null)
|
if (texture == null)
|
||||||
{
|
{
|
||||||
@ -257,7 +264,7 @@ namespace osu.Game.Beatmaps
|
|||||||
if (beatmapFileStream == null)
|
if (beatmapFileStream == null)
|
||||||
{
|
{
|
||||||
Logger.Log($"Beatmap failed to load (file {BeatmapInfo.Path} not found on disk at expected location {fileStorePath})", level: LogLevel.Error);
|
Logger.Log($"Beatmap failed to load (file {BeatmapInfo.Path} not found on disk at expected location {fileStorePath})", level: LogLevel.Error);
|
||||||
return null;
|
return new Storyboard();
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var reader = new LineBufferedReader(beatmapFileStream))
|
using (var reader = new LineBufferedReader(beatmapFileStream))
|
||||||
|
@ -86,6 +86,7 @@ namespace osu.Game.Collections
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Size = Vector2.One,
|
Size = Vector2.One,
|
||||||
CornerRadius = item_height / 2,
|
CornerRadius = item_height / 2,
|
||||||
|
CommitOnFocusLost = true,
|
||||||
PlaceholderText = collection.IsManaged ? string.Empty : "Create a new collection"
|
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.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace osu.Game.Configuration
|
namespace osu.Game.Configuration
|
||||||
{
|
{
|
||||||
public enum IntroSequence
|
public enum IntroSequence
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace osu.Game.Configuration
|
namespace osu.Game.Configuration
|
||||||
{
|
{
|
||||||
public enum ReleaseStream
|
public enum ReleaseStream
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Overlays.Mods;
|
||||||
|
|
||||||
namespace osu.Game.Configuration
|
namespace osu.Game.Configuration
|
||||||
{
|
{
|
||||||
@ -21,6 +22,7 @@ namespace osu.Game.Configuration
|
|||||||
SetDefault(Static.LowBatteryNotificationShownOnce, false);
|
SetDefault(Static.LowBatteryNotificationShownOnce, false);
|
||||||
SetDefault(Static.FeaturedArtistDisclaimerShownOnce, false);
|
SetDefault(Static.FeaturedArtistDisclaimerShownOnce, false);
|
||||||
SetDefault(Static.LastHoverSoundPlaybackTime, (double?)null);
|
SetDefault(Static.LastHoverSoundPlaybackTime, (double?)null);
|
||||||
|
SetDefault(Static.LastModSelectPanelSamplePlaybackTime, (double?)null);
|
||||||
SetDefault<APISeasonalBackgrounds>(Static.SeasonalBackgrounds, null);
|
SetDefault<APISeasonalBackgrounds>(Static.SeasonalBackgrounds, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,5 +58,11 @@ namespace osu.Game.Configuration
|
|||||||
/// Used to debounce hover sounds game-wide to avoid volume saturation, especially in scrolling views with many UI controls like <see cref="SettingsOverlay"/>.
|
/// Used to debounce hover sounds game-wide to avoid volume saturation, especially in scrolling views with many UI controls like <see cref="SettingsOverlay"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
LastHoverSoundPlaybackTime,
|
LastHoverSoundPlaybackTime,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The last playback time in milliseconds of an on/off sample (from <see cref="ModSelectPanel"/>).
|
||||||
|
/// Used to debounce <see cref="ModSelectPanel"/> on/off sounds game-wide to avoid volume saturation, especially in activating mod presets with many mods.
|
||||||
|
/// </summary>
|
||||||
|
LastModSelectPanelSamplePlaybackTime
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace osu.Game.Configuration
|
namespace osu.Game.Configuration
|
||||||
{
|
{
|
||||||
public enum ToolbarClockDisplayMode
|
public enum ToolbarClockDisplayMode
|
||||||
|
@ -1,13 +1,10 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using JetBrains.Annotations;
|
|
||||||
using osu.Game.Online.API.Requests;
|
using osu.Game.Online.API.Requests;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
|
||||||
@ -21,8 +18,7 @@ namespace osu.Game.Database
|
|||||||
/// <param name="beatmapId">The beatmap to lookup.</param>
|
/// <param name="beatmapId">The beatmap to lookup.</param>
|
||||||
/// <param name="token">An optional cancellation token.</param>
|
/// <param name="token">An optional cancellation token.</param>
|
||||||
/// <returns>The populated beatmap, or null if the beatmap does not exist or the request could not be satisfied.</returns>
|
/// <returns>The populated beatmap, or null if the beatmap does not exist or the request could not be satisfied.</returns>
|
||||||
[ItemCanBeNull]
|
public Task<APIBeatmap?> GetBeatmapAsync(int beatmapId, CancellationToken token = default) => LookupAsync(beatmapId, token);
|
||||||
public Task<APIBeatmap> GetBeatmapAsync(int beatmapId, CancellationToken token = default) => LookupAsync(beatmapId, token);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Perform an API lookup on the specified beatmaps, populating a <see cref="APIBeatmap"/> model.
|
/// Perform an API lookup on the specified beatmaps, populating a <see cref="APIBeatmap"/> model.
|
||||||
@ -30,10 +26,10 @@ namespace osu.Game.Database
|
|||||||
/// <param name="beatmapIds">The beatmaps to lookup.</param>
|
/// <param name="beatmapIds">The beatmaps to lookup.</param>
|
||||||
/// <param name="token">An optional cancellation token.</param>
|
/// <param name="token">An optional cancellation token.</param>
|
||||||
/// <returns>The populated beatmaps. May include null results for failed retrievals.</returns>
|
/// <returns>The populated beatmaps. May include null results for failed retrievals.</returns>
|
||||||
public Task<APIBeatmap[]> GetBeatmapsAsync(int[] beatmapIds, CancellationToken token = default) => LookupAsync(beatmapIds, token);
|
public Task<APIBeatmap?[]> GetBeatmapsAsync(int[] beatmapIds, CancellationToken token = default) => LookupAsync(beatmapIds, token);
|
||||||
|
|
||||||
protected override GetBeatmapsRequest CreateRequest(IEnumerable<int> ids) => new GetBeatmapsRequest(ids.ToArray());
|
protected override GetBeatmapsRequest CreateRequest(IEnumerable<int> ids) => new GetBeatmapsRequest(ids.ToArray());
|
||||||
|
|
||||||
protected override IEnumerable<APIBeatmap> RetrieveResults(GetBeatmapsRequest request) => request.Response?.Beatmaps;
|
protected override IEnumerable<APIBeatmap>? RetrieveResults(GetBeatmapsRequest request) => request.Response?.Beatmaps;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace osu.Game.Database
|
namespace osu.Game.Database
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using JetBrains.Annotations;
|
|
||||||
using osu.Framework.Extensions.TypeExtensions;
|
using osu.Framework.Extensions.TypeExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Statistics;
|
using osu.Framework.Statistics;
|
||||||
@ -19,8 +17,9 @@ namespace osu.Game.Database
|
|||||||
/// Currently not persisted between game sessions.
|
/// Currently not persisted between game sessions.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract partial class MemoryCachingComponent<TLookup, TValue> : Component
|
public abstract partial class MemoryCachingComponent<TLookup, TValue> : Component
|
||||||
|
where TLookup : notnull
|
||||||
{
|
{
|
||||||
private readonly ConcurrentDictionary<TLookup, TValue> cache = new ConcurrentDictionary<TLookup, TValue>();
|
private readonly ConcurrentDictionary<TLookup, TValue?> cache = new ConcurrentDictionary<TLookup, TValue?>();
|
||||||
|
|
||||||
private readonly GlobalStatistic<MemoryCachingStatistics> statistics;
|
private readonly GlobalStatistic<MemoryCachingStatistics> statistics;
|
||||||
|
|
||||||
@ -37,12 +36,12 @@ namespace osu.Game.Database
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="lookup">The lookup to retrieve.</param>
|
/// <param name="lookup">The lookup to retrieve.</param>
|
||||||
/// <param name="token">An optional <see cref="CancellationToken"/> to cancel the operation.</param>
|
/// <param name="token">An optional <see cref="CancellationToken"/> to cancel the operation.</param>
|
||||||
protected async Task<TValue> GetAsync([NotNull] TLookup lookup, CancellationToken token = default)
|
protected async Task<TValue?> GetAsync(TLookup lookup, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
if (CheckExists(lookup, out TValue performance))
|
if (CheckExists(lookup, out TValue? existing))
|
||||||
{
|
{
|
||||||
statistics.Value.HitCount++;
|
statistics.Value.HitCount++;
|
||||||
return performance;
|
return existing;
|
||||||
}
|
}
|
||||||
|
|
||||||
var computed = await ComputeValueAsync(lookup, token).ConfigureAwait(false);
|
var computed = await ComputeValueAsync(lookup, token).ConfigureAwait(false);
|
||||||
@ -73,7 +72,7 @@ namespace osu.Game.Database
|
|||||||
statistics.Value.Usage = cache.Count;
|
statistics.Value.Usage = cache.Count;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected bool CheckExists([NotNull] TLookup lookup, out TValue value) =>
|
protected bool CheckExists(TLookup lookup, [MaybeNullWhen(false)] out TValue value) =>
|
||||||
cache.TryGetValue(lookup, out value);
|
cache.TryGetValue(lookup, out value);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -82,7 +81,7 @@ namespace osu.Game.Database
|
|||||||
/// <param name="lookup">The lookup to retrieve.</param>
|
/// <param name="lookup">The lookup to retrieve.</param>
|
||||||
/// <param name="token">An optional <see cref="CancellationToken"/> to cancel the operation.</param>
|
/// <param name="token">An optional <see cref="CancellationToken"/> to cancel the operation.</param>
|
||||||
/// <returns>The computed value.</returns>
|
/// <returns>The computed value.</returns>
|
||||||
protected abstract Task<TValue> ComputeValueAsync(TLookup lookup, CancellationToken token = default);
|
protected abstract Task<TValue?> ComputeValueAsync(TLookup lookup, CancellationToken token = default);
|
||||||
|
|
||||||
private class MemoryCachingStatistics
|
private class MemoryCachingStatistics
|
||||||
{
|
{
|
||||||
|
@ -1,14 +1,11 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using JetBrains.Annotations;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
@ -21,7 +18,7 @@ namespace osu.Game.Database
|
|||||||
where TRequest : APIRequest
|
where TRequest : APIRequest
|
||||||
{
|
{
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private IAPIProvider api { get; set; }
|
private IAPIProvider api { get; set; } = null!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates an <see cref="APIRequest"/> to retrieve the values for a given collection of <typeparamref name="TLookup"/>s.
|
/// Creates an <see cref="APIRequest"/> to retrieve the values for a given collection of <typeparamref name="TLookup"/>s.
|
||||||
@ -32,8 +29,7 @@ namespace osu.Game.Database
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves a list of <typeparamref name="TValue"/>s from a successful <typeparamref name="TRequest"/> created by <see cref="CreateRequest"/>.
|
/// Retrieves a list of <typeparamref name="TValue"/>s from a successful <typeparamref name="TRequest"/> created by <see cref="CreateRequest"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[CanBeNull]
|
protected abstract IEnumerable<TValue>? RetrieveResults(TRequest request);
|
||||||
protected abstract IEnumerable<TValue> RetrieveResults(TRequest request);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Perform a lookup using the specified <paramref name="id"/>, populating a <typeparamref name="TValue"/>.
|
/// Perform a lookup using the specified <paramref name="id"/>, populating a <typeparamref name="TValue"/>.
|
||||||
@ -41,8 +37,7 @@ namespace osu.Game.Database
|
|||||||
/// <param name="id">The ID to lookup.</param>
|
/// <param name="id">The ID to lookup.</param>
|
||||||
/// <param name="token">An optional cancellation token.</param>
|
/// <param name="token">An optional cancellation token.</param>
|
||||||
/// <returns>The populated <typeparamref name="TValue"/>, or null if the value does not exist or the request could not be satisfied.</returns>
|
/// <returns>The populated <typeparamref name="TValue"/>, or null if the value does not exist or the request could not be satisfied.</returns>
|
||||||
[ItemCanBeNull]
|
protected Task<TValue?> LookupAsync(TLookup id, CancellationToken token = default) => GetAsync(id, token);
|
||||||
protected Task<TValue> LookupAsync(TLookup id, CancellationToken token = default) => GetAsync(id, token);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Perform an API lookup on the specified <paramref name="ids"/>, populating a <typeparamref name="TValue"/>.
|
/// Perform an API lookup on the specified <paramref name="ids"/>, populating a <typeparamref name="TValue"/>.
|
||||||
@ -50,9 +45,9 @@ namespace osu.Game.Database
|
|||||||
/// <param name="ids">The IDs to lookup.</param>
|
/// <param name="ids">The IDs to lookup.</param>
|
||||||
/// <param name="token">An optional cancellation token.</param>
|
/// <param name="token">An optional cancellation token.</param>
|
||||||
/// <returns>The populated values. May include null results for failed retrievals.</returns>
|
/// <returns>The populated values. May include null results for failed retrievals.</returns>
|
||||||
protected Task<TValue[]> LookupAsync(TLookup[] ids, CancellationToken token = default)
|
protected Task<TValue?[]> LookupAsync(TLookup[] ids, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
var lookupTasks = new List<Task<TValue>>();
|
var lookupTasks = new List<Task<TValue?>>();
|
||||||
|
|
||||||
foreach (var id in ids)
|
foreach (var id in ids)
|
||||||
{
|
{
|
||||||
@ -69,18 +64,18 @@ namespace osu.Game.Database
|
|||||||
}
|
}
|
||||||
|
|
||||||
// cannot be sealed due to test usages (see TestUserLookupCache).
|
// cannot be sealed due to test usages (see TestUserLookupCache).
|
||||||
protected override async Task<TValue> ComputeValueAsync(TLookup lookup, CancellationToken token = default)
|
protected override async Task<TValue?> ComputeValueAsync(TLookup lookup, CancellationToken token = default)
|
||||||
=> await queryValue(lookup).ConfigureAwait(false);
|
=> await queryValue(lookup).ConfigureAwait(false);
|
||||||
|
|
||||||
private readonly Queue<(TLookup id, TaskCompletionSource<TValue>)> pendingTasks = new Queue<(TLookup, TaskCompletionSource<TValue>)>();
|
private readonly Queue<(TLookup id, TaskCompletionSource<TValue?>)> pendingTasks = new Queue<(TLookup, TaskCompletionSource<TValue?>)>();
|
||||||
private Task pendingRequestTask;
|
private Task? pendingRequestTask;
|
||||||
private readonly object taskAssignmentLock = new object();
|
private readonly object taskAssignmentLock = new object();
|
||||||
|
|
||||||
private Task<TValue> queryValue(TLookup id)
|
private Task<TValue?> queryValue(TLookup id)
|
||||||
{
|
{
|
||||||
lock (taskAssignmentLock)
|
lock (taskAssignmentLock)
|
||||||
{
|
{
|
||||||
var tcs = new TaskCompletionSource<TValue>();
|
var tcs = new TaskCompletionSource<TValue?>();
|
||||||
|
|
||||||
// Add to the queue.
|
// Add to the queue.
|
||||||
pendingTasks.Enqueue((id, tcs));
|
pendingTasks.Enqueue((id, tcs));
|
||||||
@ -96,14 +91,14 @@ namespace osu.Game.Database
|
|||||||
private async Task performLookup()
|
private async Task performLookup()
|
||||||
{
|
{
|
||||||
// contains at most 50 unique IDs from tasks, which is used to perform the lookup.
|
// contains at most 50 unique IDs from tasks, which is used to perform the lookup.
|
||||||
var nextTaskBatch = new Dictionary<TLookup, List<TaskCompletionSource<TValue>>>();
|
var nextTaskBatch = new Dictionary<TLookup, List<TaskCompletionSource<TValue?>>>();
|
||||||
|
|
||||||
// Grab at most 50 unique IDs from the queue.
|
// Grab at most 50 unique IDs from the queue.
|
||||||
lock (taskAssignmentLock)
|
lock (taskAssignmentLock)
|
||||||
{
|
{
|
||||||
while (pendingTasks.Count > 0 && nextTaskBatch.Count < 50)
|
while (pendingTasks.Count > 0 && nextTaskBatch.Count < 50)
|
||||||
{
|
{
|
||||||
(TLookup id, TaskCompletionSource<TValue> task) next = pendingTasks.Dequeue();
|
(TLookup id, TaskCompletionSource<TValue?> task) next = pendingTasks.Dequeue();
|
||||||
|
|
||||||
// Perform a secondary check for existence, in case the value was queried in a previous batch.
|
// Perform a secondary check for existence, in case the value was queried in a previous batch.
|
||||||
if (CheckExists(next.id, out var existing))
|
if (CheckExists(next.id, out var existing))
|
||||||
@ -113,7 +108,7 @@ namespace osu.Game.Database
|
|||||||
if (nextTaskBatch.TryGetValue(next.id, out var tasks))
|
if (nextTaskBatch.TryGetValue(next.id, out var tasks))
|
||||||
tasks.Add(next.task);
|
tasks.Add(next.task);
|
||||||
else
|
else
|
||||||
nextTaskBatch[next.id] = new List<TaskCompletionSource<TValue>> { next.task };
|
nextTaskBatch[next.id] = new List<TaskCompletionSource<TValue?>> { next.task };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,12 +22,15 @@ using osu.Framework.Statistics;
|
|||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Extensions;
|
||||||
using osu.Game.Input.Bindings;
|
using osu.Game.Input.Bindings;
|
||||||
|
using osu.Game.IO.Legacy;
|
||||||
using osu.Game.Models;
|
using osu.Game.Models;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Scoring.Legacy;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using Realms;
|
using Realms;
|
||||||
using Realms.Exceptions;
|
using Realms.Exceptions;
|
||||||
@ -71,8 +74,11 @@ namespace osu.Game.Database
|
|||||||
/// 24 2022-08-22 Added MaximumStatistics to ScoreInfo.
|
/// 24 2022-08-22 Added MaximumStatistics to ScoreInfo.
|
||||||
/// 25 2022-09-18 Remove skins to add with new naming.
|
/// 25 2022-09-18 Remove skins to add with new naming.
|
||||||
/// 26 2023-02-05 Added BeatmapHash to ScoreInfo.
|
/// 26 2023-02-05 Added BeatmapHash to ScoreInfo.
|
||||||
|
/// 27 2023-06-06 Added EditorTimestamp to BeatmapInfo.
|
||||||
|
/// 28 2023-06-08 Added IsLegacyScore to ScoreInfo, parsed from replay files.
|
||||||
|
/// 29 2023-06-12 Run migration of old lazer scores to be best-effort in the new scoring number space. No actual realm changes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private const int schema_version = 26;
|
private const int schema_version = 29;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Lock object which is held during <see cref="BlockAllOperations"/> sections, blocking realm retrieval during blocking periods.
|
/// Lock object which is held during <see cref="BlockAllOperations"/> sections, blocking realm retrieval during blocking periods.
|
||||||
@ -719,6 +725,11 @@ namespace osu.Game.Database
|
|||||||
|
|
||||||
private void applyMigrationsForVersion(Migration migration, ulong targetVersion)
|
private void applyMigrationsForVersion(Migration migration, ulong targetVersion)
|
||||||
{
|
{
|
||||||
|
Logger.Log($"Running realm migration to version {targetVersion}...");
|
||||||
|
Stopwatch stopwatch = new Stopwatch();
|
||||||
|
|
||||||
|
stopwatch.Start();
|
||||||
|
|
||||||
switch (targetVersion)
|
switch (targetVersion)
|
||||||
{
|
{
|
||||||
case 7:
|
case 7:
|
||||||
@ -879,6 +890,7 @@ namespace osu.Game.Database
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 26:
|
case 26:
|
||||||
|
{
|
||||||
// Add ScoreInfo.BeatmapHash property to ensure scores correspond to the correct version of beatmap.
|
// Add ScoreInfo.BeatmapHash property to ensure scores correspond to the correct version of beatmap.
|
||||||
var scores = migration.NewRealm.All<ScoreInfo>();
|
var scores = migration.NewRealm.All<ScoreInfo>();
|
||||||
|
|
||||||
@ -886,7 +898,76 @@ namespace osu.Game.Database
|
|||||||
score.BeatmapHash = score.BeatmapInfo.Hash;
|
score.BeatmapHash = score.BeatmapInfo.Hash;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 28:
|
||||||
|
{
|
||||||
|
var files = new RealmFileStore(this, storage);
|
||||||
|
var scores = migration.NewRealm.All<ScoreInfo>();
|
||||||
|
|
||||||
|
foreach (var score in scores)
|
||||||
|
{
|
||||||
|
string? replayFilename = score.Files.FirstOrDefault(f => f.Filename.EndsWith(@".osr", StringComparison.InvariantCultureIgnoreCase))?.File.GetStoragePath();
|
||||||
|
if (replayFilename == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var stream = files.Store.GetStream(replayFilename))
|
||||||
|
{
|
||||||
|
if (stream == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Trimmed down logic from LegacyScoreDecoder to extract the version from replays.
|
||||||
|
using (SerializationReader sr = new SerializationReader(stream))
|
||||||
|
{
|
||||||
|
sr.ReadByte(); // Ruleset.
|
||||||
|
int version = sr.ReadInt32();
|
||||||
|
if (version < LegacyScoreEncoder.FIRST_LAZER_VERSION)
|
||||||
|
score.IsLegacyScore = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.Error(e, $"Failed to read replay {replayFilename} during score migration", LoggingTarget.Database);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 29:
|
||||||
|
{
|
||||||
|
var scores = migration.NewRealm
|
||||||
|
.All<ScoreInfo>()
|
||||||
|
.Where(s => !s.IsLegacyScore);
|
||||||
|
|
||||||
|
foreach (var score in scores)
|
||||||
|
{
|
||||||
|
// Recalculate the old-style standardised score to see if this was an old lazer score.
|
||||||
|
bool oldScoreMatchesExpectations = StandardisedScoreMigrationTools.GetOldStandardised(score) == score.TotalScore;
|
||||||
|
// Some older scores don't have correct statistics populated, so let's give them benefit of doubt.
|
||||||
|
bool scoreIsVeryOld = score.Date < new DateTime(2023, 1, 1, 0, 0, 0);
|
||||||
|
|
||||||
|
if (oldScoreMatchesExpectations || scoreIsVeryOld)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
long calculatedNew = StandardisedScoreMigrationTools.GetNewStandardised(score);
|
||||||
|
score.TotalScore = calculatedNew;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger.Log($"Migration completed in {stopwatch.ElapsedMilliseconds}ms");
|
||||||
}
|
}
|
||||||
|
|
||||||
private string? getRulesetShortNameFromLegacyID(long rulesetId)
|
private string? getRulesetShortNameFromLegacyID(long rulesetId)
|
||||||
|
196
osu.Game/Database/StandardisedScoreMigrationTools.cs
Normal file
196
osu.Game/Database/StandardisedScoreMigrationTools.cs
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
// 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.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Judgements;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
|
||||||
|
namespace osu.Game.Database
|
||||||
|
{
|
||||||
|
public static class StandardisedScoreMigrationTools
|
||||||
|
{
|
||||||
|
public static long GetNewStandardised(ScoreInfo score)
|
||||||
|
{
|
||||||
|
int maxJudgementIndex = 0;
|
||||||
|
|
||||||
|
// Avoid retrieving from realm inside loops.
|
||||||
|
int maxCombo = score.MaxCombo;
|
||||||
|
|
||||||
|
var ruleset = score.Ruleset.CreateInstance();
|
||||||
|
var processor = ruleset.CreateScoreProcessor();
|
||||||
|
|
||||||
|
processor.TrackHitEvents = false;
|
||||||
|
|
||||||
|
var beatmap = new Beatmap();
|
||||||
|
|
||||||
|
HitResult maxRulesetJudgement = ruleset.GetHitResults().First().result;
|
||||||
|
|
||||||
|
// This is a list of all results, ordered from best to worst.
|
||||||
|
// We are constructing a "best possible" score from the statistics provided because it's the best we can do.
|
||||||
|
List<HitResult> sortedHits = score.Statistics
|
||||||
|
.Where(kvp => kvp.Key.AffectsCombo())
|
||||||
|
.OrderByDescending(kvp => Judgement.ToNumericResult(kvp.Key))
|
||||||
|
.SelectMany(kvp => Enumerable.Repeat(kvp.Key, kvp.Value))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// Attempt to use maximum statistics from the database.
|
||||||
|
var maximumJudgements = score.MaximumStatistics
|
||||||
|
.Where(kvp => kvp.Key.AffectsCombo())
|
||||||
|
.OrderByDescending(kvp => Judgement.ToNumericResult(kvp.Key))
|
||||||
|
.SelectMany(kvp => Enumerable.Repeat(new FakeJudgement(kvp.Key), kvp.Value))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// Some older scores may not have maximum statistics populated correctly.
|
||||||
|
// In this case we need to fill them with best-known-defaults.
|
||||||
|
if (maximumJudgements.Count != sortedHits.Count)
|
||||||
|
{
|
||||||
|
maximumJudgements = sortedHits
|
||||||
|
.Select(r => new FakeJudgement(getMaxJudgementFor(r, maxRulesetJudgement)))
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is required to get the correct maximum combo portion.
|
||||||
|
foreach (var judgement in maximumJudgements)
|
||||||
|
beatmap.HitObjects.Add(new FakeHit(judgement));
|
||||||
|
processor.ApplyBeatmap(beatmap);
|
||||||
|
processor.Mods.Value = score.Mods;
|
||||||
|
|
||||||
|
// Insert all misses into a queue.
|
||||||
|
// These will be nibbled at whenever we need to reset the combo.
|
||||||
|
Queue<HitResult> misses = new Queue<HitResult>(score.Statistics
|
||||||
|
.Where(kvp => kvp.Key == HitResult.Miss || kvp.Key == HitResult.LargeTickMiss)
|
||||||
|
.SelectMany(kvp => Enumerable.Repeat(kvp.Key, kvp.Value)));
|
||||||
|
|
||||||
|
foreach (var result in sortedHits)
|
||||||
|
{
|
||||||
|
// For the main part of this loop, ignore all misses, as they will be inserted from the queue.
|
||||||
|
if (result == HitResult.Miss || result == HitResult.LargeTickMiss)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Reset combo if required.
|
||||||
|
if (processor.Combo.Value == maxCombo)
|
||||||
|
insertMiss();
|
||||||
|
|
||||||
|
processor.ApplyResult(new JudgementResult(null!, maximumJudgements[maxJudgementIndex++])
|
||||||
|
{
|
||||||
|
Type = result
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we haven't forgotten any misses.
|
||||||
|
while (misses.Count > 0)
|
||||||
|
insertMiss();
|
||||||
|
|
||||||
|
var bonusHits = score.Statistics
|
||||||
|
.Where(kvp => kvp.Key.IsBonus())
|
||||||
|
.SelectMany(kvp => Enumerable.Repeat(kvp.Key, kvp.Value));
|
||||||
|
|
||||||
|
foreach (var result in bonusHits)
|
||||||
|
processor.ApplyResult(new JudgementResult(null!, new FakeJudgement(result)) { Type = result });
|
||||||
|
|
||||||
|
// Not true for all scores for whatever reason. Oh well.
|
||||||
|
// Debug.Assert(processor.HighestCombo.Value == score.MaxCombo);
|
||||||
|
|
||||||
|
return processor.TotalScore.Value;
|
||||||
|
|
||||||
|
void insertMiss()
|
||||||
|
{
|
||||||
|
if (misses.Count > 0)
|
||||||
|
{
|
||||||
|
processor.ApplyResult(new JudgementResult(null!, maximumJudgements[maxJudgementIndex++])
|
||||||
|
{
|
||||||
|
Type = misses.Dequeue(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// We ran out of misses. But we can't let max combo increase beyond the known value,
|
||||||
|
// so let's forge a miss.
|
||||||
|
processor.ApplyResult(new JudgementResult(null!, new FakeJudgement(getMaxJudgementFor(HitResult.Miss, maxRulesetJudgement)))
|
||||||
|
{
|
||||||
|
Type = HitResult.Miss,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HitResult getMaxJudgementFor(HitResult hitResult, HitResult max)
|
||||||
|
{
|
||||||
|
switch (hitResult)
|
||||||
|
{
|
||||||
|
case HitResult.Miss:
|
||||||
|
case HitResult.Meh:
|
||||||
|
case HitResult.Ok:
|
||||||
|
case HitResult.Good:
|
||||||
|
case HitResult.Great:
|
||||||
|
case HitResult.Perfect:
|
||||||
|
return max;
|
||||||
|
|
||||||
|
case HitResult.SmallTickMiss:
|
||||||
|
case HitResult.SmallTickHit:
|
||||||
|
return HitResult.SmallTickHit;
|
||||||
|
|
||||||
|
case HitResult.LargeTickMiss:
|
||||||
|
case HitResult.LargeTickHit:
|
||||||
|
return HitResult.LargeTickHit;
|
||||||
|
}
|
||||||
|
|
||||||
|
return HitResult.IgnoreHit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long GetOldStandardised(ScoreInfo score)
|
||||||
|
{
|
||||||
|
double accuracyScore =
|
||||||
|
(double)score.Statistics.Where(kvp => kvp.Key.AffectsAccuracy()).Sum(kvp => Judgement.ToNumericResult(kvp.Key) * kvp.Value)
|
||||||
|
/ score.MaximumStatistics.Where(kvp => kvp.Key.AffectsAccuracy()).Sum(kvp => Judgement.ToNumericResult(kvp.Key) * kvp.Value);
|
||||||
|
double comboScore = (double)score.MaxCombo / score.MaximumStatistics.Where(kvp => kvp.Key.AffectsCombo()).Sum(kvp => kvp.Value);
|
||||||
|
double bonusScore = score.Statistics.Where(kvp => kvp.Key.IsBonus()).Sum(kvp => Judgement.ToNumericResult(kvp.Key) * kvp.Value);
|
||||||
|
|
||||||
|
double accuracyPortion = 0.3;
|
||||||
|
|
||||||
|
switch (score.RulesetID)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
accuracyPortion = 0.75;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
accuracyPortion = 0.99;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
double modMultiplier = 1;
|
||||||
|
|
||||||
|
foreach (var mod in score.Mods)
|
||||||
|
modMultiplier *= mod.ScoreMultiplier;
|
||||||
|
|
||||||
|
return (long)((1000000 * (accuracyPortion * accuracyScore + (1 - accuracyPortion) * comboScore) + bonusScore) * modMultiplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FakeHit : HitObject
|
||||||
|
{
|
||||||
|
private readonly Judgement judgement;
|
||||||
|
|
||||||
|
public override Judgement CreateJudgement() => judgement;
|
||||||
|
|
||||||
|
public FakeHit(Judgement judgement)
|
||||||
|
{
|
||||||
|
this.judgement = judgement;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FakeJudgement : Judgement
|
||||||
|
{
|
||||||
|
public override HitResult MaxResult { get; }
|
||||||
|
|
||||||
|
public FakeJudgement(HitResult maxResult)
|
||||||
|
{
|
||||||
|
MaxResult = maxResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,10 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using JetBrains.Annotations;
|
|
||||||
using osu.Game.Online.API.Requests;
|
using osu.Game.Online.API.Requests;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
|
||||||
@ -21,8 +18,7 @@ namespace osu.Game.Database
|
|||||||
/// <param name="userId">The user to lookup.</param>
|
/// <param name="userId">The user to lookup.</param>
|
||||||
/// <param name="token">An optional cancellation token.</param>
|
/// <param name="token">An optional cancellation token.</param>
|
||||||
/// <returns>The populated user, or null if the user does not exist or the request could not be satisfied.</returns>
|
/// <returns>The populated user, or null if the user does not exist or the request could not be satisfied.</returns>
|
||||||
[ItemCanBeNull]
|
public Task<APIUser?> GetUserAsync(int userId, CancellationToken token = default) => LookupAsync(userId, token);
|
||||||
public Task<APIUser> GetUserAsync(int userId, CancellationToken token = default) => LookupAsync(userId, token);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Perform an API lookup on the specified users, populating a <see cref="APIUser"/> model.
|
/// Perform an API lookup on the specified users, populating a <see cref="APIUser"/> model.
|
||||||
@ -30,10 +26,10 @@ namespace osu.Game.Database
|
|||||||
/// <param name="userIds">The users to lookup.</param>
|
/// <param name="userIds">The users to lookup.</param>
|
||||||
/// <param name="token">An optional cancellation token.</param>
|
/// <param name="token">An optional cancellation token.</param>
|
||||||
/// <returns>The populated users. May include null results for failed retrievals.</returns>
|
/// <returns>The populated users. May include null results for failed retrievals.</returns>
|
||||||
public Task<APIUser[]> GetUsersAsync(int[] userIds, CancellationToken token = default) => LookupAsync(userIds, token);
|
public Task<APIUser?[]> GetUsersAsync(int[] userIds, CancellationToken token = default) => LookupAsync(userIds, token);
|
||||||
|
|
||||||
protected override GetUsersRequest CreateRequest(IEnumerable<int> ids) => new GetUsersRequest(ids.ToArray());
|
protected override GetUsersRequest CreateRequest(IEnumerable<int> ids) => new GetUsersRequest(ids.ToArray());
|
||||||
|
|
||||||
protected override IEnumerable<APIUser> RetrieveResults(GetUsersRequest request) => request.Response?.Users;
|
protected override IEnumerable<APIUser>? RetrieveResults(GetUsersRequest request) => request.Response?.Users;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Platform;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Extensions
|
namespace osu.Game.Extensions
|
||||||
@ -43,5 +44,20 @@ namespace osu.Game.Extensions
|
|||||||
/// <returns>The delta vector in Parent's coordinates.</returns>
|
/// <returns>The delta vector in Parent's coordinates.</returns>
|
||||||
public static Vector2 ScreenSpaceDeltaToParentSpace(this Drawable drawable, Vector2 delta) =>
|
public static Vector2 ScreenSpaceDeltaToParentSpace(this Drawable drawable, Vector2 delta) =>
|
||||||
drawable.Parent.ToLocalSpace(drawable.Parent.ToScreenSpace(Vector2.Zero) + 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.
|
/// This is required as enum member names are not allowed to contain hyphens.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public static string ToCultureCode(this Language language)
|
public static string ToCultureCode(this Language language)
|
||||||
=> language.ToString().Replace("_", "-");
|
{
|
||||||
|
if (language == Language.zh_hant)
|
||||||
|
return @"zh-tw";
|
||||||
|
|
||||||
|
return language.ToString().Replace("_", "-");
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Attempts to parse the supplied <paramref name="cultureCode"/> to a <see cref="Language"/> value.
|
/// 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>
|
/// <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>
|
/// <returns>Whether the parsing succeeded.</returns>
|
||||||
public static bool TryParseCultureCode(string cultureCode, out Language language)
|
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>
|
/// <summary>
|
||||||
/// Parses the <see cref="Language"/> that is specified in <paramref name="frameworkLocale"/>,
|
/// Parses the <see cref="Language"/> that is specified in <paramref name="frameworkLocale"/>,
|
||||||
|
@ -24,7 +24,7 @@ namespace osu.Game.Graphics.Backgrounds
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(LargeTextureStore textures)
|
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)
|
public override bool Equals(Background other)
|
||||||
|
@ -1,17 +1,16 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using JetBrains.Annotations;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Layout;
|
using osu.Framework.Layout;
|
||||||
|
using osu.Framework.Logging;
|
||||||
|
using osu.Framework.Threading;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
|
|
||||||
namespace osu.Game.Graphics.Containers
|
namespace osu.Game.Graphics.Containers
|
||||||
@ -23,11 +22,35 @@ namespace osu.Game.Graphics.Containers
|
|||||||
public partial class SectionsContainer<T> : Container<T>
|
public partial class SectionsContainer<T> : Container<T>
|
||||||
where T : Drawable
|
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;
|
get => expandableHeader;
|
||||||
set
|
set
|
||||||
@ -42,11 +65,12 @@ namespace osu.Game.Graphics.Containers
|
|||||||
if (value == null) return;
|
if (value == null) return;
|
||||||
|
|
||||||
AddInternal(expandableHeader);
|
AddInternal(expandableHeader);
|
||||||
|
|
||||||
lastKnownScroll = null;
|
lastKnownScroll = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Drawable FixedHeader
|
public Drawable? FixedHeader
|
||||||
{
|
{
|
||||||
get => fixedHeader;
|
get => fixedHeader;
|
||||||
set
|
set
|
||||||
@ -63,7 +87,7 @@ namespace osu.Game.Graphics.Containers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Drawable Footer
|
public Drawable? Footer
|
||||||
{
|
{
|
||||||
get => footer;
|
get => footer;
|
||||||
set
|
set
|
||||||
@ -75,16 +99,17 @@ namespace osu.Game.Graphics.Containers
|
|||||||
|
|
||||||
footer = value;
|
footer = value;
|
||||||
|
|
||||||
if (value == null) return;
|
if (footer == null) return;
|
||||||
|
|
||||||
footer.Anchor |= Anchor.y2;
|
footer.Anchor |= Anchor.y2;
|
||||||
footer.Origin |= Anchor.y2;
|
footer.Origin |= Anchor.y2;
|
||||||
|
|
||||||
scrollContainer.Add(footer);
|
scrollContainer.Add(footer);
|
||||||
lastKnownScroll = null;
|
lastKnownScroll = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Drawable HeaderBackground
|
public Drawable? HeaderBackground
|
||||||
{
|
{
|
||||||
get => headerBackground;
|
get => headerBackground;
|
||||||
set
|
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()
|
public SectionsContainer()
|
||||||
{
|
{
|
||||||
AddRangeInternal(new Drawable[]
|
AddRangeInternal(new Drawable[]
|
||||||
@ -150,31 +158,63 @@ namespace osu.Game.Graphics.Containers
|
|||||||
footerHeight = null;
|
footerHeight = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ScheduledDelegate? scrollToTargetDelegate;
|
||||||
|
|
||||||
public void ScrollTo(Drawable target)
|
public void ScrollTo(Drawable target)
|
||||||
{
|
{
|
||||||
|
Logger.Log($"Scrolling to {target}..");
|
||||||
|
|
||||||
lastKnownScroll = null;
|
lastKnownScroll = null;
|
||||||
|
|
||||||
// implementation similar to ScrollIntoView but a bit more nuanced.
|
float scrollTarget = getScrollTargetForDrawable(target);
|
||||||
float top = scrollContainer.GetChildPosInContent(target);
|
|
||||||
|
|
||||||
float bottomScrollExtent = scrollContainer.ScrollableExtent;
|
if (scrollTarget > scrollContainer.ScrollableExtent)
|
||||||
float scrollTarget = top - scrollContainer.DisplayableContent * scroll_y_centre;
|
|
||||||
|
|
||||||
if (scrollTarget > bottomScrollExtent)
|
|
||||||
scrollContainer.ScrollToEnd();
|
scrollContainer.ScrollToEnd();
|
||||||
else
|
else
|
||||||
scrollContainer.ScrollTo(scrollTarget);
|
scrollContainer.ScrollTo(scrollTarget);
|
||||||
|
|
||||||
if (target is T section)
|
if (target is T section)
|
||||||
lastClickedSection = 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);
|
public void ScrollToTop() => scrollContainer.ScrollTo(0);
|
||||||
|
|
||||||
[NotNull]
|
|
||||||
protected virtual UserTrackingScrollContainer CreateScrollContainer() => new UserTrackingScrollContainer();
|
protected virtual UserTrackingScrollContainer CreateScrollContainer() => new UserTrackingScrollContainer();
|
||||||
|
|
||||||
[NotNull]
|
|
||||||
protected virtual FlowContainer<T> CreateScrollContentContainer() =>
|
protected virtual FlowContainer<T> CreateScrollContentContainer() =>
|
||||||
new FillFlowContainer<T>
|
new FillFlowContainer<T>
|
||||||
{
|
{
|
||||||
|
@ -19,6 +19,7 @@ using osu.Framework.Platform;
|
|||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Input.Bindings;
|
using osu.Game.Input.Bindings;
|
||||||
|
using osu.Game.Online.Multiplayer;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.Notifications;
|
using osu.Game.Overlays.Notifications;
|
||||||
using SixLabors.ImageSharp;
|
using SixLabors.ImageSharp;
|
||||||
@ -69,7 +70,7 @@ namespace osu.Game.Graphics
|
|||||||
{
|
{
|
||||||
case GlobalAction.TakeScreenshot:
|
case GlobalAction.TakeScreenshot:
|
||||||
shutter.Play();
|
shutter.Play();
|
||||||
TakeScreenshotAsync();
|
TakeScreenshotAsync().FireAndForget();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,70 +87,75 @@ namespace osu.Game.Graphics
|
|||||||
{
|
{
|
||||||
Interlocked.Increment(ref screenShotTasks);
|
Interlocked.Increment(ref screenShotTasks);
|
||||||
|
|
||||||
if (!captureMenuCursor.Value)
|
try
|
||||||
{
|
{
|
||||||
cursorVisibility.Value = false;
|
if (!captureMenuCursor.Value)
|
||||||
|
|
||||||
// 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))
|
|
||||||
{
|
{
|
||||||
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)
|
ScheduledDelegate waitDelegate = host.DrawThread.Scheduler.AddDelayed(() =>
|
||||||
// ReSharper disable once AccessToDisposedClosure
|
{
|
||||||
framesWaitedEvent.Set();
|
if (framesWaited++ >= frames_to_wait)
|
||||||
}, 10, true);
|
// ReSharper disable once AccessToDisposedClosure
|
||||||
|
framesWaitedEvent.Set();
|
||||||
|
}, 10, true);
|
||||||
|
|
||||||
if (!framesWaitedEvent.Wait(1000))
|
if (!framesWaitedEvent.Wait(1000))
|
||||||
throw new TimeoutException("Screenshot data did not arrive in a timely fashion");
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
using (var image = await host.TakeScreenshotAsync().ConfigureAwait(false))
|
|
||||||
{
|
{
|
||||||
if (Interlocked.Decrement(ref screenShotTasks) == 0 && cursorVisibility.Value == false)
|
if (Interlocked.Decrement(ref screenShotTasks) == 0)
|
||||||
cursorVisibility.Value = true;
|
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;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -3,12 +3,19 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
using System;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
|
||||||
namespace osu.Game.Graphics.Sprites
|
namespace osu.Game.Graphics.Sprites
|
||||||
{
|
{
|
||||||
public partial class OsuSpriteText : SpriteText
|
public partial class OsuSpriteText : SpriteText
|
||||||
{
|
{
|
||||||
|
[Obsolete("Use TruncatingSpriteText instead.")]
|
||||||
|
public new bool Truncate
|
||||||
|
{
|
||||||
|
set => throw new InvalidOperationException($"Use {nameof(TruncatingSpriteText)} instead.");
|
||||||
|
}
|
||||||
|
|
||||||
public OsuSpriteText()
|
public OsuSpriteText()
|
||||||
{
|
{
|
||||||
Shadow = true;
|
Shadow = true;
|
||||||
|
29
osu.Game/Graphics/Sprites/TruncatingSpriteText.cs
Normal file
29
osu.Game/Graphics/Sprites/TruncatingSpriteText.cs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// 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.Graphics.Cursor;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
|
||||||
|
namespace osu.Game.Graphics.Sprites
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A derived version of <see cref="OsuSpriteText"/> which automatically shows non-truncated text in tooltip when required.
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class TruncatingSpriteText : OsuSpriteText, IHasTooltip
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Whether a tooltip should be shown with non-truncated text on hover.
|
||||||
|
/// </summary>
|
||||||
|
public bool ShowTooltip { get; init; } = true;
|
||||||
|
|
||||||
|
public LocalisableString TooltipText => Text;
|
||||||
|
|
||||||
|
public override bool HandlePositionalInput => IsTruncated && ShowTooltip;
|
||||||
|
|
||||||
|
public TruncatingSpriteText()
|
||||||
|
{
|
||||||
|
((SpriteText)this).Truncate = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace osu.Game.Graphics.UserInterface
|
namespace osu.Game.Graphics.UserInterface
|
||||||
{
|
{
|
||||||
public enum MenuItemType
|
public enum MenuItemType
|
||||||
|
@ -335,12 +335,11 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
{
|
{
|
||||||
new Drawable[]
|
new Drawable[]
|
||||||
{
|
{
|
||||||
Text = new OsuSpriteText
|
Text = new TruncatingSpriteText
|
||||||
{
|
{
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Truncate = true,
|
|
||||||
},
|
},
|
||||||
Icon = new SpriteIcon
|
Icon = new SpriteIcon
|
||||||
{
|
{
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace osu.Game.Graphics.UserInterface
|
namespace osu.Game.Graphics.UserInterface
|
||||||
{
|
{
|
||||||
public enum SelectionState
|
public enum SelectionState
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace osu.Game.Graphics.UserInterface
|
namespace osu.Game.Graphics.UserInterface
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <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.J }, GlobalAction.EditorFlipVertically),
|
||||||
new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.MouseWheelDown }, GlobalAction.EditorDecreaseDistanceSpacing),
|
new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.MouseWheelDown }, GlobalAction.EditorDecreaseDistanceSpacing),
|
||||||
new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.MouseWheelUp }, GlobalAction.EditorIncreaseDistanceSpacing),
|
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[]
|
public IEnumerable<KeyBinding> InGameKeyBindings => new[]
|
||||||
@ -355,6 +359,12 @@ namespace osu.Game.Input.Bindings
|
|||||||
ToggleProfile,
|
ToggleProfile,
|
||||||
|
|
||||||
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorCloneSelection))]
|
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorCloneSelection))]
|
||||||
EditorCloneSelection
|
EditorCloneSelection,
|
||||||
|
|
||||||
|
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorCyclePreviousBeatSnapDivisor))]
|
||||||
|
EditorCyclePreviousBeatSnapDivisor,
|
||||||
|
|
||||||
|
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorCycleNextBeatSnapDivisor))]
|
||||||
|
EditorCycleNextBeatSnapDivisor,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user