mirror of
https://github.com/ppy/osu.git
synced 2025-01-12 19:03:08 +08:00
Merge branch 'master' into freemod_mapinfo_fix
This commit is contained in:
commit
618819ba9f
20
.github/workflows/ci.yml
vendored
20
.github/workflows/ci.yml
vendored
@ -13,10 +13,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install .NET 8.0.x
|
||||
uses: actions/setup-dotnet@v3
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: "8.0.x"
|
||||
|
||||
@ -27,7 +27,7 @@ jobs:
|
||||
run: dotnet restore osu.Desktop.slnf
|
||||
|
||||
- name: Restore inspectcode cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ github.workspace }}/inspectcode
|
||||
key: inspectcode-${{ hashFiles('.config/dotnet-tools.json', '.github/workflows/ci.yml', 'osu.sln*', 'osu*.slnf', '.editorconfig', '.globalconfig', 'CodeAnalysis/*', '**/*.csproj', '**/*.props') }}
|
||||
@ -70,10 +70,10 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install .NET 8.0.x
|
||||
uses: actions/setup-dotnet@v3
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: "8.0.x"
|
||||
|
||||
@ -99,16 +99,16 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup JDK 11
|
||||
uses: actions/setup-java@v3
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: microsoft
|
||||
java-version: 11
|
||||
|
||||
- name: Install .NET 8.0.x
|
||||
uses: actions/setup-dotnet@v3
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: "8.0.x"
|
||||
|
||||
@ -126,10 +126,10 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install .NET 8.0.x
|
||||
uses: actions/setup-dotnet@v3
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: "8.0.x"
|
||||
|
||||
|
2
.github/workflows/diffcalc.yml
vendored
2
.github/workflows/diffcalc.yml
vendored
@ -140,7 +140,7 @@ jobs:
|
||||
GOOGLE_CREDS_FILE: ${{ steps.set-outputs.outputs.GOOGLE_CREDS_FILE }}
|
||||
steps:
|
||||
- name: Checkout diffcalc-sheet-generator
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: ${{ env.EXECUTION_ID }}
|
||||
repository: 'smoogipoo/diffcalc-sheet-generator'
|
||||
|
2
.github/workflows/report-nunit.yml
vendored
2
.github/workflows/report-nunit.yml
vendored
@ -28,7 +28,7 @@ jobs:
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Annotate CI run with test results
|
||||
uses: dorny/test-reporter@v1.6.0
|
||||
uses: dorny/test-reporter@v1.8.0
|
||||
with:
|
||||
artifact: osu-test-results-${{matrix.os.prettyname}}-${{matrix.threadingMode}}
|
||||
name: Test Results (${{matrix.os.prettyname}}, ${{matrix.threadingMode}})
|
||||
|
2
.github/workflows/sentry-release.yml
vendored
2
.github/workflows/sentry-release.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
10
.github/workflows/update-web-mod-definitions.yml
vendored
10
.github/workflows/update-web-mod-definitions.yml
vendored
@ -13,23 +13,23 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install .NET 8.0.x
|
||||
uses: actions/setup-dotnet@v3
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: "8.0.x"
|
||||
|
||||
- name: Checkout ppy/osu
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: osu
|
||||
|
||||
- name: Checkout ppy/osu-tools
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: ppy/osu-tools
|
||||
path: osu-tools
|
||||
|
||||
- name: Checkout ppy/osu-web
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: ppy/osu-web
|
||||
path: osu-web
|
||||
@ -43,7 +43,7 @@ jobs:
|
||||
working-directory: ./osu-tools
|
||||
|
||||
- name: Create pull request with changes
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
title: Update mod definitions
|
||||
body: "This PR has been auto-generated to update the mod definitions to match ppy/osu@${{ github.ref_name }}."
|
||||
|
@ -10,7 +10,7 @@
|
||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.215.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.221.0" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||
|
48
osu.Game.Benchmarks/BenchmarkStringComparison.cs
Normal file
48
osu.Game.Benchmarks/BenchmarkStringComparison.cs
Normal file
@ -0,0 +1,48 @@
|
||||
// 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 BenchmarkDotNet.Attributes;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Benchmarks
|
||||
{
|
||||
public class BenchmarkStringComparison
|
||||
{
|
||||
private string[] strings = null!;
|
||||
|
||||
[GlobalSetup]
|
||||
public void GlobalSetUp()
|
||||
{
|
||||
strings = new string[10000];
|
||||
|
||||
for (int i = 0; i < strings.Length; ++i)
|
||||
strings[i] = Guid.NewGuid().ToString();
|
||||
|
||||
for (int i = 0; i < strings.Length; ++i)
|
||||
{
|
||||
if (i % 2 == 0)
|
||||
strings[i] = strings[i].ToUpperInvariant();
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void OrdinalIgnoreCase() => compare(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
[Benchmark]
|
||||
public void OrdinalSortByCase() => compare(OrdinalSortByCaseStringComparer.DEFAULT);
|
||||
|
||||
[Benchmark]
|
||||
public void InvariantCulture() => compare(StringComparer.InvariantCulture);
|
||||
|
||||
private void compare(IComparer<string> comparer)
|
||||
{
|
||||
for (int i = 0; i < strings.Length; ++i)
|
||||
{
|
||||
for (int j = i + 1; j < strings.Length; ++j)
|
||||
_ = comparer.Compare(strings[i], strings[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -293,7 +293,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
|
||||
private JudgementResult createResult(CatchHitObject hitObject)
|
||||
{
|
||||
return new CatchJudgementResult(hitObject, hitObject.CreateJudgement())
|
||||
return new CatchJudgementResult(hitObject, hitObject.Judgement)
|
||||
{
|
||||
Type = catcher.CanCatch(hitObject) ? HitResult.Great : HitResult.Miss
|
||||
};
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
@ -17,9 +18,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||
[Resolved]
|
||||
private Playfield playfield { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private IScrollingInfo scrollingInfo { get; set; } = null!;
|
||||
|
||||
protected ScrollingHitObjectContainer HitObjectContainer => ((ManiaPlayfield)playfield).GetColumn(HitObject.Column).HitObjectContainer;
|
||||
|
||||
protected ManiaSelectionBlueprint(T hitObject)
|
||||
@ -28,14 +26,31 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||
RelativeSizeAxes = Axes.None;
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
private readonly IBindable<ScrollingDirection> directionBindable = new Bindable<ScrollingDirection>();
|
||||
|
||||
var anchor = scrollingInfo.Direction.Value == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(IScrollingInfo scrollingInfo)
|
||||
{
|
||||
directionBindable.BindTo(scrollingInfo.Direction);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
directionBindable.BindValueChanged(onDirectionChanged, true);
|
||||
}
|
||||
|
||||
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
|
||||
{
|
||||
var anchor = direction.NewValue == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
|
||||
Anchor = Origin = anchor;
|
||||
foreach (var child in InternalChildren)
|
||||
child.Anchor = child.Origin = anchor;
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
Position = Parent!.ToLocalSpace(HitObjectContainer.ScreenSpacePositionAtTime(HitObject.StartTime)) - AnchorPosition;
|
||||
Width = HitObjectContainer.DrawWidth;
|
||||
|
@ -7,7 +7,6 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
@ -149,7 +148,18 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
/// <summary>
|
||||
/// Retrieves the total amount of columns across all stages in this playfield.
|
||||
/// </summary>
|
||||
public int TotalColumns => stages.Sum(s => s.Columns.Length);
|
||||
public int TotalColumns
|
||||
{
|
||||
get
|
||||
{
|
||||
int sum = 0;
|
||||
|
||||
foreach (var stage in stages)
|
||||
sum += stage.Columns.Length;
|
||||
|
||||
return sum;
|
||||
}
|
||||
}
|
||||
|
||||
private Stage getStageByColumn(int column)
|
||||
{
|
||||
|
@ -2,7 +2,6 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
|
@ -134,7 +134,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
|
||||
// multipled by 2 to nullify the score multiplier. (autoplay mod selected)
|
||||
long totalScore = scoreProcessor.TotalScore.Value * 2;
|
||||
return totalScore == (int)(drawableSpinner.Result.TotalRotation / 360) * scoreProcessor.GetBaseScoreForResult(new SpinnerTick().CreateJudgement().MaxResult);
|
||||
return totalScore == (int)(drawableSpinner.Result.TotalRotation / 360) * scoreProcessor.GetBaseScoreForResult(new SpinnerTick().Judgement.MaxResult);
|
||||
});
|
||||
|
||||
addSeekStep(0);
|
||||
|
@ -232,7 +232,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
|
||||
IList<HitObject> nestedObjects = slider.NestedHitObjects;
|
||||
|
||||
SliderTick? lastRealTick = slider.NestedHitObjects.OfType<SliderTick>().LastOrDefault();
|
||||
SliderTick? lastRealTick = null;
|
||||
|
||||
foreach (var hitobject in slider.NestedHitObjects)
|
||||
{
|
||||
if (hitobject is SliderTick tick)
|
||||
lastRealTick = tick;
|
||||
}
|
||||
|
||||
if (lastRealTick?.StartTime > trackingEndTime)
|
||||
{
|
||||
|
@ -78,9 +78,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components
|
||||
|
||||
Scale = new Vector2(hitObject.Scale);
|
||||
|
||||
if (hitObject is IHasComboInformation combo)
|
||||
ring.BorderColour = combo.GetComboColour(skin);
|
||||
|
||||
double editorTime = editorClock.CurrentTime;
|
||||
double hitObjectTime = hitObject.StartTime;
|
||||
bool hasReachedObject = editorTime >= hitObjectTime;
|
||||
@ -92,6 +89,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components
|
||||
|
||||
ring.Scale = new Vector2(1 + 0.1f * ringScale);
|
||||
content.Alpha = 0.9f * (1 - alpha);
|
||||
|
||||
// TODO: should only update colour on skin/combo/object change.
|
||||
if (hitObject is IHasComboInformation combo && content.Alpha > 0)
|
||||
ring.BorderColour = combo.GetComboColour(skin);
|
||||
}
|
||||
else
|
||||
content.Alpha = 0;
|
||||
|
@ -416,8 +416,22 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
DrawableObject.SliderBody?.ToScreenSpace(DrawableObject.SliderBody.PathEndOffset) ?? BodyPiece.ToScreenSpace(BodyPiece.PathEndLocation)
|
||||
};
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) =>
|
||||
BodyPiece.ReceivePositionalInputAt(screenSpacePos) || ControlPointVisualiser?.Pieces.Any(p => p.ReceivePositionalInputAt(screenSpacePos)) == true;
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos)
|
||||
{
|
||||
if (BodyPiece.ReceivePositionalInputAt(screenSpacePos))
|
||||
return true;
|
||||
|
||||
if (ControlPointVisualiser == null)
|
||||
return false;
|
||||
|
||||
foreach (var p in ControlPointVisualiser.Pieces)
|
||||
{
|
||||
if (p.ReceivePositionalInputAt(screenSpacePos))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected virtual SliderCircleOverlay CreateCircleOverlay(Slider slider, SliderPosition position) => new SliderCircleOverlay(slider, position);
|
||||
}
|
||||
|
@ -2,7 +2,6 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
@ -149,8 +148,11 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
{
|
||||
StackHeightBindable.BindValueChanged(height =>
|
||||
{
|
||||
foreach (var nested in NestedHitObjects.OfType<OsuHitObject>())
|
||||
nested.StackHeight = height.NewValue;
|
||||
foreach (var nested in NestedHitObjects)
|
||||
{
|
||||
if (nested is OsuHitObject osuHitObject)
|
||||
osuHitObject.StackHeight = height.NewValue;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -252,18 +252,25 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
|
||||
protected void UpdateNestedSamples()
|
||||
{
|
||||
var firstSample = Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL)
|
||||
?? Samples.FirstOrDefault(); // TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933)
|
||||
var sampleList = new List<HitSampleInfo>();
|
||||
// TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933)
|
||||
HitSampleInfo tickSample = (Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL) ?? Samples.FirstOrDefault())?.With("slidertick");
|
||||
|
||||
if (firstSample != null)
|
||||
sampleList.Add(firstSample.With("slidertick"));
|
||||
foreach (var nested in NestedHitObjects)
|
||||
{
|
||||
switch (nested)
|
||||
{
|
||||
case SliderTick tick:
|
||||
tick.SamplesBindable.Clear();
|
||||
|
||||
foreach (var tick in NestedHitObjects.OfType<SliderTick>())
|
||||
tick.Samples = sampleList;
|
||||
if (tickSample != null)
|
||||
tick.SamplesBindable.Add(tickSample);
|
||||
break;
|
||||
|
||||
foreach (var repeat in NestedHitObjects.OfType<SliderRepeat>())
|
||||
case SliderRepeat repeat:
|
||||
repeat.Samples = this.GetNodeSamples(repeat.RepeatIndex + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (HeadCircle != null)
|
||||
HeadCircle.Samples = this.GetNodeSamples(0);
|
||||
|
@ -1,13 +1,13 @@
|
||||
// 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.ComponentModel;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Lists;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu
|
||||
{
|
||||
public partial class OsuInputManager : RulesetInputManager<OsuAction>
|
||||
{
|
||||
public IEnumerable<OsuAction> PressedActions => KeyBindingContainer.PressedActions;
|
||||
public SlimReadOnlyListWrapper<OsuAction> PressedActions => KeyBindingContainer.PressedActions;
|
||||
|
||||
/// <summary>
|
||||
/// Whether gameplay input buttons should be allowed.
|
||||
|
@ -25,8 +25,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
{
|
||||
Texture = textures.Get(@"Gameplay/osu/approachcircle").WithMaximumSize(OsuHitObject.OBJECT_DIMENSIONS * 2);
|
||||
|
||||
// account for the sprite being used for the default approach circle being taken from stable,
|
||||
// when hitcircles have 5px padding on each size. this should be removed if we update the sprite.
|
||||
// In triangles and argon skins, we expanded hitcircles to take up the full 128 px which are clickable,
|
||||
// but still use the old approach circle sprite. To make it feel correct (ie. disappear as it collides
|
||||
// with the hitcircle, *not when it overlaps the border*) we need to expand it slightly.
|
||||
Scale = new Vector2(128 / 118f);
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,6 @@ using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
@ -26,10 +25,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
var texture = skin.GetTexture(@"approachcircle");
|
||||
Debug.Assert(texture != null);
|
||||
Texture = texture.WithMaximumSize(OsuHitObject.OBJECT_DIMENSIONS * 2);
|
||||
|
||||
// account for the sprite being used for the default approach circle being taken from stable,
|
||||
// when hitcircles have 5px padding on each size. this should be removed if we update the sprite.
|
||||
Scale = new Vector2(128 / 118f);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
|
@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
|
||||
// Note that the scale adjust here is 2 instead of DrawableSliderBall.FOLLOW_AREA to match legacy behaviour.
|
||||
// This means the actual tracking area for gameplay purposes is larger than the sprite (but skins may be accounting for this).
|
||||
this.ScaleTo(0.5f).ScaleTo(2f, Math.Min(180f, remainingTime), Easing.Out)
|
||||
this.ScaleTo(1f).ScaleTo(2f, Math.Min(180f, remainingTime), Easing.Out)
|
||||
.FadeTo(0).FadeTo(1f, Math.Min(60f, remainingTime));
|
||||
}
|
||||
|
||||
|
@ -92,8 +92,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt))
|
||||
this.FadeOut();
|
||||
|
||||
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2))
|
||||
this.FadeInFromZero(spinner.TimeFadeIn / 2);
|
||||
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn))
|
||||
this.FadeInFromZero(spinner.TimeFadeIn);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
|
@ -126,11 +126,11 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
||||
|
||||
foreach (var nested in beatmap.HitObjects[0].NestedHitObjects)
|
||||
{
|
||||
var nestedJudgement = nested.CreateJudgement();
|
||||
var nestedJudgement = nested.Judgement;
|
||||
healthProcessor.ApplyResult(new JudgementResult(nested, nestedJudgement) { Type = nestedJudgement.MaxResult });
|
||||
}
|
||||
|
||||
var judgement = beatmap.HitObjects[0].CreateJudgement();
|
||||
var judgement = beatmap.HitObjects[0].Judgement;
|
||||
healthProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], judgement) { Type = judgement.MaxResult });
|
||||
|
||||
Assert.Multiple(() =>
|
||||
@ -159,11 +159,11 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
||||
|
||||
foreach (var nested in beatmap.HitObjects[0].NestedHitObjects)
|
||||
{
|
||||
var nestedJudgement = nested.CreateJudgement();
|
||||
var nestedJudgement = nested.Judgement;
|
||||
healthProcessor.ApplyResult(new JudgementResult(nested, nestedJudgement) { Type = nestedJudgement.MaxResult });
|
||||
}
|
||||
|
||||
var judgement = beatmap.HitObjects[0].CreateJudgement();
|
||||
var judgement = beatmap.HitObjects[0].Judgement;
|
||||
healthProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], judgement) { Type = judgement.MaxResult });
|
||||
|
||||
Assert.Multiple(() =>
|
||||
|
@ -175,7 +175,7 @@ namespace osu.Game.Tests.Gameplay
|
||||
var hitObject = new HitObject { StartTime = Time.Current };
|
||||
lifetimeEntry = new HitObjectLifetimeEntry(hitObject)
|
||||
{
|
||||
Result = new JudgementResult(hitObject, hitObject.CreateJudgement())
|
||||
Result = new JudgementResult(hitObject, hitObject.Judgement)
|
||||
{
|
||||
Type = HitResult.Great
|
||||
}
|
||||
|
@ -129,10 +129,10 @@ namespace osu.Game.Tests.Gameplay
|
||||
var scoreProcessor = new ScoreProcessor(new OsuRuleset());
|
||||
scoreProcessor.ApplyBeatmap(beatmap);
|
||||
|
||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], beatmap.HitObjects[0].CreateJudgement()) { Type = HitResult.Ok });
|
||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[1], beatmap.HitObjects[1].CreateJudgement()) { Type = HitResult.LargeTickHit });
|
||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[2], beatmap.HitObjects[2].CreateJudgement()) { Type = HitResult.SmallTickMiss });
|
||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[3], beatmap.HitObjects[3].CreateJudgement()) { Type = HitResult.SmallBonus });
|
||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], beatmap.HitObjects[0].Judgement) { Type = HitResult.Ok });
|
||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[1], beatmap.HitObjects[1].Judgement) { Type = HitResult.LargeTickHit });
|
||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[2], beatmap.HitObjects[2].Judgement) { Type = HitResult.SmallTickMiss });
|
||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[3], beatmap.HitObjects[3].Judgement) { Type = HitResult.SmallBonus });
|
||||
|
||||
var score = new ScoreInfo { Ruleset = new OsuRuleset().RulesetInfo };
|
||||
scoreProcessor.FailScore(score);
|
||||
@ -169,8 +169,8 @@ namespace osu.Game.Tests.Gameplay
|
||||
Assert.That(scoreProcessor.MinimumAccuracy.Value, Is.EqualTo(0));
|
||||
Assert.That(scoreProcessor.MaximumAccuracy.Value, Is.EqualTo(1));
|
||||
|
||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], beatmap.HitObjects[0].CreateJudgement()) { Type = HitResult.Ok });
|
||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[1], beatmap.HitObjects[1].CreateJudgement()) { Type = HitResult.Great });
|
||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], beatmap.HitObjects[0].Judgement) { Type = HitResult.Ok });
|
||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[1], beatmap.HitObjects[1].Judgement) { Type = HitResult.Great });
|
||||
|
||||
Assert.That(scoreProcessor.Accuracy.Value, Is.EqualTo((double)(100 + 300) / (2 * 300)).Within(Precision.DOUBLE_EPSILON));
|
||||
Assert.That(scoreProcessor.MinimumAccuracy.Value, Is.EqualTo((double)(100 + 300) / (4 * 300)).Within(Precision.DOUBLE_EPSILON));
|
||||
|
@ -112,7 +112,7 @@ namespace osu.Game.Tests.Rulesets.Scoring
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
var judgementResult = new JudgementResult(fourObjectBeatmap.HitObjects[i], fourObjectBeatmap.HitObjects[i].CreateJudgement())
|
||||
var judgementResult = new JudgementResult(fourObjectBeatmap.HitObjects[i], fourObjectBeatmap.HitObjects[i].Judgement)
|
||||
{
|
||||
Type = i == 2 ? minResult : hitResult
|
||||
};
|
||||
@ -141,7 +141,7 @@ namespace osu.Game.Tests.Rulesets.Scoring
|
||||
|
||||
for (int i = 0; i < object_count; ++i)
|
||||
{
|
||||
var judgementResult = new JudgementResult(largeBeatmap.HitObjects[i], largeBeatmap.HitObjects[i].CreateJudgement())
|
||||
var judgementResult = new JudgementResult(largeBeatmap.HitObjects[i], largeBeatmap.HitObjects[i].Judgement)
|
||||
{
|
||||
Type = HitResult.Great
|
||||
};
|
||||
@ -325,11 +325,11 @@ namespace osu.Game.Tests.Rulesets.Scoring
|
||||
scoreProcessor = new TestScoreProcessor();
|
||||
scoreProcessor.ApplyBeatmap(beatmap);
|
||||
|
||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], beatmap.HitObjects[0].CreateJudgement()) { Type = HitResult.Great });
|
||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], beatmap.HitObjects[0].Judgement) { Type = HitResult.Great });
|
||||
Assert.That(scoreProcessor.Combo.Value, Is.EqualTo(1));
|
||||
Assert.That(scoreProcessor.Accuracy.Value, Is.EqualTo(1));
|
||||
|
||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[1], beatmap.HitObjects[1].CreateJudgement()) { Type = HitResult.ComboBreak });
|
||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[1], beatmap.HitObjects[1].Judgement) { Type = HitResult.ComboBreak });
|
||||
Assert.That(scoreProcessor.Combo.Value, Is.EqualTo(0));
|
||||
Assert.That(scoreProcessor.Accuracy.Value, Is.EqualTo(1));
|
||||
}
|
||||
@ -350,7 +350,7 @@ namespace osu.Game.Tests.Rulesets.Scoring
|
||||
|
||||
for (int i = 0; i < beatmap.HitObjects.Count; i++)
|
||||
{
|
||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[i], beatmap.HitObjects[i].CreateJudgement())
|
||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[i], beatmap.HitObjects[i].Judgement)
|
||||
{
|
||||
Type = i == 0 ? HitResult.Miss : HitResult.Great
|
||||
});
|
||||
@ -441,10 +441,7 @@ namespace osu.Game.Tests.Rulesets.Scoring
|
||||
private readonly HitResult maxResult;
|
||||
private readonly HitResult? minResult;
|
||||
|
||||
public override Judgement CreateJudgement()
|
||||
{
|
||||
return new TestJudgement(maxResult, minResult);
|
||||
}
|
||||
public override Judgement CreateJudgement() => new TestJudgement(maxResult, minResult);
|
||||
|
||||
public TestHitObject(HitResult maxResult, HitResult? minResult = null)
|
||||
{
|
||||
|
60
osu.Game.Tests/Visual/Beatmaps/TestSceneDifficultyIcon.cs
Normal file
60
osu.Game.Tests/Visual/Beatmaps/TestSceneDifficultyIcon.cs
Normal file
@ -0,0 +1,60 @@
|
||||
// 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 NUnit.Framework;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Beatmaps
|
||||
{
|
||||
public partial class TestSceneDifficultyIcon : OsuTestScene
|
||||
{
|
||||
private FillFlowContainer<DifficultyIcon> fill = null!;
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Child = fill = new FillFlowContainer<DifficultyIcon>
|
||||
{
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Width = 300,
|
||||
Direction = FillDirection.Full,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
};
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateDifficultyIcon()
|
||||
{
|
||||
AddRepeatStep("create difficulty icon", () =>
|
||||
{
|
||||
var rulesetInfo = new OsuRuleset().RulesetInfo;
|
||||
var beatmapInfo = new TestBeatmap(rulesetInfo).BeatmapInfo;
|
||||
|
||||
beatmapInfo.Difficulty.ApproachRate = RNG.Next(0, 10);
|
||||
beatmapInfo.Difficulty.CircleSize = RNG.Next(0, 10);
|
||||
beatmapInfo.Difficulty.OverallDifficulty = RNG.Next(0, 10);
|
||||
beatmapInfo.Difficulty.DrainRate = RNG.Next(0, 10);
|
||||
beatmapInfo.StarRating = RNG.NextSingle(0, 10);
|
||||
beatmapInfo.BPM = RNG.Next(60, 300);
|
||||
|
||||
fill.Add(new DifficultyIcon(beatmapInfo, rulesetInfo)
|
||||
{
|
||||
Scale = new Vector2(2),
|
||||
});
|
||||
}, 10);
|
||||
|
||||
AddStep("no tooltip", () => fill.ForEach(icon => icon.TooltipType = DifficultyIconTooltipType.None));
|
||||
AddStep("basic tooltip", () => fill.ForEach(icon => icon.TooltipType = DifficultyIconTooltipType.StarRating));
|
||||
AddStep("extended tooltip", () => fill.ForEach(icon => icon.TooltipType = DifficultyIconTooltipType.Extended));
|
||||
}
|
||||
}
|
||||
}
|
@ -8,11 +8,13 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Login;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Users.Drawables;
|
||||
using osuTK.Input;
|
||||
|
||||
@ -25,6 +27,9 @@ namespace osu.Game.Tests.Visual.Menus
|
||||
|
||||
private LoginOverlay loginOverlay = null!;
|
||||
|
||||
[Resolved]
|
||||
private OsuConfigManager configManager { get; set; } = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
@ -156,5 +161,36 @@ namespace osu.Game.Tests.Visual.Menus
|
||||
});
|
||||
AddAssert("login overlay is hidden", () => loginOverlay.State.Value == Visibility.Hidden);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestUncheckingRememberUsernameClearsIt()
|
||||
{
|
||||
AddStep("logout", () => API.Logout());
|
||||
AddStep("set username", () => configManager.SetValue(OsuSetting.Username, "test_user"));
|
||||
AddStep("set remember password", () => configManager.SetValue(OsuSetting.SavePassword, true));
|
||||
AddStep("uncheck remember username", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(loginOverlay.ChildrenOfType<SettingsCheckbox>().First());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddAssert("remember username off", () => configManager.Get<bool>(OsuSetting.SaveUsername), () => Is.False);
|
||||
AddAssert("remember password off", () => configManager.Get<bool>(OsuSetting.SavePassword), () => Is.False);
|
||||
AddAssert("username cleared", () => configManager.Get<string>(OsuSetting.Username), () => Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestUncheckingRememberPasswordClearsToken()
|
||||
{
|
||||
AddStep("logout", () => API.Logout());
|
||||
AddStep("set token", () => configManager.SetValue(OsuSetting.Token, "test_token"));
|
||||
AddStep("set remember password", () => configManager.SetValue(OsuSetting.SavePassword, true));
|
||||
AddStep("uncheck remember token", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(loginOverlay.ChildrenOfType<SettingsCheckbox>().Last());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddAssert("remember password off", () => configManager.Get<bool>(OsuSetting.SavePassword), () => Is.False);
|
||||
AddAssert("token cleared", () => configManager.Get<string>(OsuSetting.Token), () => Is.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
@ -18,6 +19,7 @@ using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Collections;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API;
|
||||
@ -221,6 +223,67 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAttemptPlayBeatmapWrongHashFails()
|
||||
{
|
||||
Screens.Select.SongSelect songSelect = null;
|
||||
|
||||
AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).GetResultSafely());
|
||||
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||
AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded);
|
||||
|
||||
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
|
||||
|
||||
AddStep("change beatmap files", () =>
|
||||
{
|
||||
foreach (var file in Game.Beatmap.Value.BeatmapSetInfo.Files.Where(f => Path.GetExtension(f.Filename) == ".osu"))
|
||||
{
|
||||
using (var stream = Game.Storage.GetStream(Path.Combine("files", file.File.GetStoragePath()), FileAccess.ReadWrite))
|
||||
stream.WriteByte(0);
|
||||
}
|
||||
});
|
||||
|
||||
AddStep("invalidate cache", () =>
|
||||
{
|
||||
((IWorkingBeatmapCache)Game.BeatmapManager).Invalidate(Game.Beatmap.Value.BeatmapSetInfo);
|
||||
});
|
||||
|
||||
AddStep("select next difficulty", () => InputManager.Key(Key.Down));
|
||||
AddStep("press enter", () => InputManager.Key(Key.Enter));
|
||||
|
||||
AddUntilStep("wait for player loader", () => Game.ScreenStack.CurrentScreen is PlayerLoader);
|
||||
AddUntilStep("wait for song select", () => songSelect.IsCurrentScreen());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAttemptPlayBeatmapMissingFails()
|
||||
{
|
||||
Screens.Select.SongSelect songSelect = null;
|
||||
|
||||
AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).GetResultSafely());
|
||||
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||
AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded);
|
||||
|
||||
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
|
||||
|
||||
AddStep("delete beatmap files", () =>
|
||||
{
|
||||
foreach (var file in Game.Beatmap.Value.BeatmapSetInfo.Files.Where(f => Path.GetExtension(f.Filename) == ".osu"))
|
||||
Game.Storage.Delete(Path.Combine("files", file.File.GetStoragePath()));
|
||||
});
|
||||
|
||||
AddStep("invalidate cache", () =>
|
||||
{
|
||||
((IWorkingBeatmapCache)Game.BeatmapManager).Invalidate(Game.Beatmap.Value.BeatmapSetInfo);
|
||||
});
|
||||
|
||||
AddStep("select next difficulty", () => InputManager.Key(Key.Down));
|
||||
AddStep("press enter", () => InputManager.Key(Key.Enter));
|
||||
|
||||
AddUntilStep("wait for player loader", () => Game.ScreenStack.CurrentScreen is PlayerLoader);
|
||||
AddUntilStep("wait for song select", () => songSelect.IsCurrentScreen());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRetryCountIncrements()
|
||||
{
|
||||
|
@ -9,6 +9,7 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Threading;
|
||||
@ -301,6 +302,25 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
switchToGameplayScene();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRulesetInputDisabledWhenSkinEditorOpen()
|
||||
{
|
||||
advanceToSongSelect();
|
||||
openSkinEditor();
|
||||
|
||||
AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely());
|
||||
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
|
||||
|
||||
switchToGameplayScene();
|
||||
AddUntilStep("nested input disabled", () => ((Player)Game.ScreenStack.CurrentScreen).ChildrenOfType<PassThroughInputManager>().All(manager => !manager.UseParentInput));
|
||||
|
||||
toggleSkinEditor();
|
||||
AddUntilStep("nested input enabled", () => ((Player)Game.ScreenStack.CurrentScreen).ChildrenOfType<PassThroughInputManager>().Any(manager => manager.UseParentInput));
|
||||
|
||||
toggleSkinEditor();
|
||||
AddUntilStep("nested input disabled", () => ((Player)Game.ScreenStack.CurrentScreen).ChildrenOfType<PassThroughInputManager>().All(manager => !manager.UseParentInput));
|
||||
}
|
||||
|
||||
private void advanceToSongSelect()
|
||||
{
|
||||
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||
|
@ -81,16 +81,17 @@ namespace osu.Game.Tests.Visual.Online
|
||||
},
|
||||
|
||||
// Taken from https://github.com/ppy/osu/issues/13993#issuecomment-885994077
|
||||
new[] { "Problematic", @"My tablet doesn't work :( It's a Huion 420 and it's apparently incompatible with OpenTablet Driver. The warning I get is: ""DeviceInUseException: Device is currently in use by another kernel module. To fix this issue, please follow the instructions from https://github.com/OpenTabletDriver/OpenTabletDriver/wiki/Linux-FAQ#arg umentoutofrangeexception-value-0-15"" and it repeats 4 times on the notification before logging subsequent warnings. Checking the logs, it looks for other Huion tablets before sending the notification (e.g. ""2021-07-23 03:52:33 [verbose]: Detect: Searching for tablet 'Huion WH1409 V2' 20 2021-07-23 03:52:33 [error]: DeviceInUseException: Device is currently in use by another kernel module. To fix this issue, please follow the instructions from https://github.com/OpenTabletDriver/OpenTabletDriver/wiki/Linux-FAQ#arg umentoutofrangeexception-value-0-15"") I use an Arch based installation of Linux and the tablet runs perfectly with Digimend kernel driver, with area configuration, pen pressure, etc. On osu!lazer the cursor disappears until I set it to ""Borderless"" instead of ""Fullscreen"" and even after it shows up, it goes to the bottom left corner as soon as a map starts. I have honestly 0 idea of whats going on at this point.", },
|
||||
new[]
|
||||
{
|
||||
"Problematic", @"My tablet doesn't work :(
|
||||
It's a Huion 420 and it's apparently incompatible with OpenTablet Driver. The warning I get is: ""DeviceInUseException: Device is currently in use by another kernel module. To fix this issue, please follow the instructions from https://github.com/OpenTabletDriver/OpenTabletDriver/wiki/Linux-FAQ#arg umentoutofrangeexception-value-0-15"" and it repeats 4 times on the notification before logging subsequent warnings.
|
||||
Checking the logs, it looks for other Huion tablets before sending the notification (e.g.
|
||||
""2021-07-23 03:52:33 [verbose]: Detect: Searching for tablet 'Huion WH1409 V2'
|
||||
20 2021-07-23 03:52:33 [error]: DeviceInUseException: Device is currently in use by another kernel module. To fix this issue, please follow the instructions from https://github.com/OpenTabletDriver/OpenTabletDriver/wiki/Linux-FAQ#arg umentoutofrangeexception-value-0-15"")
|
||||
I use an Arch based installation of Linux and the tablet runs perfectly with Digimend kernel driver, with area configuration, pen pressure, etc. On osu!lazer the cursor disappears until I set it to ""Borderless"" instead of ""Fullscreen"" and even after it shows up, it goes to the bottom left corner as soon as a map starts.
|
||||
I have honestly 0 idea of whats going on at this point."
|
||||
}
|
||||
"Code Block", @"User not found! ;_;
|
||||
|
||||
There are a few possible reasons for this:
|
||||
|
||||
They may have changed their username.
|
||||
The account may be temporarily unavailable due to security or abuse issues.
|
||||
You may have made a typo!"
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -137,6 +137,11 @@ namespace osu.Game.Tests.Visual.Online
|
||||
@"top_ranks",
|
||||
@"medals"
|
||||
},
|
||||
RankHighest = new APIUser.UserRankHighest
|
||||
{
|
||||
Rank = 1,
|
||||
UpdatedAt = DateTimeOffset.Now,
|
||||
},
|
||||
Statistics = new UserStatistics
|
||||
{
|
||||
IsRanked = true,
|
||||
|
42
osu.Game.Tests/Visual/Ranking/TestSceneGradedCircles.cs
Normal file
42
osu.Game.Tests/Visual/Ranking/TestSceneGradedCircles.cs
Normal file
@ -0,0 +1,42 @@
|
||||
// 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;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Ranking.Expanded.Accuracy;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Ranking
|
||||
{
|
||||
public partial class TestSceneGradedCircles : OsuTestScene
|
||||
{
|
||||
private readonly GradedCircles ring;
|
||||
|
||||
public TestSceneGradedCircles()
|
||||
{
|
||||
ScoreProcessor scoreProcessor = new OsuRuleset().CreateScoreProcessor();
|
||||
double accuracyX = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.X);
|
||||
double accuracyS = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.S);
|
||||
|
||||
double accuracyA = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.A);
|
||||
double accuracyB = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.B);
|
||||
double accuracyC = scoreProcessor.AccuracyCutoffFromRank(ScoreRank.C);
|
||||
|
||||
Add(ring = new GradedCircles(accuracyC, accuracyB, accuracyA, accuracyS, accuracyX)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(400)
|
||||
});
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
AddSliderStep("Progress", 0.0, 1.0, 1.0, p => ring.Progress = p);
|
||||
}
|
||||
}
|
||||
}
|
@ -629,7 +629,8 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
var sets = new List<BeatmapSetInfo>();
|
||||
|
||||
const string zzz_string = "zzzzz";
|
||||
const string zzz_lowercase = "zzzzz";
|
||||
const string zzz_uppercase = "ZZZZZ";
|
||||
|
||||
AddStep("Populuate beatmap sets", () =>
|
||||
{
|
||||
@ -640,10 +641,16 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
var set = TestResources.CreateTestBeatmapSetInfo();
|
||||
|
||||
if (i == 4)
|
||||
set.Beatmaps.ForEach(b => b.Metadata.Artist = zzz_string);
|
||||
set.Beatmaps.ForEach(b => b.Metadata.Artist = zzz_uppercase);
|
||||
|
||||
if (i == 8)
|
||||
set.Beatmaps.ForEach(b => b.Metadata.Artist = zzz_lowercase);
|
||||
|
||||
if (i == 12)
|
||||
set.Beatmaps.ForEach(b => b.Metadata.Author.Username = zzz_uppercase);
|
||||
|
||||
if (i == 16)
|
||||
set.Beatmaps.ForEach(b => b.Metadata.Author.Username = zzz_string);
|
||||
set.Beatmaps.ForEach(b => b.Metadata.Author.Username = zzz_lowercase);
|
||||
|
||||
sets.Add(set);
|
||||
}
|
||||
@ -652,9 +659,11 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
loadBeatmaps(sets);
|
||||
|
||||
AddStep("Sort by author", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Author }, false));
|
||||
AddAssert($"Check {zzz_string} is at bottom", () => carousel.BeatmapSets.Last().Metadata.Author.Username == zzz_string);
|
||||
AddAssert($"Check {zzz_uppercase} is last", () => carousel.BeatmapSets.Last().Metadata.Author.Username == zzz_uppercase);
|
||||
AddAssert($"Check {zzz_lowercase} is second last", () => carousel.BeatmapSets.SkipLast(1).Last().Metadata.Author.Username == zzz_lowercase);
|
||||
AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false));
|
||||
AddAssert($"Check {zzz_string} is at bottom", () => carousel.BeatmapSets.Last().Metadata.Artist == zzz_string);
|
||||
AddAssert($"Check {zzz_uppercase} is last", () => carousel.BeatmapSets.Last().Metadata.Artist == zzz_uppercase);
|
||||
AddAssert($"Check {zzz_lowercase} is second last", () => carousel.BeatmapSets.SkipLast(1).Last().Metadata.Artist == zzz_lowercase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -282,7 +282,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
/// <summary>
|
||||
/// Check that the logo is tracking the position of the facade, with an acceptable precision lenience.
|
||||
/// </summary>
|
||||
public bool IsLogoTracking => Precision.AlmostEquals(Logo.Position, ComputeLogoTrackingPosition());
|
||||
public bool IsLogoTracking => Precision.AlmostEquals(Logo!.Position, ComputeLogoTrackingPosition());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -788,7 +788,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
AddAssert("all columns visible", () => this.ChildrenOfType<ModColumn>().All(col => col.IsPresent));
|
||||
|
||||
AddStep("set search", () => modSelectOverlay.SearchTerm = "HD");
|
||||
AddAssert("one column visible", () => this.ChildrenOfType<ModColumn>().Count(col => col.IsPresent) == 1);
|
||||
AddAssert("two columns visible", () => this.ChildrenOfType<ModColumn>().Count(col => col.IsPresent) == 2);
|
||||
|
||||
AddStep("filter out everything", () => modSelectOverlay.SearchTerm = "Some long search term with no matches");
|
||||
AddAssert("no columns visible", () => this.ChildrenOfType<ModColumn>().All(col => !col.IsPresent));
|
||||
@ -812,7 +812,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
AddAssert("all columns visible", () => this.ChildrenOfType<ModColumn>().All(col => col.IsPresent));
|
||||
|
||||
AddStep("set search", () => modSelectOverlay.SearchTerm = "fail");
|
||||
AddAssert("one column visible", () => this.ChildrenOfType<ModColumn>().Count(col => col.IsPresent) == 2);
|
||||
AddAssert("one column visible", () => this.ChildrenOfType<ModColumn>().Count(col => col.IsPresent) == 1);
|
||||
|
||||
AddStep("hide", () => modSelectOverlay.Hide());
|
||||
AddStep("show", () => modSelectOverlay.Show());
|
||||
|
@ -4,6 +4,7 @@
|
||||
using System.Diagnostics;
|
||||
using ManagedBass.Fx;
|
||||
using osu.Framework.Audio.Mixing;
|
||||
using osu.Framework.Caching;
|
||||
using osu.Framework.Graphics;
|
||||
|
||||
namespace osu.Game.Audio.Effects
|
||||
@ -22,6 +23,8 @@ namespace osu.Game.Audio.Effects
|
||||
|
||||
private bool isAttached;
|
||||
|
||||
private readonly Cached filterApplication = new Cached();
|
||||
|
||||
private int cutoff;
|
||||
|
||||
/// <summary>
|
||||
@ -36,7 +39,7 @@ namespace osu.Game.Audio.Effects
|
||||
return;
|
||||
|
||||
cutoff = value;
|
||||
updateFilter(cutoff);
|
||||
filterApplication.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
@ -61,6 +64,17 @@ namespace osu.Game.Audio.Effects
|
||||
Cutoff = getInitialCutoff(type);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (!filterApplication.IsValid)
|
||||
{
|
||||
updateFilter(cutoff);
|
||||
filterApplication.Validate();
|
||||
}
|
||||
}
|
||||
|
||||
private int getInitialCutoff(BQFType type)
|
||||
{
|
||||
switch (type)
|
||||
|
@ -14,6 +14,7 @@ using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
@ -31,14 +32,16 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether to display a tooltip on hover. Only works if a beatmap was provided at construction time.
|
||||
/// Which type of tooltip to show. Only works if a beatmap was provided at construction time.
|
||||
/// </summary>
|
||||
public bool ShowTooltip { get; set; } = true;
|
||||
public DifficultyIconTooltipType TooltipType { get; set; } = DifficultyIconTooltipType.StarRating;
|
||||
|
||||
private readonly IBeatmapInfo? beatmap;
|
||||
|
||||
private readonly IRulesetInfo ruleset;
|
||||
|
||||
private readonly Mod[]? mods;
|
||||
|
||||
private Drawable background = null!;
|
||||
|
||||
private readonly Container iconContainer;
|
||||
@ -58,11 +61,14 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
/// Creates a new <see cref="DifficultyIcon"/>. Will use provided beatmap's <see cref="BeatmapInfo.StarRating"/> for initial value.
|
||||
/// </summary>
|
||||
/// <param name="beatmap">The beatmap to be displayed in the tooltip, and to be used for the initial star rating value.</param>
|
||||
/// <param name="mods">An array of mods to account for in the calculations</param>
|
||||
/// <param name="ruleset">An optional ruleset to be used for the icon display, in place of the beatmap's ruleset.</param>
|
||||
public DifficultyIcon(IBeatmapInfo beatmap, IRulesetInfo? ruleset = null)
|
||||
public DifficultyIcon(IBeatmapInfo beatmap, IRulesetInfo? ruleset = null, Mod[]? mods = null)
|
||||
: this(ruleset ?? beatmap.Ruleset)
|
||||
{
|
||||
this.beatmap = beatmap;
|
||||
this.mods = mods;
|
||||
|
||||
Current.Value = new StarDifficulty(beatmap.StarRating, 0);
|
||||
}
|
||||
|
||||
@ -127,6 +133,24 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
GetCustomTooltip() => new DifficultyIconTooltip();
|
||||
|
||||
DifficultyIconTooltipContent IHasCustomTooltip<DifficultyIconTooltipContent>.
|
||||
TooltipContent => (ShowTooltip && beatmap != null ? new DifficultyIconTooltipContent(beatmap, Current) : null)!;
|
||||
TooltipContent => (TooltipType != DifficultyIconTooltipType.None && beatmap != null ? new DifficultyIconTooltipContent(beatmap, Current, ruleset, mods, TooltipType) : null)!;
|
||||
}
|
||||
|
||||
public enum DifficultyIconTooltipType
|
||||
{
|
||||
/// <summary>
|
||||
/// No tooltip.
|
||||
/// </summary>
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// Star rating only.
|
||||
/// </summary>
|
||||
StarRating,
|
||||
|
||||
/// <summary>
|
||||
/// Star rating, OD, HP, CS, AR, length, BPM, and max combo.
|
||||
/// </summary>
|
||||
Extended,
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
@ -11,14 +11,25 @@ using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Beatmaps.Drawables
|
||||
{
|
||||
internal partial class DifficultyIconTooltip : VisibilityContainer, ITooltip<DifficultyIconTooltipContent>
|
||||
{
|
||||
private OsuSpriteText difficultyName;
|
||||
private StarRatingDisplay starRating;
|
||||
private OsuSpriteText difficultyName = null!;
|
||||
private StarRatingDisplay starRating = null!;
|
||||
private OsuSpriteText overallDifficulty = null!;
|
||||
private OsuSpriteText drainRate = null!;
|
||||
private OsuSpriteText circleSize = null!;
|
||||
private OsuSpriteText approachRate = null!;
|
||||
private OsuSpriteText bpm = null!;
|
||||
private OsuSpriteText length = null!;
|
||||
|
||||
private FillFlowContainer difficultyFillFlowContainer = null!;
|
||||
private FillFlowContainer miscFillFlowContainer = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
@ -31,7 +42,6 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Alpha = 0.9f,
|
||||
Colour = colours.Gray3,
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
@ -49,19 +59,49 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Font = OsuFont.GetFont(size: 16, weight: FontWeight.Bold),
|
||||
Font = OsuFont.GetFont(size: 16, weight: FontWeight.Bold)
|
||||
},
|
||||
starRating = new StarRatingDisplay(default, StarRatingDisplaySize.Small)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre
|
||||
},
|
||||
difficultyFillFlowContainer = new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Alpha = 0,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(5),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
circleSize = new OsuSpriteText { Font = OsuFont.GetFont(size: 14) },
|
||||
drainRate = new OsuSpriteText { Font = OsuFont.GetFont(size: 14) },
|
||||
overallDifficulty = new OsuSpriteText { Font = OsuFont.GetFont(size: 14) },
|
||||
approachRate = new OsuSpriteText { Font = OsuFont.GetFont(size: 14) },
|
||||
}
|
||||
},
|
||||
miscFillFlowContainer = new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Alpha = 0,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(5),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
length = new OsuSpriteText { Font = OsuFont.GetFont(size: 14) },
|
||||
bpm = new OsuSpriteText { Font = OsuFont.GetFont(size: 14) },
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private DifficultyIconTooltipContent displayedContent;
|
||||
private DifficultyIconTooltipContent? displayedContent;
|
||||
|
||||
public void SetContent(DifficultyIconTooltipContent content)
|
||||
{
|
||||
@ -72,6 +112,45 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
|
||||
starRating.Current.BindTarget = displayedContent.Difficulty;
|
||||
difficultyName.Text = displayedContent.BeatmapInfo.DifficultyName;
|
||||
|
||||
if (displayedContent.TooltipType == DifficultyIconTooltipType.StarRating)
|
||||
{
|
||||
difficultyFillFlowContainer.Hide();
|
||||
miscFillFlowContainer.Hide();
|
||||
return;
|
||||
}
|
||||
|
||||
difficultyFillFlowContainer.Show();
|
||||
miscFillFlowContainer.Show();
|
||||
|
||||
double rate = 1;
|
||||
|
||||
if (displayedContent.Mods != null)
|
||||
{
|
||||
foreach (var mod in displayedContent.Mods.OfType<IApplicableToRate>())
|
||||
rate = mod.ApplyToRate(0, rate);
|
||||
}
|
||||
|
||||
double bpmAdjusted = displayedContent.BeatmapInfo.BPM * rate;
|
||||
|
||||
BeatmapDifficulty originalDifficulty = new BeatmapDifficulty(displayedContent.BeatmapInfo.Difficulty);
|
||||
|
||||
if (displayedContent.Mods != null)
|
||||
{
|
||||
foreach (var mod in displayedContent.Mods.OfType<IApplicableToDifficulty>())
|
||||
mod.ApplyToDifficulty(originalDifficulty);
|
||||
}
|
||||
|
||||
Ruleset ruleset = displayedContent.Ruleset.CreateInstance();
|
||||
BeatmapDifficulty adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(originalDifficulty, rate);
|
||||
|
||||
circleSize.Text = @"CS: " + adjustedDifficulty.CircleSize.ToString(@"0.##");
|
||||
drainRate.Text = @" HP: " + adjustedDifficulty.DrainRate.ToString(@"0.##");
|
||||
approachRate.Text = @" AR: " + adjustedDifficulty.ApproachRate.ToString(@"0.##");
|
||||
overallDifficulty.Text = @" OD: " + adjustedDifficulty.OverallDifficulty.ToString(@"0.##");
|
||||
|
||||
length.Text = "Length: " + TimeSpan.FromMilliseconds(displayedContent.BeatmapInfo.Length / rate).ToString(@"mm\:ss");
|
||||
bpm.Text = " BPM: " + Math.Round(bpmAdjusted, 0);
|
||||
}
|
||||
|
||||
public void Move(Vector2 pos) => Position = pos;
|
||||
@ -85,11 +164,20 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
{
|
||||
public readonly IBeatmapInfo BeatmapInfo;
|
||||
public readonly IBindable<StarDifficulty> Difficulty;
|
||||
public readonly IRulesetInfo Ruleset;
|
||||
public readonly Mod[]? Mods;
|
||||
public readonly DifficultyIconTooltipType TooltipType;
|
||||
|
||||
public DifficultyIconTooltipContent(IBeatmapInfo beatmapInfo, IBindable<StarDifficulty> difficulty)
|
||||
public DifficultyIconTooltipContent(IBeatmapInfo beatmapInfo, IBindable<StarDifficulty> difficulty, IRulesetInfo rulesetInfo, Mod[]? mods, DifficultyIconTooltipType tooltipType)
|
||||
{
|
||||
if (tooltipType == DifficultyIconTooltipType.None)
|
||||
throw new ArgumentOutOfRangeException(nameof(tooltipType), tooltipType, "Cannot instantiate a tooltip without a type");
|
||||
|
||||
BeatmapInfo = beatmapInfo;
|
||||
Difficulty = difficulty;
|
||||
Ruleset = rulesetInfo;
|
||||
Mods = mods;
|
||||
TooltipType = tooltipType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
private readonly OffsetCorrectionClock? userGlobalOffsetClock;
|
||||
private readonly OffsetCorrectionClock? platformOffsetClock;
|
||||
private readonly OffsetCorrectionClock? userBeatmapOffsetClock;
|
||||
private readonly FramedOffsetClock? userBeatmapOffsetClock;
|
||||
|
||||
private readonly IFrameBasedClock finalClockSource;
|
||||
|
||||
@ -70,7 +70,7 @@ namespace osu.Game.Beatmaps
|
||||
userGlobalOffsetClock = new OffsetCorrectionClock(platformOffsetClock);
|
||||
|
||||
// User per-beatmap offset will be applied to this final clock.
|
||||
finalClockSource = userBeatmapOffsetClock = new OffsetCorrectionClock(userGlobalOffsetClock);
|
||||
finalClockSource = userBeatmapOffsetClock = new FramedOffsetClock(userGlobalOffsetClock);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -122,7 +122,7 @@ namespace osu.Game.Beatmaps
|
||||
Debug.Assert(userBeatmapOffsetClock != null);
|
||||
Debug.Assert(platformOffsetClock != null);
|
||||
|
||||
return userGlobalOffsetClock.RateAdjustedOffset + userBeatmapOffsetClock.RateAdjustedOffset + platformOffsetClock.RateAdjustedOffset;
|
||||
return userGlobalOffsetClock.RateAdjustedOffset + userBeatmapOffsetClock.Offset + platformOffsetClock.RateAdjustedOffset;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -94,7 +94,7 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
static void addCombo(HitObject hitObject, ref int combo)
|
||||
{
|
||||
if (hitObject.CreateJudgement().MaxResult.AffectsCombo())
|
||||
if (hitObject.Judgement.MaxResult.AffectsCombo())
|
||||
combo++;
|
||||
|
||||
foreach (var nested in hitObject.NestedHitObjects)
|
||||
|
@ -9,6 +9,7 @@ using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics.Rendering;
|
||||
using osu.Framework.Graphics.Rendering.Dummy;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
@ -143,8 +144,6 @@ namespace osu.Game.Beatmaps
|
||||
{
|
||||
string fileStorePath = BeatmapSetInfo.GetPathForFile(BeatmapInfo.Path);
|
||||
|
||||
// TODO: check validity of file
|
||||
|
||||
var stream = GetStream(fileStorePath);
|
||||
|
||||
if (stream == null)
|
||||
@ -153,6 +152,12 @@ namespace osu.Game.Beatmaps
|
||||
return null;
|
||||
}
|
||||
|
||||
if (stream.ComputeMD5Hash() != BeatmapInfo.MD5Hash)
|
||||
{
|
||||
Logger.Log($"Beatmap failed to load (file {BeatmapInfo.Path} does not have the expected hash).", level: LogLevel.Error);
|
||||
return null;
|
||||
}
|
||||
|
||||
using (var reader = new LineBufferedReader(stream))
|
||||
return Decoder.GetDecoder<Beatmap>(reader).Decode(reader);
|
||||
}
|
||||
|
@ -77,12 +77,19 @@ namespace osu.Game.Configuration
|
||||
|
||||
SetDefault(OsuSetting.SavePassword, false).ValueChanged += enabled =>
|
||||
{
|
||||
if (enabled.NewValue) SetValue(OsuSetting.SaveUsername, true);
|
||||
if (enabled.NewValue)
|
||||
SetValue(OsuSetting.SaveUsername, true);
|
||||
else
|
||||
GetBindable<string>(OsuSetting.Token).SetDefault();
|
||||
};
|
||||
|
||||
SetDefault(OsuSetting.SaveUsername, true).ValueChanged += enabled =>
|
||||
{
|
||||
if (!enabled.NewValue) SetValue(OsuSetting.SavePassword, false);
|
||||
if (!enabled.NewValue)
|
||||
{
|
||||
GetBindable<string>(OsuSetting.Username).SetDefault();
|
||||
SetValue(OsuSetting.SavePassword, false);
|
||||
}
|
||||
};
|
||||
|
||||
SetDefault(OsuSetting.ExternalLinkWarning, true);
|
||||
|
@ -1,8 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@ -19,7 +17,7 @@ namespace osu.Game.Graphics.Containers
|
||||
{
|
||||
public Facade LogoFacade => facade;
|
||||
|
||||
protected OsuLogo Logo { get; private set; }
|
||||
protected OsuLogo? Logo { get; private set; }
|
||||
|
||||
private readonly InternalFacade facade = new InternalFacade();
|
||||
|
||||
@ -76,15 +74,15 @@ namespace osu.Game.Graphics.Containers
|
||||
/// <remarks>Will only be correct if the logo's <see cref="Drawable.RelativePositionAxes"/> are set to Axes.Both</remarks>
|
||||
protected Vector2 ComputeLogoTrackingPosition()
|
||||
{
|
||||
var absolutePos = Logo.Parent!.ToLocalSpace(LogoFacade.ScreenSpaceDrawQuad.Centre);
|
||||
var absolutePos = Logo!.Parent!.ToLocalSpace(LogoFacade.ScreenSpaceDrawQuad.Centre);
|
||||
|
||||
return new Vector2(absolutePos.X / Logo.Parent!.RelativeToAbsoluteFactor.X,
|
||||
absolutePos.Y / Logo.Parent!.RelativeToAbsoluteFactor.Y);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
protected override void UpdateAfterChildren()
|
||||
{
|
||||
base.Update();
|
||||
base.UpdateAfterChildren();
|
||||
|
||||
if (Logo == null)
|
||||
return;
|
||||
|
@ -10,11 +10,11 @@ using osu.Game.Overlays;
|
||||
|
||||
namespace osu.Game.Graphics.Containers.Markdown
|
||||
{
|
||||
public partial class OsuMarkdownFencedCodeBlock : MarkdownFencedCodeBlock
|
||||
public partial class OsuMarkdownCodeBlock : MarkdownCodeBlock
|
||||
{
|
||||
// TODO : change to monospace font for this component
|
||||
public OsuMarkdownFencedCodeBlock(FencedCodeBlock fencedCodeBlock)
|
||||
: base(fencedCodeBlock)
|
||||
public OsuMarkdownCodeBlock(CodeBlock codeBlock)
|
||||
: base(codeBlock)
|
||||
{
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ namespace osu.Game.Graphics.Containers.Markdown
|
||||
|
||||
protected override MarkdownHeading CreateHeading(HeadingBlock headingBlock) => new OsuMarkdownHeading(headingBlock);
|
||||
|
||||
protected override MarkdownFencedCodeBlock CreateFencedCodeBlock(FencedCodeBlock fencedCodeBlock) => new OsuMarkdownFencedCodeBlock(fencedCodeBlock);
|
||||
protected override MarkdownCodeBlock CreateCodeBlock(CodeBlock codeBlock) => new OsuMarkdownCodeBlock(codeBlock);
|
||||
|
||||
protected override MarkdownSeparator CreateSeparator(ThematicBreakBlock thematicBlock) => new OsuMarkdownSeparator();
|
||||
|
||||
|
49
osu.Game/Localisation/StorageErrorDialogStrings.cs
Normal file
49
osu.Game/Localisation/StorageErrorDialogStrings.cs
Normal file
@ -0,0 +1,49 @@
|
||||
// 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.Localisation;
|
||||
|
||||
namespace osu.Game.Localisation
|
||||
{
|
||||
public static class StorageErrorDialogStrings
|
||||
{
|
||||
private const string prefix = @"osu.Game.Resources.Localisation.StorageErrorDialog";
|
||||
|
||||
/// <summary>
|
||||
/// "osu! storage error"
|
||||
/// </summary>
|
||||
public static LocalisableString StorageError => new TranslatableString(getKey(@"storage_error"), @"osu! storage error");
|
||||
|
||||
/// <summary>
|
||||
/// "The specified osu! data location ("{0}") is not accessible. If it is on external storage, please reconnect the device and try again."
|
||||
/// </summary>
|
||||
public static LocalisableString LocationIsNotAccessible(string? loc) => new TranslatableString(getKey(@"location_is_not_accessible"), @"The specified osu! data location (""{0}"") is not accessible. If it is on external storage, please reconnect the device and try again.", loc);
|
||||
|
||||
/// <summary>
|
||||
/// "The specified osu! data location ("{0}") is empty. If you have moved the files, please close osu! and move them back."
|
||||
/// </summary>
|
||||
public static LocalisableString LocationIsEmpty(string? loc2) => new TranslatableString(getKey(@"location_is_empty"), @"The specified osu! data location (""{0}"") is empty. If you have moved the files, please close osu! and move them back.", loc2);
|
||||
|
||||
/// <summary>
|
||||
/// "Try again"
|
||||
/// </summary>
|
||||
public static LocalisableString TryAgain => new TranslatableString(getKey(@"try_again"), @"Try again");
|
||||
|
||||
/// <summary>
|
||||
/// "Use default location until restart"
|
||||
/// </summary>
|
||||
public static LocalisableString UseDefaultLocation => new TranslatableString(getKey(@"use_default_location"), @"Use default location until restart");
|
||||
|
||||
/// <summary>
|
||||
/// "Reset to default location"
|
||||
/// </summary>
|
||||
public static LocalisableString ResetToDefaultLocation => new TranslatableString(getKey(@"reset_to_default_location"), @"Reset to default location");
|
||||
|
||||
/// <summary>
|
||||
/// "Start fresh at specified location"
|
||||
/// </summary>
|
||||
public static LocalisableString StartFresh => new TranslatableString(getKey(@"start_fresh"), @"Start fresh at specified location");
|
||||
|
||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||
}
|
||||
}
|
@ -34,6 +34,19 @@ namespace osu.Game.Online.API.Requests.Responses
|
||||
[JsonProperty(@"previous_usernames")]
|
||||
public string[] PreviousUsernames;
|
||||
|
||||
[JsonProperty(@"rank_highest")]
|
||||
[CanBeNull]
|
||||
public UserRankHighest RankHighest;
|
||||
|
||||
public class UserRankHighest
|
||||
{
|
||||
[JsonProperty(@"rank")]
|
||||
public int Rank;
|
||||
|
||||
[JsonProperty(@"updated_at")]
|
||||
public DateTimeOffset UpdatedAt;
|
||||
}
|
||||
|
||||
[JsonProperty(@"country_code")]
|
||||
private string countryCodeString;
|
||||
|
||||
|
@ -4,9 +4,11 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.ListExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Lists;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
@ -23,12 +25,21 @@ namespace osu.Game.Online.Chat
|
||||
/// <summary>
|
||||
/// Each word part of a chat link (split for word-wrap support).
|
||||
/// </summary>
|
||||
public readonly List<Drawable> Parts;
|
||||
public readonly SlimReadOnlyListWrapper<Drawable> Parts;
|
||||
|
||||
[Resolved]
|
||||
private OverlayColourProvider? overlayColourProvider { get; set; }
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parts.Any(d => d.ReceivePositionalInputAt(screenSpacePos));
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos)
|
||||
{
|
||||
foreach (var part in Parts)
|
||||
{
|
||||
if (part.ReceivePositionalInputAt(screenSpacePos))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new LinkHoverSounds(sampleSet, Parts);
|
||||
|
||||
@ -39,7 +50,7 @@ namespace osu.Game.Online.Chat
|
||||
|
||||
public DrawableLinkCompiler(IEnumerable<Drawable> parts)
|
||||
{
|
||||
Parts = parts.ToList();
|
||||
Parts = parts.ToList().AsSlimReadOnly();
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -52,15 +63,24 @@ namespace osu.Game.Online.Chat
|
||||
|
||||
private partial class LinkHoverSounds : HoverClickSounds
|
||||
{
|
||||
private readonly List<Drawable> parts;
|
||||
private readonly SlimReadOnlyListWrapper<Drawable> parts;
|
||||
|
||||
public LinkHoverSounds(HoverSampleSet sampleSet, List<Drawable> parts)
|
||||
public LinkHoverSounds(HoverSampleSet sampleSet, SlimReadOnlyListWrapper<Drawable> parts)
|
||||
: base(sampleSet)
|
||||
{
|
||||
this.parts = parts;
|
||||
}
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => parts.Any(d => d.ReceivePositionalInputAt(screenSpacePos));
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos)
|
||||
{
|
||||
foreach (var part in parts)
|
||||
{
|
||||
if (part.ReceivePositionalInputAt(screenSpacePos))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,18 @@ namespace osu.Game.Online
|
||||
/// </summary>
|
||||
public interface IStatefulUserHubClient
|
||||
{
|
||||
/// <summary>
|
||||
/// Invoked when the server requests a client to disconnect.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When this request is received, the client must presume any and all further requests to the server
|
||||
/// will either fail or be ignored.
|
||||
/// This method is ONLY to be used for the purposes of:
|
||||
/// <list type="bullet">
|
||||
/// <item>actually physically disconnecting from the server,</item>
|
||||
/// <item>cleaning up / setting up any and all required local client state.</item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
Task DisconnectRequested();
|
||||
}
|
||||
}
|
||||
|
@ -58,6 +58,7 @@ namespace osu.Game.Online.Metadata
|
||||
// https://github.com/dotnet/aspnetcore/issues/15198
|
||||
connection.On<BeatmapUpdates>(nameof(IMetadataClient.BeatmapSetsUpdated), ((IMetadataClient)this).BeatmapSetsUpdated);
|
||||
connection.On<int, UserPresence?>(nameof(IMetadataClient.UserPresenceUpdated), ((IMetadataClient)this).UserPresenceUpdated);
|
||||
connection.On(nameof(IStatefulUserHubClient.DisconnectRequested), ((IMetadataClient)this).DisconnectRequested);
|
||||
};
|
||||
|
||||
IsConnected.BindTo(connector.IsConnected);
|
||||
@ -231,7 +232,8 @@ namespace osu.Game.Online.Metadata
|
||||
public override async Task DisconnectRequested()
|
||||
{
|
||||
await base.DisconnectRequested().ConfigureAwait(false);
|
||||
await EndWatchingUserPresence().ConfigureAwait(false);
|
||||
if (connector != null)
|
||||
await connector.Disconnect().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
|
@ -297,7 +297,7 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
},
|
||||
icon = new DifficultyIcon(beatmapInfo, ruleset)
|
||||
{
|
||||
ShowTooltip = false,
|
||||
TooltipType = DifficultyIconTooltipType.None,
|
||||
Current = { Value = new StarDifficulty(beatmapInfo.StarRating, 0) },
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
|
@ -66,13 +66,10 @@ namespace osu.Game.Overlays.Chat.ChannelList
|
||||
Colour = colourProvider.Background4,
|
||||
Alpha = 0f,
|
||||
},
|
||||
new Container
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Left = 18, Right = 10 },
|
||||
Child = new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
@ -98,9 +95,8 @@ namespace osu.Game.Overlays.Chat.ChannelList
|
||||
createMentionPill(),
|
||||
close = createCloseButton(),
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Action = () => OnRequestSelect?.Invoke(Channel);
|
||||
|
@ -18,6 +18,7 @@ using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Utils;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Overlays.Mods
|
||||
@ -158,7 +159,7 @@ namespace osu.Game.Overlays.Mods
|
||||
foreach (var mod in Mods.Value.OfType<IApplicableToRate>())
|
||||
rate = mod.ApplyToRate(0, rate);
|
||||
|
||||
bpmDisplay.Current.Value = BeatmapInfo.Value.BPM * rate;
|
||||
bpmDisplay.Current.Value = FormatUtils.RoundBPM(BeatmapInfo.Value.BPM, rate);
|
||||
|
||||
BeatmapDifficulty originalDifficulty = new BeatmapDifficulty(BeatmapInfo.Value.Difficulty);
|
||||
|
||||
@ -188,7 +189,7 @@ namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
protected override double RollingDuration => 250;
|
||||
|
||||
protected override LocalisableString FormatCount(double count) => count.ToLocalisableString("0 BPM");
|
||||
protected override LocalisableString FormatCount(int count) => count.ToLocalisableString("0 BPM");
|
||||
|
||||
protected override OsuSpriteText CreateSpriteText() => new OsuSpriteText
|
||||
{
|
||||
|
@ -77,11 +77,11 @@ namespace osu.Game.Overlays.Mods
|
||||
/// <seealso cref="ModState.Visible"/>
|
||||
public bool Visible => modState.Visible;
|
||||
|
||||
public override IEnumerable<LocalisableString> FilterTerms => new[]
|
||||
public override IEnumerable<LocalisableString> FilterTerms => new LocalisableString[]
|
||||
{
|
||||
Mod.Name,
|
||||
Mod.Name.Replace(" ", string.Empty),
|
||||
Mod.Acronym,
|
||||
Mod.Description
|
||||
};
|
||||
|
||||
public override bool MatchingFilter
|
||||
|
@ -143,6 +143,13 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
scoreRankInfo.Value.RankCount = user?.Statistics?.GradesCount[scoreRankInfo.Key] ?? 0;
|
||||
|
||||
detailGlobalRank.Content = user?.Statistics?.GlobalRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-";
|
||||
|
||||
var rankHighest = user?.RankHighest;
|
||||
|
||||
detailGlobalRank.ContentTooltipText = rankHighest != null
|
||||
? UsersStrings.ShowRankHighest(rankHighest.Rank.ToLocalisableString("\\##,##0"), rankHighest.UpdatedAt.ToLocalisableString(@"d MMM yyyy"))
|
||||
: string.Empty;
|
||||
|
||||
detailCountryRank.Content = user?.Statistics?.CountryRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-";
|
||||
|
||||
rankGraph.Statistics.Value = user?.Statistics;
|
||||
|
@ -4,6 +4,7 @@
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
@ -13,7 +14,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
public partial class ProfileValueDisplay : CompositeDrawable
|
||||
{
|
||||
private readonly OsuSpriteText title;
|
||||
private readonly OsuSpriteText content;
|
||||
private readonly ContentText content;
|
||||
|
||||
public LocalisableString Title
|
||||
{
|
||||
@ -25,6 +26,11 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
set => content.Text = value;
|
||||
}
|
||||
|
||||
public LocalisableString ContentTooltipText
|
||||
{
|
||||
set => content.TooltipText = value;
|
||||
}
|
||||
|
||||
public ProfileValueDisplay(bool big = false, int minimumWidth = 60)
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
@ -38,9 +44,9 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
{
|
||||
Font = OsuFont.GetFont(size: 12)
|
||||
},
|
||||
content = new OsuSpriteText
|
||||
content = new ContentText
|
||||
{
|
||||
Font = OsuFont.GetFont(size: big ? 30 : 20, weight: FontWeight.Light)
|
||||
Font = OsuFont.GetFont(size: big ? 30 : 20, weight: FontWeight.Light),
|
||||
},
|
||||
new Container // Add a minimum size to the FillFlowContainer
|
||||
{
|
||||
@ -56,5 +62,10 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
title.Colour = colourProvider.Content1;
|
||||
content.Colour = colourProvider.Content2;
|
||||
}
|
||||
|
||||
private partial class ContentText : OsuSpriteText, IHasTooltip
|
||||
{
|
||||
public LocalisableString TooltipText { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,25 +5,19 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
|
||||
namespace osu.Game.Overlays.Profile.Header.Components
|
||||
{
|
||||
public partial class TotalPlayTime : CompositeDrawable, IHasTooltip
|
||||
public partial class TotalPlayTime : CompositeDrawable
|
||||
{
|
||||
public readonly Bindable<UserProfileData?> User = new Bindable<UserProfileData?>();
|
||||
|
||||
public LocalisableString TooltipText { get; set; }
|
||||
|
||||
private ProfileValueDisplay info = null!;
|
||||
|
||||
public TotalPlayTime()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
TooltipText = "0 hours";
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -32,6 +26,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
InternalChild = info = new ProfileValueDisplay(minimumWidth: 140)
|
||||
{
|
||||
Title = UsersStrings.ShowStatsPlayTime,
|
||||
ContentTooltipText = "0 hours",
|
||||
};
|
||||
|
||||
User.BindValueChanged(updateTime, true);
|
||||
@ -40,7 +35,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
private void updateTime(ValueChangedEvent<UserProfileData?> user)
|
||||
{
|
||||
int? playTime = user.NewValue?.User.Statistics?.PlayTime;
|
||||
TooltipText = (playTime ?? 0) / 3600 + " hours";
|
||||
info.ContentTooltipText = (playTime ?? 0) / 3600 + " hours";
|
||||
info.Content = formatTime(playTime);
|
||||
}
|
||||
|
||||
|
@ -26,15 +26,11 @@ namespace osu.Game.Overlays.Profile.Header
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Background5,
|
||||
},
|
||||
new Container
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding { Horizontal = WaveOverlayContainer.HORIZONTAL_PADDING, Vertical = 10 },
|
||||
Child = new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
@ -70,7 +66,6 @@ namespace osu.Game.Overlays.Profile.Header
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,6 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using osu.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Extensions;
|
||||
@ -28,15 +27,16 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
||||
var renderer = config.GetBindable<RendererType>(FrameworkSetting.Renderer);
|
||||
automaticRendererInUse = renderer.Value == RendererType.Automatic;
|
||||
|
||||
SettingsEnumDropdown<RendererType> rendererDropdown;
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
rendererDropdown = new RendererSettingsDropdown
|
||||
new RendererSettingsDropdown
|
||||
{
|
||||
LabelText = GraphicsSettingsStrings.Renderer,
|
||||
Current = renderer,
|
||||
Items = host.GetPreferredRenderersForCurrentPlatform().Order().Where(t => t != RendererType.Vulkan),
|
||||
Items = host.GetPreferredRenderersForCurrentPlatform().Order()
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
.Where(t => t != RendererType.Vulkan && t != RendererType.OpenGLLegacy),
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
Keywords = new[] { @"compatibility", @"directx" },
|
||||
},
|
||||
// TODO: this needs to be a custom dropdown at some point
|
||||
@ -79,13 +79,6 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: remove this once we support SDL+android.
|
||||
if (RuntimeInfo.OS == RuntimeInfo.Platform.Android)
|
||||
{
|
||||
rendererDropdown.Items = new[] { RendererType.Automatic, RendererType.OpenGLLegacy };
|
||||
rendererDropdown.SetNoticeText("New renderer support for android is coming soon!", true);
|
||||
}
|
||||
}
|
||||
|
||||
private partial class RendererSettingsDropdown : SettingsEnumDropdown<RendererType>
|
||||
|
@ -10,9 +10,11 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics.Containers;
|
||||
@ -66,6 +68,7 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
private IBindable<WorkingBeatmap> beatmap { get; set; } = null!;
|
||||
|
||||
private OsuScreen? lastTargetScreen;
|
||||
private InvokeOnDisposal? nestedInputManagerDisable;
|
||||
|
||||
private Vector2 lastDrawSize;
|
||||
|
||||
@ -100,12 +103,14 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
{
|
||||
globallyDisableBeatmapSkinSetting();
|
||||
|
||||
if (skinEditor != null)
|
||||
{
|
||||
disableNestedInputManagers();
|
||||
skinEditor.Show();
|
||||
|
||||
if (lastTargetScreen is MainMenu)
|
||||
PresentGameplay();
|
||||
|
||||
if (skinEditor != null)
|
||||
{
|
||||
skinEditor.Show();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -122,6 +127,9 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
|
||||
AddInternal(editor);
|
||||
|
||||
if (lastTargetScreen is MainMenu)
|
||||
PresentGameplay();
|
||||
|
||||
Debug.Assert(lastTargetScreen != null);
|
||||
|
||||
SetTarget(lastTargetScreen);
|
||||
@ -132,6 +140,8 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
{
|
||||
skinEditor?.Save(false);
|
||||
skinEditor?.Hide();
|
||||
nestedInputManagerDisable?.Dispose();
|
||||
nestedInputManagerDisable = null;
|
||||
|
||||
globallyReenableBeatmapSkinSetting();
|
||||
}
|
||||
@ -243,6 +253,9 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
/// </summary>
|
||||
public void SetTarget(OsuScreen screen)
|
||||
{
|
||||
nestedInputManagerDisable?.Dispose();
|
||||
nestedInputManagerDisable = null;
|
||||
|
||||
lastTargetScreen = screen;
|
||||
|
||||
if (skinEditor == null) return;
|
||||
@ -261,7 +274,7 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
|
||||
Debug.Assert(skinEditor != null);
|
||||
|
||||
if (!target.IsLoaded)
|
||||
if (!target.IsLoaded || !skinEditor.IsLoaded)
|
||||
{
|
||||
Scheduler.AddOnce(setTarget, target);
|
||||
return;
|
||||
@ -271,6 +284,7 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
{
|
||||
skinEditor.Save(false);
|
||||
skinEditor.UpdateTargetScreen(target);
|
||||
disableNestedInputManagers();
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -280,6 +294,21 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
}
|
||||
}
|
||||
|
||||
private void disableNestedInputManagers()
|
||||
{
|
||||
if (lastTargetScreen == null)
|
||||
return;
|
||||
|
||||
var nestedInputManagers = lastTargetScreen.ChildrenOfType<PassThroughInputManager>().Where(manager => manager.UseParentInput).ToArray();
|
||||
foreach (var inputManager in nestedInputManagers)
|
||||
inputManager.UseParentInput = false;
|
||||
nestedInputManagerDisable = new InvokeOnDisposal(() =>
|
||||
{
|
||||
foreach (var inputManager in nestedInputManagers)
|
||||
inputManager.UseParentInput = true;
|
||||
});
|
||||
}
|
||||
|
||||
private readonly Bindable<bool> beatmapSkins = new Bindable<bool>();
|
||||
private LeasedBindable<bool>? leasedBeatmapSkins;
|
||||
|
||||
@ -325,7 +354,7 @@ namespace osu.Game.Overlays.SkinEditor
|
||||
base.LoadComplete();
|
||||
|
||||
if (!LoadedBeatmapSuccessfully)
|
||||
Scheduler.AddDelayed(this.Exit, 3000);
|
||||
Scheduler.AddDelayed(this.Exit, 1000);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
|
@ -113,9 +113,9 @@ namespace osu.Game.Rulesets.Difficulty
|
||||
private IEnumerable<HitResult> getPerfectHitResults(HitObject hitObject)
|
||||
{
|
||||
foreach (HitObject nested in hitObject.NestedHitObjects)
|
||||
yield return nested.CreateJudgement().MaxResult;
|
||||
yield return nested.Judgement.MaxResult;
|
||||
|
||||
yield return hitObject.CreateJudgement().MaxResult;
|
||||
yield return hitObject.Judgement.MaxResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
@ -124,12 +123,34 @@ namespace osu.Game.Rulesets.Edit
|
||||
|
||||
private (HitObject before, HitObject after)? getObjectsOnEitherSideOfCurrentTime()
|
||||
{
|
||||
HitObject? lastBefore = playfield.HitObjectContainer.AliveObjects.LastOrDefault(h => h.HitObject.StartTime < editorClock.CurrentTime)?.HitObject;
|
||||
HitObject? lastBefore = null;
|
||||
|
||||
foreach (var entry in playfield.HitObjectContainer.AliveEntries)
|
||||
{
|
||||
double objTime = entry.Value.HitObject.StartTime;
|
||||
|
||||
if (objTime >= editorClock.CurrentTime)
|
||||
continue;
|
||||
|
||||
if (objTime > lastBefore?.StartTime)
|
||||
lastBefore = entry.Value.HitObject;
|
||||
}
|
||||
|
||||
if (lastBefore == null)
|
||||
return null;
|
||||
|
||||
HitObject? firstAfter = playfield.HitObjectContainer.AliveObjects.FirstOrDefault(h => h.HitObject.StartTime >= editorClock.CurrentTime)?.HitObject;
|
||||
HitObject? firstAfter = null;
|
||||
|
||||
foreach (var entry in playfield.HitObjectContainer.AliveEntries)
|
||||
{
|
||||
double objTime = entry.Value.HitObject.StartTime;
|
||||
|
||||
if (objTime < editorClock.CurrentTime)
|
||||
continue;
|
||||
|
||||
if (objTime < firstAfter?.StartTime)
|
||||
firstAfter = entry.Value.HitObject;
|
||||
}
|
||||
|
||||
if (firstAfter == null)
|
||||
return null;
|
||||
|
@ -770,7 +770,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
private void ensureEntryHasResult()
|
||||
{
|
||||
Debug.Assert(Entry != null);
|
||||
Entry.Result ??= CreateResult(HitObject.CreateJudgement())
|
||||
Entry.Result ??= CreateResult(HitObject.Judgement)
|
||||
?? throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}.");
|
||||
}
|
||||
|
||||
|
@ -163,8 +163,19 @@ namespace osu.Game.Rulesets.Objects
|
||||
protected void AddNested(HitObject hitObject) => nestedHitObjects.Add(hitObject);
|
||||
|
||||
/// <summary>
|
||||
/// Creates the <see cref="Judgement"/> that represents the scoring information for this <see cref="HitObject"/>.
|
||||
/// The <see cref="Judgement"/> that represents the scoring information for this <see cref="HitObject"/>.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public Judgement Judgement => judgement ??= CreateJudgement();
|
||||
|
||||
private Judgement judgement;
|
||||
|
||||
/// <summary>
|
||||
/// Should be overridden to create a <see cref="Judgement"/> that represents the scoring information for this <see cref="HitObject"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For read access, use <see cref="Judgement"/> to avoid unnecessary allocations.
|
||||
/// </remarks>
|
||||
[NotNull]
|
||||
public virtual Judgement CreateJudgement() => new Judgement();
|
||||
|
||||
|
@ -61,16 +61,17 @@ namespace osu.Game.Rulesets.Objects
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
Debug.Assert(args.NewItems != null);
|
||||
|
||||
foreach (var c in args.NewItems.Cast<PathControlPoint>())
|
||||
c.Changed += invalidate;
|
||||
foreach (object? newItem in args.NewItems)
|
||||
((PathControlPoint)newItem).Changed += invalidate;
|
||||
|
||||
break;
|
||||
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
Debug.Assert(args.OldItems != null);
|
||||
|
||||
foreach (var c in args.OldItems.Cast<PathControlPoint>())
|
||||
c.Changed -= invalidate;
|
||||
foreach (object? oldItem in args.OldItems)
|
||||
((PathControlPoint)oldItem).Changed -= invalidate;
|
||||
break;
|
||||
}
|
||||
|
||||
@ -269,10 +270,10 @@ namespace osu.Game.Rulesets.Objects
|
||||
{
|
||||
List<Vector2> subPath = calculateSubPath(segmentVertices, segmentType);
|
||||
// Skip the first vertex if it is the same as the last vertex from the previous segment
|
||||
int skipFirst = calculatedPath.Count > 0 && subPath.Count > 0 && calculatedPath.Last() == subPath[0] ? 1 : 0;
|
||||
bool skipFirst = calculatedPath.Count > 0 && subPath.Count > 0 && calculatedPath.Last() == subPath[0];
|
||||
|
||||
foreach (Vector2 t in subPath.Skip(skipFirst))
|
||||
calculatedPath.Add(t);
|
||||
for (int j = skipFirst ? 1 : 0; j < subPath.Count; j++)
|
||||
calculatedPath.Add(subPath[j]);
|
||||
}
|
||||
|
||||
if (i > 0)
|
||||
|
@ -149,7 +149,7 @@ namespace osu.Game.Rulesets.Scoring
|
||||
|
||||
foreach (var obj in EnumerateHitObjects(beatmap))
|
||||
{
|
||||
var judgement = obj.CreateJudgement();
|
||||
var judgement = obj.Judgement;
|
||||
|
||||
var result = CreateResult(obj, judgement);
|
||||
if (result == null)
|
||||
|
@ -141,7 +141,7 @@ namespace osu.Game.Rulesets.Scoring
|
||||
|
||||
void increaseHp(HitObject hitObject)
|
||||
{
|
||||
double amount = GetHealthIncreaseFor(hitObject, hitObject.CreateJudgement().MaxResult);
|
||||
double amount = GetHealthIncreaseFor(hitObject, hitObject.Judgement.MaxResult);
|
||||
currentHpUncapped += amount;
|
||||
currentHp = Math.Max(0, Math.Min(1, currentHp + amount));
|
||||
}
|
||||
|
@ -47,10 +47,7 @@ namespace osu.Game.Screens.Edit
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Background4,
|
||||
},
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new GridContainer
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ColumnDimensions = new[]
|
||||
@ -75,7 +72,6 @@ namespace osu.Game.Screens.Edit
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -97,11 +97,14 @@ namespace osu.Game.Screens.Edit.Components
|
||||
editorClock.Start();
|
||||
}
|
||||
|
||||
private static readonly IconUsage play_icon = FontAwesome.Regular.PlayCircle;
|
||||
private static readonly IconUsage pause_icon = FontAwesome.Regular.PauseCircle;
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
playButton.Icon = editorClock.IsRunning ? FontAwesome.Regular.PauseCircle : FontAwesome.Regular.PlayCircle;
|
||||
playButton.Icon = editorClock.IsRunning ? pause_icon : play_icon;
|
||||
}
|
||||
|
||||
private partial class PlaybackTabControl : OsuTabControl<double>
|
||||
|
@ -47,11 +47,26 @@ namespace osu.Game.Screens.Edit.Components
|
||||
};
|
||||
}
|
||||
|
||||
private double? lastTime;
|
||||
private double? lastBPM;
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (lastTime != editorClock.CurrentTime)
|
||||
{
|
||||
lastTime = editorClock.CurrentTime;
|
||||
trackTimer.Text = editorClock.CurrentTime.ToEditorFormattedString();
|
||||
bpm.Text = @$"{editorBeatmap.ControlPointInfo.TimingPointAt(editorClock.CurrentTime).BPM:0} BPM";
|
||||
}
|
||||
|
||||
double newBPM = editorBeatmap.ControlPointInfo.TimingPointAt(editorClock.CurrentTime).BPM;
|
||||
|
||||
if (lastBPM != newBPM)
|
||||
{
|
||||
lastBPM = newBPM;
|
||||
bpm.Text = @$"{newBPM:0} BPM";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -86,10 +86,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Background3
|
||||
},
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new GridContainer
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Content = new[]
|
||||
@ -118,19 +115,10 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
new Drawable[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new GridContainer
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Content = new[]
|
||||
@ -157,9 +145,6 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
new Dimension(GridSizeMode.Absolute, 20)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
new Drawable[]
|
||||
{
|
||||
|
@ -116,6 +116,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
updateStacking();
|
||||
}
|
||||
|
||||
private readonly Stack<HitObject> currentConcurrentObjects = new Stack<HitObject>();
|
||||
|
||||
private void updateStacking()
|
||||
{
|
||||
// because only blueprints of objects which are alive (via pooling) are displayed in the timeline, it's feasible to do this every-update.
|
||||
@ -125,10 +127,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
// after the stack gets this tall, we can presume there is space underneath to draw subsequent blueprints.
|
||||
const int stack_reset_count = 3;
|
||||
|
||||
Stack<HitObject> currentConcurrentObjects = new Stack<HitObject>();
|
||||
currentConcurrentObjects.Clear();
|
||||
|
||||
foreach (var b in SelectionBlueprints.Reverse())
|
||||
for (int i = SelectionBlueprints.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var b = SelectionBlueprints[i];
|
||||
|
||||
// remove objects from the stack as long as their end time is in the past.
|
||||
while (currentConcurrentObjects.TryPeek(out HitObject hitObject))
|
||||
{
|
||||
|
@ -57,16 +57,12 @@ namespace osu.Game.Screens.Edit
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Background4
|
||||
},
|
||||
new Container
|
||||
new GridContainer
|
||||
{
|
||||
Name = "Timeline content",
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding { Horizontal = PADDING, Top = PADDING },
|
||||
Child = new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
@ -87,7 +83,6 @@ namespace osu.Game.Screens.Edit
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.Absolute, 90),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -64,16 +64,11 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding(padding),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding(padding),
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
@ -92,8 +87,6 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
new Drawable[]
|
||||
{
|
||||
|
@ -34,9 +34,6 @@ namespace osu.Game.Screens.Edit.Verify
|
||||
InterpretedDifficulty.Default = StarDifficulty.GetDifficultyRating(EditorBeatmap.BeatmapInfo.StarRating);
|
||||
InterpretedDifficulty.SetDefault();
|
||||
|
||||
Child = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
@ -53,7 +50,6 @@ namespace osu.Game.Screens.Edit.Verify
|
||||
new IssueSettings(),
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@ using System.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Dialog;
|
||||
|
||||
@ -17,7 +18,7 @@ namespace osu.Game.Screens.Menu
|
||||
|
||||
public StorageErrorDialog(OsuStorage storage, OsuStorageError error)
|
||||
{
|
||||
HeaderText = "osu! storage error";
|
||||
HeaderText = StorageErrorDialogStrings.StorageError;
|
||||
Icon = FontAwesome.Solid.ExclamationTriangle;
|
||||
|
||||
var buttons = new List<PopupDialogButton>();
|
||||
@ -25,13 +26,13 @@ namespace osu.Game.Screens.Menu
|
||||
switch (error)
|
||||
{
|
||||
case OsuStorageError.NotAccessible:
|
||||
BodyText = $"The specified osu! data location (\"{storage.CustomStoragePath}\") is not accessible. If it is on external storage, please reconnect the device and try again.";
|
||||
BodyText = StorageErrorDialogStrings.LocationIsNotAccessible(storage.CustomStoragePath);
|
||||
|
||||
buttons.AddRange(new PopupDialogButton[]
|
||||
{
|
||||
new PopupDialogCancelButton
|
||||
{
|
||||
Text = "Try again",
|
||||
Text = StorageErrorDialogStrings.TryAgain,
|
||||
Action = () =>
|
||||
{
|
||||
if (!storage.TryChangeToCustomStorage(out var nextError))
|
||||
@ -40,29 +41,29 @@ namespace osu.Game.Screens.Menu
|
||||
},
|
||||
new PopupDialogCancelButton
|
||||
{
|
||||
Text = "Use default location until restart",
|
||||
Text = StorageErrorDialogStrings.UseDefaultLocation,
|
||||
},
|
||||
new PopupDialogOkButton
|
||||
{
|
||||
Text = "Reset to default location",
|
||||
Text = StorageErrorDialogStrings.ResetToDefaultLocation,
|
||||
Action = storage.ResetCustomStoragePath
|
||||
},
|
||||
});
|
||||
break;
|
||||
|
||||
case OsuStorageError.AccessibleButEmpty:
|
||||
BodyText = $"The specified osu! data location (\"{storage.CustomStoragePath}\") is empty. If you have moved the files, please close osu! and move them back.";
|
||||
BodyText = StorageErrorDialogStrings.LocationIsEmpty(storage.CustomStoragePath);
|
||||
|
||||
// Todo: Provide the option to search for the files similar to migration.
|
||||
buttons.AddRange(new PopupDialogButton[]
|
||||
{
|
||||
new PopupDialogCancelButton
|
||||
{
|
||||
Text = "Start fresh at specified location"
|
||||
Text = StorageErrorDialogStrings.StartFresh
|
||||
},
|
||||
new PopupDialogOkButton
|
||||
{
|
||||
Text = "Reset to default location",
|
||||
Text = StorageErrorDialogStrings.ResetToDefaultLocation,
|
||||
Action = storage.ResetCustomStoragePath
|
||||
},
|
||||
});
|
||||
|
@ -26,18 +26,15 @@ namespace osu.Game.Screens.OnlinePlay.Components
|
||||
[Resolved(typeof(Room))]
|
||||
protected BindableList<PlaylistItem> Playlist { get; private set; }
|
||||
|
||||
private readonly Drawable playlistArea;
|
||||
private readonly GridContainer playlistArea;
|
||||
private readonly DrawableRoomPlaylist playlist;
|
||||
|
||||
public MatchBeatmapDetailArea()
|
||||
{
|
||||
Add(playlistArea = new Container
|
||||
Add(playlistArea = new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Vertical = 10 },
|
||||
Child = new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
@ -68,7 +65,6 @@ namespace osu.Game.Screens.OnlinePlay.Components
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.Absolute, 50),
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -281,7 +281,13 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
}
|
||||
|
||||
if (beatmap != null)
|
||||
difficultyIconContainer.Child = new DifficultyIcon(beatmap, ruleset) { Size = new Vector2(icon_height) };
|
||||
{
|
||||
difficultyIconContainer.Child = new DifficultyIcon(beatmap, ruleset, requiredMods)
|
||||
{
|
||||
Size = new Vector2(icon_height),
|
||||
TooltipType = DifficultyIconTooltipType.Extended,
|
||||
};
|
||||
}
|
||||
else
|
||||
difficultyIconContainer.Clear();
|
||||
|
||||
|
@ -40,15 +40,12 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
Colour = Color4.Black,
|
||||
Alpha = 0.5f
|
||||
},
|
||||
new Container
|
||||
new GridContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Horizontal = padding },
|
||||
Child = new GridContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize, minSize: 80 - 2 * padding)
|
||||
@ -74,7 +71,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -95,13 +95,10 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
new Drawable[]
|
||||
{
|
||||
// Playlist items column
|
||||
new Container
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Right = 5 },
|
||||
Child = new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[] { new OverlinedPlaylistHeader(), },
|
||||
@ -127,7 +124,6 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(),
|
||||
}
|
||||
}
|
||||
},
|
||||
// Spacer
|
||||
null,
|
||||
|
@ -545,7 +545,7 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
if (playable.HitObjects.Count == 0)
|
||||
{
|
||||
Logger.Log("Beatmap contains no hit objects!", level: LogLevel.Error);
|
||||
Logger.Log("Beatmap contains no hit objects!", level: LogLevel.Important);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ namespace osu.Game.Screens.Play
|
||||
Scores = { BindTarget = LeaderboardScores }
|
||||
};
|
||||
|
||||
protected override bool HandleTokenRetrievalFailure(Exception exception) => false;
|
||||
protected override bool ShouldExitOnTokenRetrievalFailure(Exception exception) => false;
|
||||
|
||||
protected override Task ImportScore(Score score)
|
||||
{
|
||||
|
@ -118,7 +118,7 @@ namespace osu.Game.Screens.Play
|
||||
token = r.ID;
|
||||
tcs.SetResult(true);
|
||||
};
|
||||
req.Failure += handleTokenFailure;
|
||||
req.Failure += ex => handleTokenFailure(ex, displayNotification: true);
|
||||
|
||||
api.Queue(req);
|
||||
|
||||
@ -128,40 +128,49 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
return true;
|
||||
|
||||
void handleTokenFailure(Exception exception)
|
||||
void handleTokenFailure(Exception exception, bool displayNotification = false)
|
||||
{
|
||||
tcs.SetResult(false);
|
||||
|
||||
if (HandleTokenRetrievalFailure(exception))
|
||||
bool shouldExit = ShouldExitOnTokenRetrievalFailure(exception);
|
||||
|
||||
if (displayNotification || shouldExit)
|
||||
{
|
||||
string whatWillHappen = shouldExit
|
||||
? "Play in this state is not permitted."
|
||||
: "Your score will not be submitted.";
|
||||
|
||||
if (string.IsNullOrEmpty(exception.Message))
|
||||
Logger.Error(exception, "Failed to retrieve a score submission token.");
|
||||
Logger.Error(exception, $"Failed to retrieve a score submission token.\n\n{whatWillHappen}");
|
||||
else
|
||||
{
|
||||
switch (exception.Message)
|
||||
{
|
||||
case "expired token":
|
||||
Logger.Log("Score submission failed because your system clock is set incorrectly. Please check your system time, date and timezone.", level: LogLevel.Important);
|
||||
case @"missing token header":
|
||||
case @"invalid client hash":
|
||||
case @"invalid verification hash":
|
||||
Logger.Log($"Please ensure that you are using the latest version of the official game releases.\n\n{whatWillHappen}", level: LogLevel.Important);
|
||||
break;
|
||||
|
||||
case @"expired token":
|
||||
Logger.Log($"Your system clock is set incorrectly. Please check your system time, date and timezone.\n\n{whatWillHappen}", level: LogLevel.Important);
|
||||
break;
|
||||
|
||||
default:
|
||||
Logger.Log($"You are not able to submit a score: {exception.Message}", level: LogLevel.Important);
|
||||
Logger.Log($"{whatWillHappen} {exception.Message}", level: LogLevel.Important);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldExit)
|
||||
{
|
||||
Schedule(() =>
|
||||
{
|
||||
ValidForResume = false;
|
||||
this.Exit();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// Gameplay is allowed to continue, but we still should keep track of the error.
|
||||
// In the future, this should be visible to the user in some way.
|
||||
Logger.Log($"Score submission token retrieval failed ({exception.Message})");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -170,7 +179,7 @@ namespace osu.Game.Screens.Play
|
||||
/// </summary>
|
||||
/// <param name="exception">The error causing the failure.</param>
|
||||
/// <returns>Whether gameplay should be immediately exited as a result. Returning false allows the gameplay session to continue. Defaults to true.</returns>
|
||||
protected virtual bool HandleTokenRetrievalFailure(Exception exception) => true;
|
||||
protected virtual bool ShouldExitOnTokenRetrievalFailure(Exception exception) => true;
|
||||
|
||||
protected override async Task PrepareScoreForResultsAsync(Score score)
|
||||
{
|
||||
@ -231,7 +240,7 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
/// <summary>
|
||||
/// Construct a request to be used for retrieval of the score token.
|
||||
/// Can return null, at which point <see cref="HandleTokenRetrievalFailure"/> will be fired.
|
||||
/// Can return null, at which point <see cref="ShouldExitOnTokenRetrievalFailure"/> will be fired.
|
||||
/// </summary>
|
||||
[CanBeNull]
|
||||
protected abstract APIRequest<APIScoreToken> CreateTokenRequest();
|
||||
|
@ -150,13 +150,10 @@ namespace osu.Game.Screens.Ranking.Contracted
|
||||
},
|
||||
new Drawable[]
|
||||
{
|
||||
new Container
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Vertical = 5 },
|
||||
Child = new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
@ -189,7 +186,6 @@ namespace osu.Game.Screens.Ranking.Contracted
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
RowDimensions = new[]
|
||||
|
@ -64,7 +64,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
||||
/// <summary>
|
||||
/// Relative width of the rank circles.
|
||||
/// </summary>
|
||||
public const float RANK_CIRCLE_RADIUS = 0.06f;
|
||||
public const float RANK_CIRCLE_RADIUS = 0.05f;
|
||||
|
||||
/// <summary>
|
||||
/// Relative width of the circle showing the accuracy.
|
||||
@ -74,12 +74,12 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
||||
/// <summary>
|
||||
/// SS is displayed as a 1% region, otherwise it would be invisible.
|
||||
/// </summary>
|
||||
private const double virtual_ss_percentage = 0.01;
|
||||
public const double VIRTUAL_SS_PERCENTAGE = 0.01;
|
||||
|
||||
/// <summary>
|
||||
/// The width of a <see cref="RankNotch"/> in terms of accuracy.
|
||||
/// The width of spacing in terms of accuracy between the grade circles.
|
||||
/// </summary>
|
||||
public const double NOTCH_WIDTH_PERCENTAGE = 1.0 / 360;
|
||||
public const double GRADE_SPACING_PERCENTAGE = 2.0 / 360;
|
||||
|
||||
/// <summary>
|
||||
/// The easing for the circle filling transforms.
|
||||
@ -89,7 +89,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
||||
private readonly ScoreInfo score;
|
||||
|
||||
private CircularProgress accuracyCircle;
|
||||
private CircularProgress innerMask;
|
||||
private GradedCircles gradedCircles;
|
||||
private Container<RankBadge> badges;
|
||||
private RankText rankText;
|
||||
|
||||
@ -158,82 +158,16 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
||||
Colour = ColourInfo.GradientVertical(Color4Extensions.FromHex("#7CF6FF"), Color4Extensions.FromHex("#BAFFA9")),
|
||||
InnerRadius = accuracy_circle_radius,
|
||||
},
|
||||
new BufferedContainer
|
||||
new Container
|
||||
{
|
||||
Name = "Graded circles",
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Size = new Vector2(0.8f),
|
||||
Padding = new MarginPadding(2),
|
||||
Children = new Drawable[]
|
||||
Padding = new MarginPadding(2.5f),
|
||||
Child = gradedCircles = new GradedCircles(accuracyC, accuracyB, accuracyA, accuracyS, accuracyX)
|
||||
{
|
||||
new CircularProgress
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = OsuColour.ForRank(ScoreRank.X),
|
||||
InnerRadius = RANK_CIRCLE_RADIUS,
|
||||
Current = { Value = accuracyX }
|
||||
},
|
||||
new CircularProgress
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = OsuColour.ForRank(ScoreRank.S),
|
||||
InnerRadius = RANK_CIRCLE_RADIUS,
|
||||
Current = { Value = accuracyX - virtual_ss_percentage }
|
||||
},
|
||||
new CircularProgress
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = OsuColour.ForRank(ScoreRank.A),
|
||||
InnerRadius = RANK_CIRCLE_RADIUS,
|
||||
Current = { Value = accuracyS }
|
||||
},
|
||||
new CircularProgress
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = OsuColour.ForRank(ScoreRank.B),
|
||||
InnerRadius = RANK_CIRCLE_RADIUS,
|
||||
Current = { Value = accuracyA }
|
||||
},
|
||||
new CircularProgress
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = OsuColour.ForRank(ScoreRank.C),
|
||||
InnerRadius = RANK_CIRCLE_RADIUS,
|
||||
Current = { Value = accuracyB }
|
||||
},
|
||||
new CircularProgress
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = OsuColour.ForRank(ScoreRank.D),
|
||||
InnerRadius = RANK_CIRCLE_RADIUS,
|
||||
Current = { Value = accuracyC }
|
||||
},
|
||||
new RankNotch((float)accuracyX),
|
||||
new RankNotch((float)(accuracyX - virtual_ss_percentage)),
|
||||
new RankNotch((float)accuracyS),
|
||||
new RankNotch((float)accuracyA),
|
||||
new RankNotch((float)accuracyB),
|
||||
new RankNotch((float)accuracyC),
|
||||
new BufferedContainer
|
||||
{
|
||||
Name = "Graded circle mask",
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding(1),
|
||||
Blending = new BlendingParameters
|
||||
{
|
||||
Source = BlendingType.DstColor,
|
||||
Destination = BlendingType.OneMinusSrcColor,
|
||||
SourceAlpha = BlendingType.One,
|
||||
DestinationAlpha = BlendingType.SrcAlpha
|
||||
},
|
||||
Child = innerMask = new CircularProgress
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
InnerRadius = RANK_CIRCLE_RADIUS - 0.02f,
|
||||
}
|
||||
}
|
||||
RelativeSizeAxes = Axes.Both
|
||||
}
|
||||
},
|
||||
badges = new Container<RankBadge>
|
||||
@ -248,7 +182,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
||||
new RankBadge(accuracyB, Interpolation.Lerp(accuracyB, accuracyA, 0.5), getRank(ScoreRank.B)),
|
||||
// The S and A badges are moved down slightly to prevent collision with the SS badge.
|
||||
new RankBadge(accuracyA, Interpolation.Lerp(accuracyA, accuracyS, 0.25), getRank(ScoreRank.A)),
|
||||
new RankBadge(accuracyS, Interpolation.Lerp(accuracyS, (accuracyX - virtual_ss_percentage), 0.25), getRank(ScoreRank.S)),
|
||||
new RankBadge(accuracyS, Interpolation.Lerp(accuracyS, (accuracyX - VIRTUAL_SS_PERCENTAGE), 0.25), getRank(ScoreRank.S)),
|
||||
new RankBadge(accuracyX, accuracyX, getRank(ScoreRank.X)),
|
||||
}
|
||||
},
|
||||
@ -296,7 +230,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
||||
}
|
||||
|
||||
using (BeginDelayedSequence(RANK_CIRCLE_TRANSFORM_DELAY))
|
||||
innerMask.FillTo(1f, RANK_CIRCLE_TRANSFORM_DURATION, ACCURACY_TRANSFORM_EASING);
|
||||
gradedCircles.TransformTo(nameof(GradedCircles.Progress), 1.0, RANK_CIRCLE_TRANSFORM_DURATION, ACCURACY_TRANSFORM_EASING);
|
||||
|
||||
using (BeginDelayedSequence(ACCURACY_TRANSFORM_DELAY))
|
||||
{
|
||||
@ -313,10 +247,10 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
||||
// to prevent ambiguity on what grade it's pointing at.
|
||||
foreach (double p in notchPercentages)
|
||||
{
|
||||
if (Precision.AlmostEquals(p, targetAccuracy, NOTCH_WIDTH_PERCENTAGE / 2))
|
||||
if (Precision.AlmostEquals(p, targetAccuracy, GRADE_SPACING_PERCENTAGE / 2))
|
||||
{
|
||||
int tippingDirection = targetAccuracy - p >= 0 ? 1 : -1; // We "round up" here to match rank criteria
|
||||
targetAccuracy = p + tippingDirection * (NOTCH_WIDTH_PERCENTAGE / 2);
|
||||
targetAccuracy = p + tippingDirection * (GRADE_SPACING_PERCENTAGE / 2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -325,7 +259,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
||||
if (score.Rank == ScoreRank.X || score.Rank == ScoreRank.XH)
|
||||
targetAccuracy = 1;
|
||||
else
|
||||
targetAccuracy = Math.Min(accuracyX - virtual_ss_percentage - NOTCH_WIDTH_PERCENTAGE / 2, targetAccuracy);
|
||||
targetAccuracy = Math.Min(accuracyX - VIRTUAL_SS_PERCENTAGE - GRADE_SPACING_PERCENTAGE / 2, targetAccuracy);
|
||||
|
||||
// The accuracy circle gauge visually fills up a bit too much.
|
||||
// This wouldn't normally matter but we want it to align properly with the inner graded circle in the above cases.
|
||||
@ -365,7 +299,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
||||
continue;
|
||||
|
||||
using (BeginDelayedSequence(
|
||||
inverseEasing(ACCURACY_TRANSFORM_EASING, Math.Min(accuracyX - virtual_ss_percentage, badge.Accuracy) / targetAccuracy) * ACCURACY_TRANSFORM_DURATION))
|
||||
inverseEasing(ACCURACY_TRANSFORM_EASING, Math.Min(accuracyX - VIRTUAL_SS_PERCENTAGE, badge.Accuracy) / targetAccuracy) * ACCURACY_TRANSFORM_DURATION))
|
||||
{
|
||||
badge.Appear();
|
||||
|
||||
@ -425,7 +359,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
||||
.FadeOut(800, Easing.Out);
|
||||
|
||||
accuracyCircle
|
||||
.FillTo(accuracyS - NOTCH_WIDTH_PERCENTAGE / 2 - visual_alignment_offset, 70, Easing.OutQuint);
|
||||
.FillTo(accuracyS - GRADE_SPACING_PERCENTAGE / 2 - visual_alignment_offset, 70, Easing.OutQuint);
|
||||
|
||||
badges.Single(b => b.Rank == getRank(ScoreRank.S))
|
||||
.FadeOut(70, Easing.OutQuint);
|
||||
|
89
osu.Game/Screens/Ranking/Expanded/Accuracy/GradedCircles.cs
Normal file
89
osu.Game/Screens/Ranking/Expanded/Accuracy/GradedCircles.cs
Normal file
@ -0,0 +1,89 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Scoring;
|
||||
|
||||
namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
||||
{
|
||||
public partial class GradedCircles : CompositeDrawable
|
||||
{
|
||||
private double progress;
|
||||
|
||||
public double Progress
|
||||
{
|
||||
get => progress;
|
||||
set
|
||||
{
|
||||
progress = value;
|
||||
|
||||
foreach (var circle in circles)
|
||||
circle.RevealProgress = value;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Container<GradedCircle> circles;
|
||||
|
||||
public GradedCircles(double accuracyC, double accuracyB, double accuracyA, double accuracyS, double accuracyX)
|
||||
{
|
||||
InternalChild = circles = new Container<GradedCircle>
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new[]
|
||||
{
|
||||
new GradedCircle(0.0, accuracyC)
|
||||
{
|
||||
Colour = OsuColour.ForRank(ScoreRank.D),
|
||||
},
|
||||
new GradedCircle(accuracyC, accuracyB)
|
||||
{
|
||||
Colour = OsuColour.ForRank(ScoreRank.C),
|
||||
},
|
||||
new GradedCircle(accuracyB, accuracyA)
|
||||
{
|
||||
Colour = OsuColour.ForRank(ScoreRank.B),
|
||||
},
|
||||
new GradedCircle(accuracyA, accuracyS)
|
||||
{
|
||||
Colour = OsuColour.ForRank(ScoreRank.A),
|
||||
},
|
||||
new GradedCircle(accuracyS, accuracyX - AccuracyCircle.VIRTUAL_SS_PERCENTAGE)
|
||||
{
|
||||
Colour = OsuColour.ForRank(ScoreRank.S),
|
||||
},
|
||||
new GradedCircle(accuracyX - AccuracyCircle.VIRTUAL_SS_PERCENTAGE, 1.0)
|
||||
{
|
||||
Colour = OsuColour.ForRank(ScoreRank.X)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private partial class GradedCircle : CircularProgress
|
||||
{
|
||||
public double RevealProgress
|
||||
{
|
||||
set => Current.Value = Math.Clamp(value, startProgress, endProgress) - startProgress;
|
||||
}
|
||||
|
||||
private readonly double startProgress;
|
||||
private readonly double endProgress;
|
||||
|
||||
public GradedCircle(double startProgress, double endProgress)
|
||||
{
|
||||
this.startProgress = startProgress + AccuracyCircle.GRADE_SPACING_PERCENTAGE * 0.5;
|
||||
this.endProgress = endProgress - AccuracyCircle.GRADE_SPACING_PERCENTAGE * 0.5;
|
||||
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
InnerRadius = AccuracyCircle.RANK_CIRCLE_RADIUS;
|
||||
Rotation = (float)this.startProgress * 360;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
||||
{
|
||||
/// <summary>
|
||||
/// A solid "notch" of the <see cref="AccuracyCircle"/> that appears at the ends of the rank circles to add separation.
|
||||
/// </summary>
|
||||
public partial class RankNotch : CompositeDrawable
|
||||
{
|
||||
private readonly float position;
|
||||
|
||||
public RankNotch(float position)
|
||||
{
|
||||
this.position = position;
|
||||
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
InternalChild = new Container
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Rotation = position * 360f,
|
||||
Child = new Box
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Height = AccuracyCircle.RANK_CIRCLE_RADIUS,
|
||||
Width = (float)AccuracyCircle.NOTCH_WIDTH_PERCENTAGE * 360f,
|
||||
Colour = OsuColour.Gray(0.3f),
|
||||
EdgeSmoothness = new Vector2(1f)
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -75,15 +75,10 @@ namespace osu.Game.Screens.Select
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Colour4.Black.Opacity(0.3f),
|
||||
},
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Horizontal = spacing },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Horizontal = spacing },
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
@ -167,8 +162,6 @@ namespace osu.Game.Screens.Select
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
loading = new LoadingLayer(true)
|
||||
};
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Screens.Select
|
||||
{
|
||||
@ -405,9 +406,9 @@ namespace osu.Game.Screens.Select
|
||||
foreach (var mod in mods.Value.OfType<IApplicableToRate>())
|
||||
rate = mod.ApplyToRate(0, rate);
|
||||
|
||||
int bpmMax = (int)Math.Round(Math.Round(beatmap.ControlPointInfo.BPMMaximum) * rate);
|
||||
int bpmMin = (int)Math.Round(Math.Round(beatmap.ControlPointInfo.BPMMinimum) * rate);
|
||||
int mostCommonBPM = (int)Math.Round(Math.Round(60000 / beatmap.GetMostCommonBeatLength()) * rate);
|
||||
int bpmMax = FormatUtils.RoundBPM(beatmap.ControlPointInfo.BPMMaximum, rate);
|
||||
int bpmMin = FormatUtils.RoundBPM(beatmap.ControlPointInfo.BPMMinimum, rate);
|
||||
int mostCommonBPM = FormatUtils.RoundBPM(60000 / beatmap.GetMostCommonBeatLength(), rate);
|
||||
|
||||
string labelText = bpmMin == bpmMax
|
||||
? $"{bpmMin}"
|
||||
|
@ -7,6 +7,7 @@ using System.Linq;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Screens.Select.Filter;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Screens.Select.Carousel
|
||||
{
|
||||
@ -67,19 +68,19 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
{
|
||||
default:
|
||||
case SortMode.Artist:
|
||||
comparison = string.Compare(BeatmapSet.Metadata.Artist, otherSet.BeatmapSet.Metadata.Artist, StringComparison.OrdinalIgnoreCase);
|
||||
comparison = OrdinalSortByCaseStringComparer.DEFAULT.Compare(BeatmapSet.Metadata.Artist, otherSet.BeatmapSet.Metadata.Artist);
|
||||
break;
|
||||
|
||||
case SortMode.Title:
|
||||
comparison = string.Compare(BeatmapSet.Metadata.Title, otherSet.BeatmapSet.Metadata.Title, StringComparison.OrdinalIgnoreCase);
|
||||
comparison = OrdinalSortByCaseStringComparer.DEFAULT.Compare(BeatmapSet.Metadata.Title, otherSet.BeatmapSet.Metadata.Title);
|
||||
break;
|
||||
|
||||
case SortMode.Author:
|
||||
comparison = string.Compare(BeatmapSet.Metadata.Author.Username, otherSet.BeatmapSet.Metadata.Author.Username, StringComparison.OrdinalIgnoreCase);
|
||||
comparison = OrdinalSortByCaseStringComparer.DEFAULT.Compare(BeatmapSet.Metadata.Author.Username, otherSet.BeatmapSet.Metadata.Author.Username);
|
||||
break;
|
||||
|
||||
case SortMode.Source:
|
||||
comparison = string.Compare(BeatmapSet.Metadata.Source, otherSet.BeatmapSet.Metadata.Source, StringComparison.OrdinalIgnoreCase);
|
||||
comparison = OrdinalSortByCaseStringComparer.DEFAULT.Compare(BeatmapSet.Metadata.Source, otherSet.BeatmapSet.Metadata.Source);
|
||||
break;
|
||||
|
||||
case SortMode.DateAdded:
|
||||
|
@ -122,7 +122,7 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
{
|
||||
difficultyIcon = new DifficultyIcon(beatmapInfo)
|
||||
{
|
||||
ShowTooltip = false,
|
||||
TooltipType = DifficultyIconTooltipType.None,
|
||||
Scale = new Vector2(1.8f),
|
||||
},
|
||||
new FillFlowContainer
|
||||
|
@ -153,6 +153,8 @@ namespace osu.Game.Tests.Visual
|
||||
|
||||
public new Bindable<IReadOnlyList<Mod>> SelectedMods => base.SelectedMods;
|
||||
|
||||
public new Storage Storage => base.Storage;
|
||||
|
||||
public new SpectatorClient SpectatorClient => base.SpectatorClient;
|
||||
|
||||
// if we don't apply these changes, when running under nUnit the version that gets populated is that of nUnit.
|
||||
@ -166,7 +168,7 @@ namespace osu.Game.Tests.Visual
|
||||
public TestOsuGame(Storage storage, IAPIProvider api, string[] args = null)
|
||||
: base(args)
|
||||
{
|
||||
Storage = storage;
|
||||
base.Storage = storage;
|
||||
API = api;
|
||||
}
|
||||
|
||||
|
@ -61,7 +61,7 @@ namespace osu.Game.Tests.Visual
|
||||
PauseOnFocusLost = pauseOnFocusLost;
|
||||
}
|
||||
|
||||
protected override bool HandleTokenRetrievalFailure(Exception exception) => false;
|
||||
protected override bool ShouldExitOnTokenRetrievalFailure(Exception exception) => false;
|
||||
|
||||
protected override APIRequest<APIScoreToken> CreateTokenRequest()
|
||||
{
|
||||
|
@ -166,6 +166,9 @@ namespace osu.Game.Users
|
||||
globalRankDisplay = new ProfileValueDisplay(true)
|
||||
{
|
||||
Title = UsersStrings.ShowRankGlobalSimple,
|
||||
// TODO: implement highest rank tooltip
|
||||
// `RankHighest` resides in `APIUser`, but `api.LocalUser` doesn't update
|
||||
// maybe move to `UserStatistics` in api, so `SoloStatisticsWatcher` can update the value
|
||||
},
|
||||
countryRankDisplay = new ProfileValueDisplay(true)
|
||||
{
|
||||
|
@ -49,5 +49,15 @@ namespace osu.Game.Utils
|
||||
|
||||
return precision;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies rounding to the given BPM value.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Double-rounding is applied intentionally (see https://github.com/ppy/osu/pull/18345#issue-1243311382 for rationale).
|
||||
/// </remarks>
|
||||
/// <param name="baseBpm">The base BPM to round.</param>
|
||||
/// <param name="rate">Rate adjustment, if applicable.</param>
|
||||
public static int RoundBPM(double baseBpm, double rate = 1) => (int)Math.Round(Math.Round(baseBpm) * rate);
|
||||
}
|
||||
}
|
||||
|
49
osu.Game/Utils/OrdinalSortByCaseStringComparer.cs
Normal file
49
osu.Game/Utils/OrdinalSortByCaseStringComparer.cs
Normal file
@ -0,0 +1,49 @@
|
||||
// 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;
|
||||
|
||||
namespace osu.Game.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// This string comparer is something of a cross-over between <see cref="StringComparer.Ordinal"/> and <see cref="StringComparer.OrdinalIgnoreCase"/>.
|
||||
/// <see cref="StringComparer.OrdinalIgnoreCase"/> is used first, but <see cref="StringComparer.Ordinal"/> is used as a tie-breaker.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This comparer's behaviour somewhat emulates <see cref="StringComparer.InvariantCulture"/>,
|
||||
/// but non-ordinal comparers - both culture-aware and culture-invariant - have huge performance overheads due to i18n factors (up to 5x slower).
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// Given the following strings to sort: <c>[A, B, C, D, a, b, c, d, A]</c> and a stable sorting algorithm:
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// <see cref="StringComparer.Ordinal"/> would return <c>[A, A, B, C, D, a, b, c, d]</c>.
|
||||
/// This is undesirable as letters are interleaved.
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <see cref="StringComparer.OrdinalIgnoreCase"/> would return <c>[A, a, A, B, b, C, c, D, d]</c>.
|
||||
/// Different letters are not interleaved, but because case is ignored, the As are left in arbitrary order.
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// <item>
|
||||
/// <see cref="OrdinalSortByCaseStringComparer"/> would return <c>[a, A, A, b, B, c, C, d, D]</c>, which is the expected behaviour.
|
||||
/// </item>
|
||||
/// </example>
|
||||
public class OrdinalSortByCaseStringComparer : IComparer<string>
|
||||
{
|
||||
public static readonly OrdinalSortByCaseStringComparer DEFAULT = new OrdinalSortByCaseStringComparer();
|
||||
|
||||
private OrdinalSortByCaseStringComparer()
|
||||
{
|
||||
}
|
||||
|
||||
public int Compare(string? a, string? b)
|
||||
{
|
||||
int result = StringComparer.OrdinalIgnoreCase.Compare(a, b);
|
||||
if (result == 0)
|
||||
result = -StringComparer.Ordinal.Compare(a, b); // negative to place lowercase letters before uppercase.
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
@ -36,7 +36,7 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Realm" Version="11.5.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2024.215.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2024.221.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2024.207.0" />
|
||||
<PackageReference Include="Sentry" Version="3.41.3" />
|
||||
<!-- Held back due to 0.34.0 failing AOT compilation on ZstdSharp.dll dependency. -->
|
||||
|
@ -23,6 +23,6 @@
|
||||
<RuntimeIdentifier>iossimulator-x64</RuntimeIdentifier>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2024.215.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2024.221.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -102,6 +102,19 @@
|
||||
<string>osz</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>sh.ppy.osu.items</string>
|
||||
</array>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>sh.ppy.osu.olz</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<string>olz</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
<array>
|
||||
|
Loading…
Reference in New Issue
Block a user