mirror of
https://github.com/ppy/osu.git
synced 2025-01-13 03:13:21 +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
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install .NET 8.0.x
|
- name: Install .NET 8.0.x
|
||||||
uses: actions/setup-dotnet@v3
|
uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
dotnet-version: "8.0.x"
|
dotnet-version: "8.0.x"
|
||||||
|
|
||||||
@ -27,7 +27,7 @@ jobs:
|
|||||||
run: dotnet restore osu.Desktop.slnf
|
run: dotnet restore osu.Desktop.slnf
|
||||||
|
|
||||||
- name: Restore inspectcode cache
|
- name: Restore inspectcode cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ${{ github.workspace }}/inspectcode
|
path: ${{ github.workspace }}/inspectcode
|
||||||
key: inspectcode-${{ hashFiles('.config/dotnet-tools.json', '.github/workflows/ci.yml', 'osu.sln*', 'osu*.slnf', '.editorconfig', '.globalconfig', 'CodeAnalysis/*', '**/*.csproj', '**/*.props') }}
|
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
|
timeout-minutes: 60
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install .NET 8.0.x
|
- name: Install .NET 8.0.x
|
||||||
uses: actions/setup-dotnet@v3
|
uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
dotnet-version: "8.0.x"
|
dotnet-version: "8.0.x"
|
||||||
|
|
||||||
@ -99,16 +99,16 @@ jobs:
|
|||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup JDK 11
|
- name: Setup JDK 11
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: microsoft
|
distribution: microsoft
|
||||||
java-version: 11
|
java-version: 11
|
||||||
|
|
||||||
- name: Install .NET 8.0.x
|
- name: Install .NET 8.0.x
|
||||||
uses: actions/setup-dotnet@v3
|
uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
dotnet-version: "8.0.x"
|
dotnet-version: "8.0.x"
|
||||||
|
|
||||||
@ -126,10 +126,10 @@ jobs:
|
|||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install .NET 8.0.x
|
- name: Install .NET 8.0.x
|
||||||
uses: actions/setup-dotnet@v3
|
uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
dotnet-version: "8.0.x"
|
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 }}
|
GOOGLE_CREDS_FILE: ${{ steps.set-outputs.outputs.GOOGLE_CREDS_FILE }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout diffcalc-sheet-generator
|
- name: Checkout diffcalc-sheet-generator
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
path: ${{ env.EXECUTION_ID }}
|
path: ${{ env.EXECUTION_ID }}
|
||||||
repository: 'smoogipoo/diffcalc-sheet-generator'
|
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
|
timeout-minutes: 5
|
||||||
steps:
|
steps:
|
||||||
- name: Annotate CI run with test results
|
- name: Annotate CI run with test results
|
||||||
uses: dorny/test-reporter@v1.6.0
|
uses: dorny/test-reporter@v1.8.0
|
||||||
with:
|
with:
|
||||||
artifact: osu-test-results-${{matrix.os.prettyname}}-${{matrix.threadingMode}}
|
artifact: osu-test-results-${{matrix.os.prettyname}}-${{matrix.threadingMode}}
|
||||||
name: 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
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
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
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Install .NET 8.0.x
|
- name: Install .NET 8.0.x
|
||||||
uses: actions/setup-dotnet@v3
|
uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
dotnet-version: "8.0.x"
|
dotnet-version: "8.0.x"
|
||||||
|
|
||||||
- name: Checkout ppy/osu
|
- name: Checkout ppy/osu
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
path: osu
|
path: osu
|
||||||
|
|
||||||
- name: Checkout ppy/osu-tools
|
- name: Checkout ppy/osu-tools
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: ppy/osu-tools
|
repository: ppy/osu-tools
|
||||||
path: osu-tools
|
path: osu-tools
|
||||||
|
|
||||||
- name: Checkout ppy/osu-web
|
- name: Checkout ppy/osu-web
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: ppy/osu-web
|
repository: ppy/osu-web
|
||||||
path: osu-web
|
path: osu-web
|
||||||
@ -43,7 +43,7 @@ jobs:
|
|||||||
working-directory: ./osu-tools
|
working-directory: ./osu-tools
|
||||||
|
|
||||||
- name: Create pull request with changes
|
- name: Create pull request with changes
|
||||||
uses: peter-evans/create-pull-request@v5
|
uses: peter-evans/create-pull-request@v6
|
||||||
with:
|
with:
|
||||||
title: Update mod definitions
|
title: Update mod definitions
|
||||||
body: "This PR has been auto-generated to update the mod definitions to match ppy/osu@${{ github.ref_name }}."
|
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>
|
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.215.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.221.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
<!-- 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)
|
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
|
Type = catcher.CanCatch(hitObject) ? HitResult.Great : HitResult.Miss
|
||||||
};
|
};
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
@ -17,9 +18,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private Playfield playfield { get; set; } = null!;
|
private Playfield playfield { get; set; } = null!;
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private IScrollingInfo scrollingInfo { get; set; } = null!;
|
|
||||||
|
|
||||||
protected ScrollingHitObjectContainer HitObjectContainer => ((ManiaPlayfield)playfield).GetColumn(HitObject.Column).HitObjectContainer;
|
protected ScrollingHitObjectContainer HitObjectContainer => ((ManiaPlayfield)playfield).GetColumn(HitObject.Column).HitObjectContainer;
|
||||||
|
|
||||||
protected ManiaSelectionBlueprint(T hitObject)
|
protected ManiaSelectionBlueprint(T hitObject)
|
||||||
@ -28,14 +26,31 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
RelativeSizeAxes = Axes.None;
|
RelativeSizeAxes = Axes.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
private readonly IBindable<ScrollingDirection> directionBindable = new Bindable<ScrollingDirection>();
|
||||||
{
|
|
||||||
base.Update();
|
|
||||||
|
|
||||||
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;
|
Anchor = Origin = anchor;
|
||||||
foreach (var child in InternalChildren)
|
foreach (var child in InternalChildren)
|
||||||
child.Anchor = child.Origin = anchor;
|
child.Anchor = child.Origin = anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
Position = Parent!.ToLocalSpace(HitObjectContainer.ScreenSpacePositionAtTime(HitObject.StartTime)) - AnchorPosition;
|
Position = Parent!.ToLocalSpace(HitObjectContainer.ScreenSpacePositionAtTime(HitObject.StartTime)) - AnchorPosition;
|
||||||
Width = HitObjectContainer.DrawWidth;
|
Width = HitObjectContainer.DrawWidth;
|
||||||
|
@ -7,7 +7,6 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics.Primitives;
|
using osu.Framework.Graphics.Primitives;
|
||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
@ -149,7 +148,18 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves the total amount of columns across all stages in this playfield.
|
/// Retrieves the total amount of columns across all stages in this playfield.
|
||||||
/// </summary>
|
/// </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)
|
private Stage getStageByColumn(int column)
|
||||||
{
|
{
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
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)
|
// multipled by 2 to nullify the score multiplier. (autoplay mod selected)
|
||||||
long totalScore = scoreProcessor.TotalScore.Value * 2;
|
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);
|
addSeekStep(0);
|
||||||
|
@ -232,7 +232,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
|||||||
|
|
||||||
IList<HitObject> nestedObjects = slider.NestedHitObjects;
|
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)
|
if (lastRealTick?.StartTime > trackingEndTime)
|
||||||
{
|
{
|
||||||
|
@ -78,9 +78,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components
|
|||||||
|
|
||||||
Scale = new Vector2(hitObject.Scale);
|
Scale = new Vector2(hitObject.Scale);
|
||||||
|
|
||||||
if (hitObject is IHasComboInformation combo)
|
|
||||||
ring.BorderColour = combo.GetComboColour(skin);
|
|
||||||
|
|
||||||
double editorTime = editorClock.CurrentTime;
|
double editorTime = editorClock.CurrentTime;
|
||||||
double hitObjectTime = hitObject.StartTime;
|
double hitObjectTime = hitObject.StartTime;
|
||||||
bool hasReachedObject = editorTime >= hitObjectTime;
|
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);
|
ring.Scale = new Vector2(1 + 0.1f * ringScale);
|
||||||
content.Alpha = 0.9f * (1 - alpha);
|
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
|
else
|
||||||
content.Alpha = 0;
|
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)
|
DrawableObject.SliderBody?.ToScreenSpace(DrawableObject.SliderBody.PathEndOffset) ?? BodyPiece.ToScreenSpace(BodyPiece.PathEndLocation)
|
||||||
};
|
};
|
||||||
|
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) =>
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos)
|
||||||
BodyPiece.ReceivePositionalInputAt(screenSpacePos) || ControlPointVisualiser?.Pieces.Any(p => p.ReceivePositionalInputAt(screenSpacePos)) == true;
|
{
|
||||||
|
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);
|
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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
@ -149,8 +148,11 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
{
|
{
|
||||||
StackHeightBindable.BindValueChanged(height =>
|
StackHeightBindable.BindValueChanged(height =>
|
||||||
{
|
{
|
||||||
foreach (var nested in NestedHitObjects.OfType<OsuHitObject>())
|
foreach (var nested in NestedHitObjects)
|
||||||
nested.StackHeight = height.NewValue;
|
{
|
||||||
|
if (nested is OsuHitObject osuHitObject)
|
||||||
|
osuHitObject.StackHeight = height.NewValue;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,18 +252,25 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
|
|
||||||
protected void UpdateNestedSamples()
|
protected void UpdateNestedSamples()
|
||||||
{
|
{
|
||||||
var firstSample = Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL)
|
// TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933)
|
||||||
?? Samples.FirstOrDefault(); // 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");
|
||||||
var sampleList = new List<HitSampleInfo>();
|
|
||||||
|
|
||||||
if (firstSample != null)
|
foreach (var nested in NestedHitObjects)
|
||||||
sampleList.Add(firstSample.With("slidertick"));
|
{
|
||||||
|
switch (nested)
|
||||||
|
{
|
||||||
|
case SliderTick tick:
|
||||||
|
tick.SamplesBindable.Clear();
|
||||||
|
|
||||||
foreach (var tick in NestedHitObjects.OfType<SliderTick>())
|
if (tickSample != null)
|
||||||
tick.Samples = sampleList;
|
tick.SamplesBindable.Add(tickSample);
|
||||||
|
break;
|
||||||
|
|
||||||
foreach (var repeat in NestedHitObjects.OfType<SliderRepeat>())
|
case SliderRepeat repeat:
|
||||||
repeat.Samples = this.GetNodeSamples(repeat.RepeatIndex + 1);
|
repeat.Samples = this.GetNodeSamples(repeat.RepeatIndex + 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (HeadCircle != null)
|
if (HeadCircle != null)
|
||||||
HeadCircle.Samples = this.GetNodeSamples(0);
|
HeadCircle.Samples = this.GetNodeSamples(0);
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Framework.Lists;
|
||||||
using osu.Game.Input.Bindings;
|
using osu.Game.Input.Bindings;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.UI;
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
{
|
{
|
||||||
public partial class OsuInputManager : RulesetInputManager<OsuAction>
|
public partial class OsuInputManager : RulesetInputManager<OsuAction>
|
||||||
{
|
{
|
||||||
public IEnumerable<OsuAction> PressedActions => KeyBindingContainer.PressedActions;
|
public SlimReadOnlyListWrapper<OsuAction> PressedActions => KeyBindingContainer.PressedActions;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether gameplay input buttons should be allowed.
|
/// 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);
|
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,
|
// In triangles and argon skins, we expanded hitcircles to take up the full 128 px which are clickable,
|
||||||
// when hitcircles have 5px padding on each size. this should be removed if we update the sprite.
|
// 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);
|
Scale = new Vector2(128 / 118f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,6 @@ using osu.Framework.Graphics.Sprites;
|
|||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||||
@ -26,10 +25,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
var texture = skin.GetTexture(@"approachcircle");
|
var texture = skin.GetTexture(@"approachcircle");
|
||||||
Debug.Assert(texture != null);
|
Debug.Assert(texture != null);
|
||||||
Texture = texture.WithMaximumSize(OsuHitObject.OBJECT_DIMENSIONS * 2);
|
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()
|
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.
|
// 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 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));
|
.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))
|
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt))
|
||||||
this.FadeOut();
|
this.FadeOut();
|
||||||
|
|
||||||
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2))
|
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn))
|
||||||
this.FadeInFromZero(spinner.TimeFadeIn / 2);
|
this.FadeInFromZero(spinner.TimeFadeIn);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
|
@ -126,11 +126,11 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
|||||||
|
|
||||||
foreach (var nested in beatmap.HitObjects[0].NestedHitObjects)
|
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 });
|
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 });
|
healthProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], judgement) { Type = judgement.MaxResult });
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
@ -159,11 +159,11 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
|||||||
|
|
||||||
foreach (var nested in beatmap.HitObjects[0].NestedHitObjects)
|
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 });
|
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 });
|
healthProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], judgement) { Type = judgement.MaxResult });
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
|
@ -175,7 +175,7 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
var hitObject = new HitObject { StartTime = Time.Current };
|
var hitObject = new HitObject { StartTime = Time.Current };
|
||||||
lifetimeEntry = new HitObjectLifetimeEntry(hitObject)
|
lifetimeEntry = new HitObjectLifetimeEntry(hitObject)
|
||||||
{
|
{
|
||||||
Result = new JudgementResult(hitObject, hitObject.CreateJudgement())
|
Result = new JudgementResult(hitObject, hitObject.Judgement)
|
||||||
{
|
{
|
||||||
Type = HitResult.Great
|
Type = HitResult.Great
|
||||||
}
|
}
|
||||||
|
@ -129,10 +129,10 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
var scoreProcessor = new ScoreProcessor(new OsuRuleset());
|
var scoreProcessor = new ScoreProcessor(new OsuRuleset());
|
||||||
scoreProcessor.ApplyBeatmap(beatmap);
|
scoreProcessor.ApplyBeatmap(beatmap);
|
||||||
|
|
||||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], beatmap.HitObjects[0].CreateJudgement()) { Type = HitResult.Ok });
|
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], beatmap.HitObjects[0].Judgement) { Type = HitResult.Ok });
|
||||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[1], beatmap.HitObjects[1].CreateJudgement()) { Type = HitResult.LargeTickHit });
|
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[1], beatmap.HitObjects[1].Judgement) { Type = HitResult.LargeTickHit });
|
||||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[2], beatmap.HitObjects[2].CreateJudgement()) { Type = HitResult.SmallTickMiss });
|
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[2], beatmap.HitObjects[2].Judgement) { Type = HitResult.SmallTickMiss });
|
||||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[3], beatmap.HitObjects[3].CreateJudgement()) { Type = HitResult.SmallBonus });
|
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[3], beatmap.HitObjects[3].Judgement) { Type = HitResult.SmallBonus });
|
||||||
|
|
||||||
var score = new ScoreInfo { Ruleset = new OsuRuleset().RulesetInfo };
|
var score = new ScoreInfo { Ruleset = new OsuRuleset().RulesetInfo };
|
||||||
scoreProcessor.FailScore(score);
|
scoreProcessor.FailScore(score);
|
||||||
@ -169,8 +169,8 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
Assert.That(scoreProcessor.MinimumAccuracy.Value, Is.EqualTo(0));
|
Assert.That(scoreProcessor.MinimumAccuracy.Value, Is.EqualTo(0));
|
||||||
Assert.That(scoreProcessor.MaximumAccuracy.Value, Is.EqualTo(1));
|
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[0], beatmap.HitObjects[0].Judgement) { Type = HitResult.Ok });
|
||||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[1], beatmap.HitObjects[1].CreateJudgement()) { Type = HitResult.Great });
|
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.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));
|
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++)
|
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
|
Type = i == 2 ? minResult : hitResult
|
||||||
};
|
};
|
||||||
@ -141,7 +141,7 @@ namespace osu.Game.Tests.Rulesets.Scoring
|
|||||||
|
|
||||||
for (int i = 0; i < object_count; ++i)
|
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
|
Type = HitResult.Great
|
||||||
};
|
};
|
||||||
@ -325,11 +325,11 @@ namespace osu.Game.Tests.Rulesets.Scoring
|
|||||||
scoreProcessor = new TestScoreProcessor();
|
scoreProcessor = new TestScoreProcessor();
|
||||||
scoreProcessor.ApplyBeatmap(beatmap);
|
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.Combo.Value, Is.EqualTo(1));
|
||||||
Assert.That(scoreProcessor.Accuracy.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.Combo.Value, Is.EqualTo(0));
|
||||||
Assert.That(scoreProcessor.Accuracy.Value, Is.EqualTo(1));
|
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++)
|
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
|
Type = i == 0 ? HitResult.Miss : HitResult.Great
|
||||||
});
|
});
|
||||||
@ -441,10 +441,7 @@ namespace osu.Game.Tests.Rulesets.Scoring
|
|||||||
private readonly HitResult maxResult;
|
private readonly HitResult maxResult;
|
||||||
private readonly HitResult? minResult;
|
private readonly HitResult? minResult;
|
||||||
|
|
||||||
public override Judgement CreateJudgement()
|
public override Judgement CreateJudgement() => new TestJudgement(maxResult, minResult);
|
||||||
{
|
|
||||||
return new TestJudgement(maxResult, minResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
public TestHitObject(HitResult maxResult, HitResult? minResult = null)
|
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;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests;
|
using osu.Game.Online.API.Requests;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.Login;
|
using osu.Game.Overlays.Login;
|
||||||
|
using osu.Game.Overlays.Settings;
|
||||||
using osu.Game.Users.Drawables;
|
using osu.Game.Users.Drawables;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
@ -25,6 +27,9 @@ namespace osu.Game.Tests.Visual.Menus
|
|||||||
|
|
||||||
private LoginOverlay loginOverlay = null!;
|
private LoginOverlay loginOverlay = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OsuConfigManager configManager { get; set; } = null!;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
@ -156,5 +161,36 @@ namespace osu.Game.Tests.Visual.Menus
|
|||||||
});
|
});
|
||||||
AddAssert("login overlay is hidden", () => loginOverlay.State.Value == Visibility.Hidden);
|
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
|
#nullable disable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
@ -18,6 +19,7 @@ using osu.Framework.Testing;
|
|||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Collections;
|
using osu.Game.Collections;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Extensions;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Online.API;
|
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);
|
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]
|
[Test]
|
||||||
public void TestRetryCountIncrements()
|
public void TestRetryCountIncrements()
|
||||||
{
|
{
|
||||||
|
@ -9,6 +9,7 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
@ -301,6 +302,25 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
switchToGameplayScene();
|
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()
|
private void advanceToSongSelect()
|
||||||
{
|
{
|
||||||
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
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
|
// 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[]
|
new[]
|
||||||
{
|
{
|
||||||
"Problematic", @"My tablet doesn't work :(
|
"Code Block", @"User not found! ;_;
|
||||||
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.
|
There are a few possible reasons for this:
|
||||||
""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"")
|
They may have changed their username.
|
||||||
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.
|
The account may be temporarily unavailable due to security or abuse issues.
|
||||||
I have honestly 0 idea of whats going on at this point."
|
You may have made a typo!"
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -137,6 +137,11 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
@"top_ranks",
|
@"top_ranks",
|
||||||
@"medals"
|
@"medals"
|
||||||
},
|
},
|
||||||
|
RankHighest = new APIUser.UserRankHighest
|
||||||
|
{
|
||||||
|
Rank = 1,
|
||||||
|
UpdatedAt = DateTimeOffset.Now,
|
||||||
|
},
|
||||||
Statistics = new UserStatistics
|
Statistics = new UserStatistics
|
||||||
{
|
{
|
||||||
IsRanked = true,
|
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>();
|
var sets = new List<BeatmapSetInfo>();
|
||||||
|
|
||||||
const string zzz_string = "zzzzz";
|
const string zzz_lowercase = "zzzzz";
|
||||||
|
const string zzz_uppercase = "ZZZZZ";
|
||||||
|
|
||||||
AddStep("Populuate beatmap sets", () =>
|
AddStep("Populuate beatmap sets", () =>
|
||||||
{
|
{
|
||||||
@ -640,10 +641,16 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
var set = TestResources.CreateTestBeatmapSetInfo();
|
var set = TestResources.CreateTestBeatmapSetInfo();
|
||||||
|
|
||||||
if (i == 4)
|
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)
|
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);
|
sets.Add(set);
|
||||||
}
|
}
|
||||||
@ -652,9 +659,11 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
loadBeatmaps(sets);
|
loadBeatmaps(sets);
|
||||||
|
|
||||||
AddStep("Sort by author", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Author }, false));
|
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));
|
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>
|
/// <summary>
|
||||||
|
@ -282,7 +282,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Check that the logo is tracking the position of the facade, with an acceptable precision lenience.
|
/// Check that the logo is tracking the position of the facade, with an acceptable precision lenience.
|
||||||
/// </summary>
|
/// </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));
|
AddAssert("all columns visible", () => this.ChildrenOfType<ModColumn>().All(col => col.IsPresent));
|
||||||
|
|
||||||
AddStep("set search", () => modSelectOverlay.SearchTerm = "HD");
|
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");
|
AddStep("filter out everything", () => modSelectOverlay.SearchTerm = "Some long search term with no matches");
|
||||||
AddAssert("no columns visible", () => this.ChildrenOfType<ModColumn>().All(col => !col.IsPresent));
|
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));
|
AddAssert("all columns visible", () => this.ChildrenOfType<ModColumn>().All(col => col.IsPresent));
|
||||||
|
|
||||||
AddStep("set search", () => modSelectOverlay.SearchTerm = "fail");
|
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("hide", () => modSelectOverlay.Hide());
|
||||||
AddStep("show", () => modSelectOverlay.Show());
|
AddStep("show", () => modSelectOverlay.Show());
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using ManagedBass.Fx;
|
using ManagedBass.Fx;
|
||||||
using osu.Framework.Audio.Mixing;
|
using osu.Framework.Audio.Mixing;
|
||||||
|
using osu.Framework.Caching;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Audio.Effects
|
namespace osu.Game.Audio.Effects
|
||||||
@ -22,6 +23,8 @@ namespace osu.Game.Audio.Effects
|
|||||||
|
|
||||||
private bool isAttached;
|
private bool isAttached;
|
||||||
|
|
||||||
|
private readonly Cached filterApplication = new Cached();
|
||||||
|
|
||||||
private int cutoff;
|
private int cutoff;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -36,7 +39,7 @@ namespace osu.Game.Audio.Effects
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
cutoff = value;
|
cutoff = value;
|
||||||
updateFilter(cutoff);
|
filterApplication.Invalidate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,6 +64,17 @@ namespace osu.Game.Audio.Effects
|
|||||||
Cutoff = getInitialCutoff(type);
|
Cutoff = getInitialCutoff(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
if (!filterApplication.IsValid)
|
||||||
|
{
|
||||||
|
updateFilter(cutoff);
|
||||||
|
filterApplication.Validate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private int getInitialCutoff(BQFType type)
|
private int getInitialCutoff(BQFType type)
|
||||||
{
|
{
|
||||||
switch (type)
|
switch (type)
|
||||||
|
@ -14,6 +14,7 @@ using osu.Framework.Graphics.UserInterface;
|
|||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
@ -31,14 +32,16 @@ namespace osu.Game.Beatmaps.Drawables
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
public bool ShowTooltip { get; set; } = true;
|
public DifficultyIconTooltipType TooltipType { get; set; } = DifficultyIconTooltipType.StarRating;
|
||||||
|
|
||||||
private readonly IBeatmapInfo? beatmap;
|
private readonly IBeatmapInfo? beatmap;
|
||||||
|
|
||||||
private readonly IRulesetInfo ruleset;
|
private readonly IRulesetInfo ruleset;
|
||||||
|
|
||||||
|
private readonly Mod[]? mods;
|
||||||
|
|
||||||
private Drawable background = null!;
|
private Drawable background = null!;
|
||||||
|
|
||||||
private readonly Container iconContainer;
|
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.
|
/// Creates a new <see cref="DifficultyIcon"/>. Will use provided beatmap's <see cref="BeatmapInfo.StarRating"/> for initial value.
|
||||||
/// </summary>
|
/// </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="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>
|
/// <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(ruleset ?? beatmap.Ruleset)
|
||||||
{
|
{
|
||||||
this.beatmap = beatmap;
|
this.beatmap = beatmap;
|
||||||
|
this.mods = mods;
|
||||||
|
|
||||||
Current.Value = new StarDifficulty(beatmap.StarRating, 0);
|
Current.Value = new StarDifficulty(beatmap.StarRating, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,6 +133,24 @@ namespace osu.Game.Beatmaps.Drawables
|
|||||||
GetCustomTooltip() => new DifficultyIconTooltip();
|
GetCustomTooltip() => new DifficultyIconTooltip();
|
||||||
|
|
||||||
DifficultyIconTooltipContent IHasCustomTooltip<DifficultyIconTooltipContent>.
|
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.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -11,14 +11,25 @@ using osu.Framework.Graphics.Cursor;
|
|||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Beatmaps.Drawables
|
namespace osu.Game.Beatmaps.Drawables
|
||||||
{
|
{
|
||||||
internal partial class DifficultyIconTooltip : VisibilityContainer, ITooltip<DifficultyIconTooltipContent>
|
internal partial class DifficultyIconTooltip : VisibilityContainer, ITooltip<DifficultyIconTooltipContent>
|
||||||
{
|
{
|
||||||
private OsuSpriteText difficultyName;
|
private OsuSpriteText difficultyName = null!;
|
||||||
private StarRatingDisplay starRating;
|
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]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load(OsuColour colours)
|
||||||
@ -31,7 +42,6 @@ namespace osu.Game.Beatmaps.Drawables
|
|||||||
{
|
{
|
||||||
new Box
|
new Box
|
||||||
{
|
{
|
||||||
Alpha = 0.9f,
|
|
||||||
Colour = colours.Gray3,
|
Colour = colours.Gray3,
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
},
|
},
|
||||||
@ -49,19 +59,49 @@ namespace osu.Game.Beatmaps.Drawables
|
|||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = 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)
|
starRating = new StarRatingDisplay(default, StarRatingDisplaySize.Small)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre
|
||||||
|
},
|
||||||
|
difficultyFillFlowContainer = new FillFlowContainer
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = 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)
|
public void SetContent(DifficultyIconTooltipContent content)
|
||||||
{
|
{
|
||||||
@ -72,6 +112,45 @@ namespace osu.Game.Beatmaps.Drawables
|
|||||||
|
|
||||||
starRating.Current.BindTarget = displayedContent.Difficulty;
|
starRating.Current.BindTarget = displayedContent.Difficulty;
|
||||||
difficultyName.Text = displayedContent.BeatmapInfo.DifficultyName;
|
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;
|
public void Move(Vector2 pos) => Position = pos;
|
||||||
@ -85,11 +164,20 @@ namespace osu.Game.Beatmaps.Drawables
|
|||||||
{
|
{
|
||||||
public readonly IBeatmapInfo BeatmapInfo;
|
public readonly IBeatmapInfo BeatmapInfo;
|
||||||
public readonly IBindable<StarDifficulty> Difficulty;
|
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;
|
BeatmapInfo = beatmapInfo;
|
||||||
Difficulty = difficulty;
|
Difficulty = difficulty;
|
||||||
|
Ruleset = rulesetInfo;
|
||||||
|
Mods = mods;
|
||||||
|
TooltipType = tooltipType;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
private readonly OffsetCorrectionClock? userGlobalOffsetClock;
|
private readonly OffsetCorrectionClock? userGlobalOffsetClock;
|
||||||
private readonly OffsetCorrectionClock? platformOffsetClock;
|
private readonly OffsetCorrectionClock? platformOffsetClock;
|
||||||
private readonly OffsetCorrectionClock? userBeatmapOffsetClock;
|
private readonly FramedOffsetClock? userBeatmapOffsetClock;
|
||||||
|
|
||||||
private readonly IFrameBasedClock finalClockSource;
|
private readonly IFrameBasedClock finalClockSource;
|
||||||
|
|
||||||
@ -70,7 +70,7 @@ namespace osu.Game.Beatmaps
|
|||||||
userGlobalOffsetClock = new OffsetCorrectionClock(platformOffsetClock);
|
userGlobalOffsetClock = new OffsetCorrectionClock(platformOffsetClock);
|
||||||
|
|
||||||
// User per-beatmap offset will be applied to this final clock.
|
// User per-beatmap offset will be applied to this final clock.
|
||||||
finalClockSource = userBeatmapOffsetClock = new OffsetCorrectionClock(userGlobalOffsetClock);
|
finalClockSource = userBeatmapOffsetClock = new FramedOffsetClock(userGlobalOffsetClock);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -122,7 +122,7 @@ namespace osu.Game.Beatmaps
|
|||||||
Debug.Assert(userBeatmapOffsetClock != null);
|
Debug.Assert(userBeatmapOffsetClock != null);
|
||||||
Debug.Assert(platformOffsetClock != 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)
|
static void addCombo(HitObject hitObject, ref int combo)
|
||||||
{
|
{
|
||||||
if (hitObject.CreateJudgement().MaxResult.AffectsCombo())
|
if (hitObject.Judgement.MaxResult.AffectsCombo())
|
||||||
combo++;
|
combo++;
|
||||||
|
|
||||||
foreach (var nested in hitObject.NestedHitObjects)
|
foreach (var nested in hitObject.NestedHitObjects)
|
||||||
|
@ -9,6 +9,7 @@ using System.Linq;
|
|||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Audio.Track;
|
using osu.Framework.Audio.Track;
|
||||||
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Graphics.Rendering;
|
using osu.Framework.Graphics.Rendering;
|
||||||
using osu.Framework.Graphics.Rendering.Dummy;
|
using osu.Framework.Graphics.Rendering.Dummy;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
@ -143,8 +144,6 @@ namespace osu.Game.Beatmaps
|
|||||||
{
|
{
|
||||||
string fileStorePath = BeatmapSetInfo.GetPathForFile(BeatmapInfo.Path);
|
string fileStorePath = BeatmapSetInfo.GetPathForFile(BeatmapInfo.Path);
|
||||||
|
|
||||||
// TODO: check validity of file
|
|
||||||
|
|
||||||
var stream = GetStream(fileStorePath);
|
var stream = GetStream(fileStorePath);
|
||||||
|
|
||||||
if (stream == null)
|
if (stream == null)
|
||||||
@ -153,6 +152,12 @@ namespace osu.Game.Beatmaps
|
|||||||
return null;
|
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))
|
using (var reader = new LineBufferedReader(stream))
|
||||||
return Decoder.GetDecoder<Beatmap>(reader).Decode(reader);
|
return Decoder.GetDecoder<Beatmap>(reader).Decode(reader);
|
||||||
}
|
}
|
||||||
|
@ -77,12 +77,19 @@ namespace osu.Game.Configuration
|
|||||||
|
|
||||||
SetDefault(OsuSetting.SavePassword, false).ValueChanged += enabled =>
|
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 =>
|
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);
|
SetDefault(OsuSetting.ExternalLinkWarning, true);
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -19,7 +17,7 @@ namespace osu.Game.Graphics.Containers
|
|||||||
{
|
{
|
||||||
public Facade LogoFacade => facade;
|
public Facade LogoFacade => facade;
|
||||||
|
|
||||||
protected OsuLogo Logo { get; private set; }
|
protected OsuLogo? Logo { get; private set; }
|
||||||
|
|
||||||
private readonly InternalFacade facade = new InternalFacade();
|
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>
|
/// <remarks>Will only be correct if the logo's <see cref="Drawable.RelativePositionAxes"/> are set to Axes.Both</remarks>
|
||||||
protected Vector2 ComputeLogoTrackingPosition()
|
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,
|
return new Vector2(absolutePos.X / Logo.Parent!.RelativeToAbsoluteFactor.X,
|
||||||
absolutePos.Y / Logo.Parent!.RelativeToAbsoluteFactor.Y);
|
absolutePos.Y / Logo.Parent!.RelativeToAbsoluteFactor.Y);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void UpdateAfterChildren()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.UpdateAfterChildren();
|
||||||
|
|
||||||
if (Logo == null)
|
if (Logo == null)
|
||||||
return;
|
return;
|
||||||
|
@ -10,11 +10,11 @@ using osu.Game.Overlays;
|
|||||||
|
|
||||||
namespace osu.Game.Graphics.Containers.Markdown
|
namespace osu.Game.Graphics.Containers.Markdown
|
||||||
{
|
{
|
||||||
public partial class OsuMarkdownFencedCodeBlock : MarkdownFencedCodeBlock
|
public partial class OsuMarkdownCodeBlock : MarkdownCodeBlock
|
||||||
{
|
{
|
||||||
// TODO : change to monospace font for this component
|
// TODO : change to monospace font for this component
|
||||||
public OsuMarkdownFencedCodeBlock(FencedCodeBlock fencedCodeBlock)
|
public OsuMarkdownCodeBlock(CodeBlock codeBlock)
|
||||||
: base(fencedCodeBlock)
|
: base(codeBlock)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
@ -67,7 +67,7 @@ namespace osu.Game.Graphics.Containers.Markdown
|
|||||||
|
|
||||||
protected override MarkdownHeading CreateHeading(HeadingBlock headingBlock) => new OsuMarkdownHeading(headingBlock);
|
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();
|
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")]
|
[JsonProperty(@"previous_usernames")]
|
||||||
public string[] PreviousUsernames;
|
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")]
|
[JsonProperty(@"country_code")]
|
||||||
private string countryCodeString;
|
private string countryCodeString;
|
||||||
|
|
||||||
|
@ -4,9 +4,11 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Extensions.ListExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Lists;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
@ -23,12 +25,21 @@ namespace osu.Game.Online.Chat
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Each word part of a chat link (split for word-wrap support).
|
/// Each word part of a chat link (split for word-wrap support).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly List<Drawable> Parts;
|
public readonly SlimReadOnlyListWrapper<Drawable> Parts;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private OverlayColourProvider? overlayColourProvider { get; set; }
|
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);
|
protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new LinkHoverSounds(sampleSet, Parts);
|
||||||
|
|
||||||
@ -39,7 +50,7 @@ namespace osu.Game.Online.Chat
|
|||||||
|
|
||||||
public DrawableLinkCompiler(IEnumerable<Drawable> parts)
|
public DrawableLinkCompiler(IEnumerable<Drawable> parts)
|
||||||
{
|
{
|
||||||
Parts = parts.ToList();
|
Parts = parts.ToList().AsSlimReadOnly();
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -52,15 +63,24 @@ namespace osu.Game.Online.Chat
|
|||||||
|
|
||||||
private partial class LinkHoverSounds : HoverClickSounds
|
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)
|
: base(sampleSet)
|
||||||
{
|
{
|
||||||
this.parts = parts;
|
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>
|
/// </summary>
|
||||||
public interface IStatefulUserHubClient
|
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();
|
Task DisconnectRequested();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,6 +58,7 @@ namespace osu.Game.Online.Metadata
|
|||||||
// https://github.com/dotnet/aspnetcore/issues/15198
|
// https://github.com/dotnet/aspnetcore/issues/15198
|
||||||
connection.On<BeatmapUpdates>(nameof(IMetadataClient.BeatmapSetsUpdated), ((IMetadataClient)this).BeatmapSetsUpdated);
|
connection.On<BeatmapUpdates>(nameof(IMetadataClient.BeatmapSetsUpdated), ((IMetadataClient)this).BeatmapSetsUpdated);
|
||||||
connection.On<int, UserPresence?>(nameof(IMetadataClient.UserPresenceUpdated), ((IMetadataClient)this).UserPresenceUpdated);
|
connection.On<int, UserPresence?>(nameof(IMetadataClient.UserPresenceUpdated), ((IMetadataClient)this).UserPresenceUpdated);
|
||||||
|
connection.On(nameof(IStatefulUserHubClient.DisconnectRequested), ((IMetadataClient)this).DisconnectRequested);
|
||||||
};
|
};
|
||||||
|
|
||||||
IsConnected.BindTo(connector.IsConnected);
|
IsConnected.BindTo(connector.IsConnected);
|
||||||
@ -231,7 +232,8 @@ namespace osu.Game.Online.Metadata
|
|||||||
public override async Task DisconnectRequested()
|
public override async Task DisconnectRequested()
|
||||||
{
|
{
|
||||||
await base.DisconnectRequested().ConfigureAwait(false);
|
await base.DisconnectRequested().ConfigureAwait(false);
|
||||||
await EndWatchingUserPresence().ConfigureAwait(false);
|
if (connector != null)
|
||||||
|
await connector.Disconnect().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
|
@ -297,7 +297,7 @@ namespace osu.Game.Overlays.BeatmapSet
|
|||||||
},
|
},
|
||||||
icon = new DifficultyIcon(beatmapInfo, ruleset)
|
icon = new DifficultyIcon(beatmapInfo, ruleset)
|
||||||
{
|
{
|
||||||
ShowTooltip = false,
|
TooltipType = DifficultyIconTooltipType.None,
|
||||||
Current = { Value = new StarDifficulty(beatmapInfo.StarRating, 0) },
|
Current = { Value = new StarDifficulty(beatmapInfo.StarRating, 0) },
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
|
@ -66,13 +66,10 @@ namespace osu.Game.Overlays.Chat.ChannelList
|
|||||||
Colour = colourProvider.Background4,
|
Colour = colourProvider.Background4,
|
||||||
Alpha = 0f,
|
Alpha = 0f,
|
||||||
},
|
},
|
||||||
new Container
|
new GridContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Padding = new MarginPadding { Left = 18, Right = 10 },
|
Padding = new MarginPadding { Left = 18, Right = 10 },
|
||||||
Child = new GridContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
ColumnDimensions = new[]
|
ColumnDimensions = new[]
|
||||||
{
|
{
|
||||||
new Dimension(GridSizeMode.AutoSize),
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
@ -98,9 +95,8 @@ namespace osu.Game.Overlays.Chat.ChannelList
|
|||||||
createMentionPill(),
|
createMentionPill(),
|
||||||
close = createCloseButton(),
|
close = createCloseButton(),
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Action = () => OnRequestSelect?.Invoke(Channel);
|
Action = () => OnRequestSelect?.Invoke(Channel);
|
||||||
|
@ -18,6 +18,7 @@ using osu.Game.Graphics.Sprites;
|
|||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Utils;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Mods
|
namespace osu.Game.Overlays.Mods
|
||||||
@ -158,7 +159,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
foreach (var mod in Mods.Value.OfType<IApplicableToRate>())
|
foreach (var mod in Mods.Value.OfType<IApplicableToRate>())
|
||||||
rate = mod.ApplyToRate(0, rate);
|
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);
|
BeatmapDifficulty originalDifficulty = new BeatmapDifficulty(BeatmapInfo.Value.Difficulty);
|
||||||
|
|
||||||
@ -188,7 +189,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
{
|
{
|
||||||
protected override double RollingDuration => 250;
|
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
|
protected override OsuSpriteText CreateSpriteText() => new OsuSpriteText
|
||||||
{
|
{
|
||||||
|
@ -77,11 +77,11 @@ namespace osu.Game.Overlays.Mods
|
|||||||
/// <seealso cref="ModState.Visible"/>
|
/// <seealso cref="ModState.Visible"/>
|
||||||
public bool Visible => modState.Visible;
|
public bool Visible => modState.Visible;
|
||||||
|
|
||||||
public override IEnumerable<LocalisableString> FilterTerms => new[]
|
public override IEnumerable<LocalisableString> FilterTerms => new LocalisableString[]
|
||||||
{
|
{
|
||||||
Mod.Name,
|
Mod.Name,
|
||||||
|
Mod.Name.Replace(" ", string.Empty),
|
||||||
Mod.Acronym,
|
Mod.Acronym,
|
||||||
Mod.Description
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public override bool MatchingFilter
|
public override bool MatchingFilter
|
||||||
|
@ -143,6 +143,13 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
|||||||
scoreRankInfo.Value.RankCount = user?.Statistics?.GradesCount[scoreRankInfo.Key] ?? 0;
|
scoreRankInfo.Value.RankCount = user?.Statistics?.GradesCount[scoreRankInfo.Key] ?? 0;
|
||||||
|
|
||||||
detailGlobalRank.Content = user?.Statistics?.GlobalRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-";
|
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)"-";
|
detailCountryRank.Content = user?.Statistics?.CountryRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-";
|
||||||
|
|
||||||
rankGraph.Statistics.Value = user?.Statistics;
|
rankGraph.Statistics.Value = user?.Statistics;
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Cursor;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
@ -13,7 +14,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
|||||||
public partial class ProfileValueDisplay : CompositeDrawable
|
public partial class ProfileValueDisplay : CompositeDrawable
|
||||||
{
|
{
|
||||||
private readonly OsuSpriteText title;
|
private readonly OsuSpriteText title;
|
||||||
private readonly OsuSpriteText content;
|
private readonly ContentText content;
|
||||||
|
|
||||||
public LocalisableString Title
|
public LocalisableString Title
|
||||||
{
|
{
|
||||||
@ -25,6 +26,11 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
|||||||
set => content.Text = value;
|
set => content.Text = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LocalisableString ContentTooltipText
|
||||||
|
{
|
||||||
|
set => content.TooltipText = value;
|
||||||
|
}
|
||||||
|
|
||||||
public ProfileValueDisplay(bool big = false, int minimumWidth = 60)
|
public ProfileValueDisplay(bool big = false, int minimumWidth = 60)
|
||||||
{
|
{
|
||||||
AutoSizeAxes = Axes.Both;
|
AutoSizeAxes = Axes.Both;
|
||||||
@ -38,9 +44,9 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
|||||||
{
|
{
|
||||||
Font = OsuFont.GetFont(size: 12)
|
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
|
new Container // Add a minimum size to the FillFlowContainer
|
||||||
{
|
{
|
||||||
@ -56,5 +62,10 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
|||||||
title.Colour = colourProvider.Content1;
|
title.Colour = colourProvider.Content1;
|
||||||
content.Colour = colourProvider.Content2;
|
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.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Cursor;
|
|
||||||
using osu.Framework.Localisation;
|
|
||||||
using osu.Game.Resources.Localisation.Web;
|
using osu.Game.Resources.Localisation.Web;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Profile.Header.Components
|
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 readonly Bindable<UserProfileData?> User = new Bindable<UserProfileData?>();
|
||||||
|
|
||||||
public LocalisableString TooltipText { get; set; }
|
|
||||||
|
|
||||||
private ProfileValueDisplay info = null!;
|
private ProfileValueDisplay info = null!;
|
||||||
|
|
||||||
public TotalPlayTime()
|
public TotalPlayTime()
|
||||||
{
|
{
|
||||||
AutoSizeAxes = Axes.Both;
|
AutoSizeAxes = Axes.Both;
|
||||||
|
|
||||||
TooltipText = "0 hours";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -32,6 +26,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
|||||||
InternalChild = info = new ProfileValueDisplay(minimumWidth: 140)
|
InternalChild = info = new ProfileValueDisplay(minimumWidth: 140)
|
||||||
{
|
{
|
||||||
Title = UsersStrings.ShowStatsPlayTime,
|
Title = UsersStrings.ShowStatsPlayTime,
|
||||||
|
ContentTooltipText = "0 hours",
|
||||||
};
|
};
|
||||||
|
|
||||||
User.BindValueChanged(updateTime, true);
|
User.BindValueChanged(updateTime, true);
|
||||||
@ -40,7 +35,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
|||||||
private void updateTime(ValueChangedEvent<UserProfileData?> user)
|
private void updateTime(ValueChangedEvent<UserProfileData?> user)
|
||||||
{
|
{
|
||||||
int? playTime = user.NewValue?.User.Statistics?.PlayTime;
|
int? playTime = user.NewValue?.User.Statistics?.PlayTime;
|
||||||
TooltipText = (playTime ?? 0) / 3600 + " hours";
|
info.ContentTooltipText = (playTime ?? 0) / 3600 + " hours";
|
||||||
info.Content = formatTime(playTime);
|
info.Content = formatTime(playTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,15 +26,11 @@ namespace osu.Game.Overlays.Profile.Header
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = colourProvider.Background5,
|
Colour = colourProvider.Background5,
|
||||||
},
|
},
|
||||||
new Container
|
new GridContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
Padding = new MarginPadding { Horizontal = WaveOverlayContainer.HORIZONTAL_PADDING, Vertical = 10 },
|
Padding = new MarginPadding { Horizontal = WaveOverlayContainer.HORIZONTAL_PADDING, Vertical = 10 },
|
||||||
Child = new GridContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
RowDimensions = new[]
|
RowDimensions = new[]
|
||||||
{
|
{
|
||||||
new Dimension(GridSizeMode.AutoSize),
|
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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Configuration;
|
using osu.Framework.Configuration;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
@ -28,15 +27,16 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
|||||||
var renderer = config.GetBindable<RendererType>(FrameworkSetting.Renderer);
|
var renderer = config.GetBindable<RendererType>(FrameworkSetting.Renderer);
|
||||||
automaticRendererInUse = renderer.Value == RendererType.Automatic;
|
automaticRendererInUse = renderer.Value == RendererType.Automatic;
|
||||||
|
|
||||||
SettingsEnumDropdown<RendererType> rendererDropdown;
|
|
||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
rendererDropdown = new RendererSettingsDropdown
|
new RendererSettingsDropdown
|
||||||
{
|
{
|
||||||
LabelText = GraphicsSettingsStrings.Renderer,
|
LabelText = GraphicsSettingsStrings.Renderer,
|
||||||
Current = 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" },
|
Keywords = new[] { @"compatibility", @"directx" },
|
||||||
},
|
},
|
||||||
// TODO: this needs to be a custom dropdown at some point
|
// 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>
|
private partial class RendererSettingsDropdown : SettingsEnumDropdown<RendererType>
|
||||||
|
@ -10,9 +10,11 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Primitives;
|
using osu.Framework.Graphics.Primitives;
|
||||||
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
@ -66,6 +68,7 @@ namespace osu.Game.Overlays.SkinEditor
|
|||||||
private IBindable<WorkingBeatmap> beatmap { get; set; } = null!;
|
private IBindable<WorkingBeatmap> beatmap { get; set; } = null!;
|
||||||
|
|
||||||
private OsuScreen? lastTargetScreen;
|
private OsuScreen? lastTargetScreen;
|
||||||
|
private InvokeOnDisposal? nestedInputManagerDisable;
|
||||||
|
|
||||||
private Vector2 lastDrawSize;
|
private Vector2 lastDrawSize;
|
||||||
|
|
||||||
@ -100,12 +103,14 @@ namespace osu.Game.Overlays.SkinEditor
|
|||||||
{
|
{
|
||||||
globallyDisableBeatmapSkinSetting();
|
globallyDisableBeatmapSkinSetting();
|
||||||
|
|
||||||
|
if (skinEditor != null)
|
||||||
|
{
|
||||||
|
disableNestedInputManagers();
|
||||||
|
skinEditor.Show();
|
||||||
|
|
||||||
if (lastTargetScreen is MainMenu)
|
if (lastTargetScreen is MainMenu)
|
||||||
PresentGameplay();
|
PresentGameplay();
|
||||||
|
|
||||||
if (skinEditor != null)
|
|
||||||
{
|
|
||||||
skinEditor.Show();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,6 +127,9 @@ namespace osu.Game.Overlays.SkinEditor
|
|||||||
|
|
||||||
AddInternal(editor);
|
AddInternal(editor);
|
||||||
|
|
||||||
|
if (lastTargetScreen is MainMenu)
|
||||||
|
PresentGameplay();
|
||||||
|
|
||||||
Debug.Assert(lastTargetScreen != null);
|
Debug.Assert(lastTargetScreen != null);
|
||||||
|
|
||||||
SetTarget(lastTargetScreen);
|
SetTarget(lastTargetScreen);
|
||||||
@ -132,6 +140,8 @@ namespace osu.Game.Overlays.SkinEditor
|
|||||||
{
|
{
|
||||||
skinEditor?.Save(false);
|
skinEditor?.Save(false);
|
||||||
skinEditor?.Hide();
|
skinEditor?.Hide();
|
||||||
|
nestedInputManagerDisable?.Dispose();
|
||||||
|
nestedInputManagerDisable = null;
|
||||||
|
|
||||||
globallyReenableBeatmapSkinSetting();
|
globallyReenableBeatmapSkinSetting();
|
||||||
}
|
}
|
||||||
@ -243,6 +253,9 @@ namespace osu.Game.Overlays.SkinEditor
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void SetTarget(OsuScreen screen)
|
public void SetTarget(OsuScreen screen)
|
||||||
{
|
{
|
||||||
|
nestedInputManagerDisable?.Dispose();
|
||||||
|
nestedInputManagerDisable = null;
|
||||||
|
|
||||||
lastTargetScreen = screen;
|
lastTargetScreen = screen;
|
||||||
|
|
||||||
if (skinEditor == null) return;
|
if (skinEditor == null) return;
|
||||||
@ -261,7 +274,7 @@ namespace osu.Game.Overlays.SkinEditor
|
|||||||
|
|
||||||
Debug.Assert(skinEditor != null);
|
Debug.Assert(skinEditor != null);
|
||||||
|
|
||||||
if (!target.IsLoaded)
|
if (!target.IsLoaded || !skinEditor.IsLoaded)
|
||||||
{
|
{
|
||||||
Scheduler.AddOnce(setTarget, target);
|
Scheduler.AddOnce(setTarget, target);
|
||||||
return;
|
return;
|
||||||
@ -271,6 +284,7 @@ namespace osu.Game.Overlays.SkinEditor
|
|||||||
{
|
{
|
||||||
skinEditor.Save(false);
|
skinEditor.Save(false);
|
||||||
skinEditor.UpdateTargetScreen(target);
|
skinEditor.UpdateTargetScreen(target);
|
||||||
|
disableNestedInputManagers();
|
||||||
}
|
}
|
||||||
else
|
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 readonly Bindable<bool> beatmapSkins = new Bindable<bool>();
|
||||||
private LeasedBindable<bool>? leasedBeatmapSkins;
|
private LeasedBindable<bool>? leasedBeatmapSkins;
|
||||||
|
|
||||||
@ -325,7 +354,7 @@ namespace osu.Game.Overlays.SkinEditor
|
|||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
if (!LoadedBeatmapSuccessfully)
|
if (!LoadedBeatmapSuccessfully)
|
||||||
Scheduler.AddDelayed(this.Exit, 3000);
|
Scheduler.AddDelayed(this.Exit, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
|
@ -113,9 +113,9 @@ namespace osu.Game.Rulesets.Difficulty
|
|||||||
private IEnumerable<HitResult> getPerfectHitResults(HitObject hitObject)
|
private IEnumerable<HitResult> getPerfectHitResults(HitObject hitObject)
|
||||||
{
|
{
|
||||||
foreach (HitObject nested in hitObject.NestedHitObjects)
|
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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
@ -124,12 +123,34 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
|
|
||||||
private (HitObject before, HitObject after)? getObjectsOnEitherSideOfCurrentTime()
|
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)
|
if (lastBefore == null)
|
||||||
return 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)
|
if (firstAfter == null)
|
||||||
return null;
|
return null;
|
||||||
|
@ -770,7 +770,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
private void ensureEntryHasResult()
|
private void ensureEntryHasResult()
|
||||||
{
|
{
|
||||||
Debug.Assert(Entry != null);
|
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)}.");
|
?? 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);
|
protected void AddNested(HitObject hitObject) => nestedHitObjects.Add(hitObject);
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </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]
|
[NotNull]
|
||||||
public virtual Judgement CreateJudgement() => new Judgement();
|
public virtual Judgement CreateJudgement() => new Judgement();
|
||||||
|
|
||||||
|
@ -61,16 +61,17 @@ namespace osu.Game.Rulesets.Objects
|
|||||||
case NotifyCollectionChangedAction.Add:
|
case NotifyCollectionChangedAction.Add:
|
||||||
Debug.Assert(args.NewItems != null);
|
Debug.Assert(args.NewItems != null);
|
||||||
|
|
||||||
foreach (var c in args.NewItems.Cast<PathControlPoint>())
|
foreach (object? newItem in args.NewItems)
|
||||||
c.Changed += invalidate;
|
((PathControlPoint)newItem).Changed += invalidate;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case NotifyCollectionChangedAction.Reset:
|
case NotifyCollectionChangedAction.Reset:
|
||||||
case NotifyCollectionChangedAction.Remove:
|
case NotifyCollectionChangedAction.Remove:
|
||||||
Debug.Assert(args.OldItems != null);
|
Debug.Assert(args.OldItems != null);
|
||||||
|
|
||||||
foreach (var c in args.OldItems.Cast<PathControlPoint>())
|
foreach (object? oldItem in args.OldItems)
|
||||||
c.Changed -= invalidate;
|
((PathControlPoint)oldItem).Changed -= invalidate;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -269,10 +270,10 @@ namespace osu.Game.Rulesets.Objects
|
|||||||
{
|
{
|
||||||
List<Vector2> subPath = calculateSubPath(segmentVertices, segmentType);
|
List<Vector2> subPath = calculateSubPath(segmentVertices, segmentType);
|
||||||
// Skip the first vertex if it is the same as the last vertex from the previous segment
|
// 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))
|
for (int j = skipFirst ? 1 : 0; j < subPath.Count; j++)
|
||||||
calculatedPath.Add(t);
|
calculatedPath.Add(subPath[j]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i > 0)
|
if (i > 0)
|
||||||
|
@ -149,7 +149,7 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
|
|
||||||
foreach (var obj in EnumerateHitObjects(beatmap))
|
foreach (var obj in EnumerateHitObjects(beatmap))
|
||||||
{
|
{
|
||||||
var judgement = obj.CreateJudgement();
|
var judgement = obj.Judgement;
|
||||||
|
|
||||||
var result = CreateResult(obj, judgement);
|
var result = CreateResult(obj, judgement);
|
||||||
if (result == null)
|
if (result == null)
|
||||||
|
@ -141,7 +141,7 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
|
|
||||||
void increaseHp(HitObject hitObject)
|
void increaseHp(HitObject hitObject)
|
||||||
{
|
{
|
||||||
double amount = GetHealthIncreaseFor(hitObject, hitObject.CreateJudgement().MaxResult);
|
double amount = GetHealthIncreaseFor(hitObject, hitObject.Judgement.MaxResult);
|
||||||
currentHpUncapped += amount;
|
currentHpUncapped += amount;
|
||||||
currentHp = Math.Max(0, Math.Min(1, currentHp + amount));
|
currentHp = Math.Max(0, Math.Min(1, currentHp + amount));
|
||||||
}
|
}
|
||||||
|
@ -47,10 +47,7 @@ namespace osu.Game.Screens.Edit
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = colourProvider.Background4,
|
Colour = colourProvider.Background4,
|
||||||
},
|
},
|
||||||
new Container
|
new GridContainer
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Child = new GridContainer
|
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
ColumnDimensions = new[]
|
ColumnDimensions = new[]
|
||||||
@ -75,7 +72,6 @@ namespace osu.Game.Screens.Edit
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -97,11 +97,14 @@ namespace osu.Game.Screens.Edit.Components
|
|||||||
editorClock.Start();
|
editorClock.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static readonly IconUsage play_icon = FontAwesome.Regular.PlayCircle;
|
||||||
|
private static readonly IconUsage pause_icon = FontAwesome.Regular.PauseCircle;
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.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>
|
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()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
|
if (lastTime != editorClock.CurrentTime)
|
||||||
|
{
|
||||||
|
lastTime = editorClock.CurrentTime;
|
||||||
trackTimer.Text = editorClock.CurrentTime.ToEditorFormattedString();
|
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,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = colourProvider.Background3
|
Colour = colourProvider.Background3
|
||||||
},
|
},
|
||||||
new Container
|
new GridContainer
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Child = new GridContainer
|
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Content = new[]
|
Content = new[]
|
||||||
@ -118,19 +115,10 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
new Drawable[]
|
new Drawable[]
|
||||||
{
|
{
|
||||||
new Container
|
new GridContainer
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
new Container
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Child = new GridContainer
|
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Content = new[]
|
Content = new[]
|
||||||
@ -157,9 +145,6 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
new Dimension(GridSizeMode.Absolute, 20)
|
new Dimension(GridSizeMode.Absolute, 20)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
new Drawable[]
|
new Drawable[]
|
||||||
{
|
{
|
||||||
|
@ -116,6 +116,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
updateStacking();
|
updateStacking();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private readonly Stack<HitObject> currentConcurrentObjects = new Stack<HitObject>();
|
||||||
|
|
||||||
private void updateStacking()
|
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.
|
// 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.
|
// after the stack gets this tall, we can presume there is space underneath to draw subsequent blueprints.
|
||||||
const int stack_reset_count = 3;
|
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.
|
// remove objects from the stack as long as their end time is in the past.
|
||||||
while (currentConcurrentObjects.TryPeek(out HitObject hitObject))
|
while (currentConcurrentObjects.TryPeek(out HitObject hitObject))
|
||||||
{
|
{
|
||||||
|
@ -57,16 +57,12 @@ namespace osu.Game.Screens.Edit
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = colourProvider.Background4
|
Colour = colourProvider.Background4
|
||||||
},
|
},
|
||||||
new Container
|
new GridContainer
|
||||||
{
|
{
|
||||||
Name = "Timeline content",
|
Name = "Timeline content",
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
Padding = new MarginPadding { Horizontal = PADDING, Top = PADDING },
|
Padding = new MarginPadding { Horizontal = PADDING, Top = PADDING },
|
||||||
Child = new GridContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
Content = new[]
|
Content = new[]
|
||||||
{
|
{
|
||||||
new Drawable[]
|
new Drawable[]
|
||||||
@ -87,7 +83,6 @@ namespace osu.Game.Screens.Edit
|
|||||||
new Dimension(),
|
new Dimension(),
|
||||||
new Dimension(GridSizeMode.Absolute, 90),
|
new Dimension(GridSizeMode.Absolute, 90),
|
||||||
}
|
}
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -64,16 +64,11 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
Content = new[]
|
Content = new[]
|
||||||
{
|
{
|
||||||
new Drawable[]
|
new Drawable[]
|
||||||
{
|
|
||||||
new Container
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Padding = new MarginPadding(padding),
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
{
|
||||||
new GridContainer
|
new GridContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Padding = new MarginPadding(padding),
|
||||||
ColumnDimensions = new[]
|
ColumnDimensions = new[]
|
||||||
{
|
{
|
||||||
new Dimension(GridSizeMode.AutoSize),
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
@ -92,8 +87,6 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
new Drawable[]
|
new Drawable[]
|
||||||
{
|
{
|
||||||
|
@ -34,9 +34,6 @@ namespace osu.Game.Screens.Edit.Verify
|
|||||||
InterpretedDifficulty.Default = StarDifficulty.GetDifficultyRating(EditorBeatmap.BeatmapInfo.StarRating);
|
InterpretedDifficulty.Default = StarDifficulty.GetDifficultyRating(EditorBeatmap.BeatmapInfo.StarRating);
|
||||||
InterpretedDifficulty.SetDefault();
|
InterpretedDifficulty.SetDefault();
|
||||||
|
|
||||||
Child = new Container
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Child = new GridContainer
|
Child = new GridContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
@ -53,7 +50,6 @@ namespace osu.Game.Screens.Edit.Verify
|
|||||||
new IssueSettings(),
|
new IssueSettings(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ using System.Collections.Generic;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Game.IO;
|
using osu.Game.IO;
|
||||||
|
using osu.Game.Localisation;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.Dialog;
|
using osu.Game.Overlays.Dialog;
|
||||||
|
|
||||||
@ -17,7 +18,7 @@ namespace osu.Game.Screens.Menu
|
|||||||
|
|
||||||
public StorageErrorDialog(OsuStorage storage, OsuStorageError error)
|
public StorageErrorDialog(OsuStorage storage, OsuStorageError error)
|
||||||
{
|
{
|
||||||
HeaderText = "osu! storage error";
|
HeaderText = StorageErrorDialogStrings.StorageError;
|
||||||
Icon = FontAwesome.Solid.ExclamationTriangle;
|
Icon = FontAwesome.Solid.ExclamationTriangle;
|
||||||
|
|
||||||
var buttons = new List<PopupDialogButton>();
|
var buttons = new List<PopupDialogButton>();
|
||||||
@ -25,13 +26,13 @@ namespace osu.Game.Screens.Menu
|
|||||||
switch (error)
|
switch (error)
|
||||||
{
|
{
|
||||||
case OsuStorageError.NotAccessible:
|
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[]
|
buttons.AddRange(new PopupDialogButton[]
|
||||||
{
|
{
|
||||||
new PopupDialogCancelButton
|
new PopupDialogCancelButton
|
||||||
{
|
{
|
||||||
Text = "Try again",
|
Text = StorageErrorDialogStrings.TryAgain,
|
||||||
Action = () =>
|
Action = () =>
|
||||||
{
|
{
|
||||||
if (!storage.TryChangeToCustomStorage(out var nextError))
|
if (!storage.TryChangeToCustomStorage(out var nextError))
|
||||||
@ -40,29 +41,29 @@ namespace osu.Game.Screens.Menu
|
|||||||
},
|
},
|
||||||
new PopupDialogCancelButton
|
new PopupDialogCancelButton
|
||||||
{
|
{
|
||||||
Text = "Use default location until restart",
|
Text = StorageErrorDialogStrings.UseDefaultLocation,
|
||||||
},
|
},
|
||||||
new PopupDialogOkButton
|
new PopupDialogOkButton
|
||||||
{
|
{
|
||||||
Text = "Reset to default location",
|
Text = StorageErrorDialogStrings.ResetToDefaultLocation,
|
||||||
Action = storage.ResetCustomStoragePath
|
Action = storage.ResetCustomStoragePath
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OsuStorageError.AccessibleButEmpty:
|
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.
|
// Todo: Provide the option to search for the files similar to migration.
|
||||||
buttons.AddRange(new PopupDialogButton[]
|
buttons.AddRange(new PopupDialogButton[]
|
||||||
{
|
{
|
||||||
new PopupDialogCancelButton
|
new PopupDialogCancelButton
|
||||||
{
|
{
|
||||||
Text = "Start fresh at specified location"
|
Text = StorageErrorDialogStrings.StartFresh
|
||||||
},
|
},
|
||||||
new PopupDialogOkButton
|
new PopupDialogOkButton
|
||||||
{
|
{
|
||||||
Text = "Reset to default location",
|
Text = StorageErrorDialogStrings.ResetToDefaultLocation,
|
||||||
Action = storage.ResetCustomStoragePath
|
Action = storage.ResetCustomStoragePath
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -26,18 +26,15 @@ namespace osu.Game.Screens.OnlinePlay.Components
|
|||||||
[Resolved(typeof(Room))]
|
[Resolved(typeof(Room))]
|
||||||
protected BindableList<PlaylistItem> Playlist { get; private set; }
|
protected BindableList<PlaylistItem> Playlist { get; private set; }
|
||||||
|
|
||||||
private readonly Drawable playlistArea;
|
private readonly GridContainer playlistArea;
|
||||||
private readonly DrawableRoomPlaylist playlist;
|
private readonly DrawableRoomPlaylist playlist;
|
||||||
|
|
||||||
public MatchBeatmapDetailArea()
|
public MatchBeatmapDetailArea()
|
||||||
{
|
{
|
||||||
Add(playlistArea = new Container
|
Add(playlistArea = new GridContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Padding = new MarginPadding { Vertical = 10 },
|
Padding = new MarginPadding { Vertical = 10 },
|
||||||
Child = new GridContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Content = new[]
|
Content = new[]
|
||||||
{
|
{
|
||||||
new Drawable[]
|
new Drawable[]
|
||||||
@ -68,7 +65,6 @@ namespace osu.Game.Screens.OnlinePlay.Components
|
|||||||
new Dimension(),
|
new Dimension(),
|
||||||
new Dimension(GridSizeMode.Absolute, 50),
|
new Dimension(GridSizeMode.Absolute, 50),
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -281,7 +281,13 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (beatmap != null)
|
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
|
else
|
||||||
difficultyIconContainer.Clear();
|
difficultyIconContainer.Clear();
|
||||||
|
|
||||||
|
@ -40,15 +40,12 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
|||||||
Colour = Color4.Black,
|
Colour = Color4.Black,
|
||||||
Alpha = 0.5f
|
Alpha = 0.5f
|
||||||
},
|
},
|
||||||
new Container
|
new GridContainer
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
AutoSizeAxes = Axes.Both,
|
AutoSizeAxes = Axes.Both,
|
||||||
Padding = new MarginPadding { Horizontal = padding },
|
Padding = new MarginPadding { Horizontal = padding },
|
||||||
Child = new GridContainer
|
|
||||||
{
|
|
||||||
AutoSizeAxes = Axes.Both,
|
|
||||||
ColumnDimensions = new[]
|
ColumnDimensions = new[]
|
||||||
{
|
{
|
||||||
new Dimension(GridSizeMode.AutoSize, minSize: 80 - 2 * padding)
|
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[]
|
new Drawable[]
|
||||||
{
|
{
|
||||||
// Playlist items column
|
// Playlist items column
|
||||||
new Container
|
new GridContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Padding = new MarginPadding { Right = 5 },
|
Padding = new MarginPadding { Right = 5 },
|
||||||
Child = new GridContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Content = new[]
|
Content = new[]
|
||||||
{
|
{
|
||||||
new Drawable[] { new OverlinedPlaylistHeader(), },
|
new Drawable[] { new OverlinedPlaylistHeader(), },
|
||||||
@ -127,7 +124,6 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
|||||||
new Dimension(GridSizeMode.AutoSize),
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
new Dimension(),
|
new Dimension(),
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
// Spacer
|
// Spacer
|
||||||
null,
|
null,
|
||||||
|
@ -545,7 +545,7 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
if (playable.HitObjects.Count == 0)
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,7 @@ namespace osu.Game.Screens.Play
|
|||||||
Scores = { BindTarget = LeaderboardScores }
|
Scores = { BindTarget = LeaderboardScores }
|
||||||
};
|
};
|
||||||
|
|
||||||
protected override bool HandleTokenRetrievalFailure(Exception exception) => false;
|
protected override bool ShouldExitOnTokenRetrievalFailure(Exception exception) => false;
|
||||||
|
|
||||||
protected override Task ImportScore(Score score)
|
protected override Task ImportScore(Score score)
|
||||||
{
|
{
|
||||||
|
@ -118,7 +118,7 @@ namespace osu.Game.Screens.Play
|
|||||||
token = r.ID;
|
token = r.ID;
|
||||||
tcs.SetResult(true);
|
tcs.SetResult(true);
|
||||||
};
|
};
|
||||||
req.Failure += handleTokenFailure;
|
req.Failure += ex => handleTokenFailure(ex, displayNotification: true);
|
||||||
|
|
||||||
api.Queue(req);
|
api.Queue(req);
|
||||||
|
|
||||||
@ -128,40 +128,49 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
void handleTokenFailure(Exception exception)
|
void handleTokenFailure(Exception exception, bool displayNotification = false)
|
||||||
{
|
{
|
||||||
tcs.SetResult(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))
|
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
|
else
|
||||||
{
|
{
|
||||||
switch (exception.Message)
|
switch (exception.Message)
|
||||||
{
|
{
|
||||||
case "expired token":
|
case @"missing token header":
|
||||||
Logger.Log("Score submission failed because your system clock is set incorrectly. Please check your system time, date and timezone.", level: LogLevel.Important);
|
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;
|
break;
|
||||||
|
|
||||||
default:
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldExit)
|
||||||
|
{
|
||||||
Schedule(() =>
|
Schedule(() =>
|
||||||
{
|
{
|
||||||
ValidForResume = false;
|
ValidForResume = false;
|
||||||
this.Exit();
|
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>
|
/// </summary>
|
||||||
/// <param name="exception">The error causing the failure.</param>
|
/// <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>
|
/// <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)
|
protected override async Task PrepareScoreForResultsAsync(Score score)
|
||||||
{
|
{
|
||||||
@ -231,7 +240,7 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Construct a request to be used for retrieval of the score token.
|
/// 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>
|
/// </summary>
|
||||||
[CanBeNull]
|
[CanBeNull]
|
||||||
protected abstract APIRequest<APIScoreToken> CreateTokenRequest();
|
protected abstract APIRequest<APIScoreToken> CreateTokenRequest();
|
||||||
|
@ -150,13 +150,10 @@ namespace osu.Game.Screens.Ranking.Contracted
|
|||||||
},
|
},
|
||||||
new Drawable[]
|
new Drawable[]
|
||||||
{
|
{
|
||||||
new Container
|
new GridContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Padding = new MarginPadding { Vertical = 5 },
|
Padding = new MarginPadding { Vertical = 5 },
|
||||||
Child = new GridContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Content = new[]
|
Content = new[]
|
||||||
{
|
{
|
||||||
new Drawable[]
|
new Drawable[]
|
||||||
@ -189,7 +186,6 @@ namespace osu.Game.Screens.Ranking.Contracted
|
|||||||
new Dimension(GridSizeMode.AutoSize),
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
RowDimensions = new[]
|
RowDimensions = new[]
|
||||||
|
@ -64,7 +64,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Relative width of the rank circles.
|
/// Relative width of the rank circles.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const float RANK_CIRCLE_RADIUS = 0.06f;
|
public const float RANK_CIRCLE_RADIUS = 0.05f;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Relative width of the circle showing the accuracy.
|
/// Relative width of the circle showing the accuracy.
|
||||||
@ -74,12 +74,12 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// SS is displayed as a 1% region, otherwise it would be invisible.
|
/// SS is displayed as a 1% region, otherwise it would be invisible.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private const double virtual_ss_percentage = 0.01;
|
public const double VIRTUAL_SS_PERCENTAGE = 0.01;
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
public const double NOTCH_WIDTH_PERCENTAGE = 1.0 / 360;
|
public const double GRADE_SPACING_PERCENTAGE = 2.0 / 360;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The easing for the circle filling transforms.
|
/// The easing for the circle filling transforms.
|
||||||
@ -89,7 +89,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
|||||||
private readonly ScoreInfo score;
|
private readonly ScoreInfo score;
|
||||||
|
|
||||||
private CircularProgress accuracyCircle;
|
private CircularProgress accuracyCircle;
|
||||||
private CircularProgress innerMask;
|
private GradedCircles gradedCircles;
|
||||||
private Container<RankBadge> badges;
|
private Container<RankBadge> badges;
|
||||||
private RankText rankText;
|
private RankText rankText;
|
||||||
|
|
||||||
@ -158,82 +158,16 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
|||||||
Colour = ColourInfo.GradientVertical(Color4Extensions.FromHex("#7CF6FF"), Color4Extensions.FromHex("#BAFFA9")),
|
Colour = ColourInfo.GradientVertical(Color4Extensions.FromHex("#7CF6FF"), Color4Extensions.FromHex("#BAFFA9")),
|
||||||
InnerRadius = accuracy_circle_radius,
|
InnerRadius = accuracy_circle_radius,
|
||||||
},
|
},
|
||||||
new BufferedContainer
|
new Container
|
||||||
{
|
{
|
||||||
Name = "Graded circles",
|
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Size = new Vector2(0.8f),
|
Size = new Vector2(0.8f),
|
||||||
Padding = new MarginPadding(2),
|
Padding = new MarginPadding(2.5f),
|
||||||
Children = new Drawable[]
|
Child = gradedCircles = new GradedCircles(accuracyC, accuracyB, accuracyA, accuracyS, accuracyX)
|
||||||
{
|
{
|
||||||
new CircularProgress
|
RelativeSizeAxes = Axes.Both
|
||||||
{
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
badges = new Container<RankBadge>
|
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)),
|
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.
|
// 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(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)),
|
new RankBadge(accuracyX, accuracyX, getRank(ScoreRank.X)),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -296,7 +230,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
|||||||
}
|
}
|
||||||
|
|
||||||
using (BeginDelayedSequence(RANK_CIRCLE_TRANSFORM_DELAY))
|
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))
|
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.
|
// to prevent ambiguity on what grade it's pointing at.
|
||||||
foreach (double p in notchPercentages)
|
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
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -325,7 +259,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
|||||||
if (score.Rank == ScoreRank.X || score.Rank == ScoreRank.XH)
|
if (score.Rank == ScoreRank.X || score.Rank == ScoreRank.XH)
|
||||||
targetAccuracy = 1;
|
targetAccuracy = 1;
|
||||||
else
|
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.
|
// 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.
|
// 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;
|
continue;
|
||||||
|
|
||||||
using (BeginDelayedSequence(
|
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();
|
badge.Appear();
|
||||||
|
|
||||||
@ -425,7 +359,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
|||||||
.FadeOut(800, Easing.Out);
|
.FadeOut(800, Easing.Out);
|
||||||
|
|
||||||
accuracyCircle
|
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))
|
badges.Single(b => b.Rank == getRank(ScoreRank.S))
|
||||||
.FadeOut(70, Easing.OutQuint);
|
.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,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = Colour4.Black.Opacity(0.3f),
|
Colour = Colour4.Black.Opacity(0.3f),
|
||||||
},
|
},
|
||||||
new Container
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Padding = new MarginPadding { Horizontal = spacing },
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
new GridContainer
|
new GridContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Padding = new MarginPadding { Horizontal = spacing },
|
||||||
RowDimensions = new[]
|
RowDimensions = new[]
|
||||||
{
|
{
|
||||||
new Dimension(GridSizeMode.AutoSize),
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
@ -167,8 +162,6 @@ namespace osu.Game.Screens.Select
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
|
||||||
},
|
|
||||||
loading = new LoadingLayer(true)
|
loading = new LoadingLayer(true)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@ using osu.Game.Rulesets.Mods;
|
|||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Resources.Localisation.Web;
|
using osu.Game.Resources.Localisation.Web;
|
||||||
|
using osu.Game.Utils;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Select
|
namespace osu.Game.Screens.Select
|
||||||
{
|
{
|
||||||
@ -405,9 +406,9 @@ namespace osu.Game.Screens.Select
|
|||||||
foreach (var mod in mods.Value.OfType<IApplicableToRate>())
|
foreach (var mod in mods.Value.OfType<IApplicableToRate>())
|
||||||
rate = mod.ApplyToRate(0, rate);
|
rate = mod.ApplyToRate(0, rate);
|
||||||
|
|
||||||
int bpmMax = (int)Math.Round(Math.Round(beatmap.ControlPointInfo.BPMMaximum) * rate);
|
int bpmMax = FormatUtils.RoundBPM(beatmap.ControlPointInfo.BPMMaximum, rate);
|
||||||
int bpmMin = (int)Math.Round(Math.Round(beatmap.ControlPointInfo.BPMMinimum) * rate);
|
int bpmMin = FormatUtils.RoundBPM(beatmap.ControlPointInfo.BPMMinimum, rate);
|
||||||
int mostCommonBPM = (int)Math.Round(Math.Round(60000 / beatmap.GetMostCommonBeatLength()) * rate);
|
int mostCommonBPM = FormatUtils.RoundBPM(60000 / beatmap.GetMostCommonBeatLength(), rate);
|
||||||
|
|
||||||
string labelText = bpmMin == bpmMax
|
string labelText = bpmMin == bpmMax
|
||||||
? $"{bpmMin}"
|
? $"{bpmMin}"
|
||||||
|
@ -7,6 +7,7 @@ using System.Linq;
|
|||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Screens.Select.Filter;
|
using osu.Game.Screens.Select.Filter;
|
||||||
|
using osu.Game.Utils;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Select.Carousel
|
namespace osu.Game.Screens.Select.Carousel
|
||||||
{
|
{
|
||||||
@ -67,19 +68,19 @@ namespace osu.Game.Screens.Select.Carousel
|
|||||||
{
|
{
|
||||||
default:
|
default:
|
||||||
case SortMode.Artist:
|
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;
|
break;
|
||||||
|
|
||||||
case SortMode.Title:
|
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;
|
break;
|
||||||
|
|
||||||
case SortMode.Author:
|
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;
|
break;
|
||||||
|
|
||||||
case SortMode.Source:
|
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;
|
break;
|
||||||
|
|
||||||
case SortMode.DateAdded:
|
case SortMode.DateAdded:
|
||||||
|
@ -122,7 +122,7 @@ namespace osu.Game.Screens.Select.Carousel
|
|||||||
{
|
{
|
||||||
difficultyIcon = new DifficultyIcon(beatmapInfo)
|
difficultyIcon = new DifficultyIcon(beatmapInfo)
|
||||||
{
|
{
|
||||||
ShowTooltip = false,
|
TooltipType = DifficultyIconTooltipType.None,
|
||||||
Scale = new Vector2(1.8f),
|
Scale = new Vector2(1.8f),
|
||||||
},
|
},
|
||||||
new FillFlowContainer
|
new FillFlowContainer
|
||||||
|
@ -153,6 +153,8 @@ namespace osu.Game.Tests.Visual
|
|||||||
|
|
||||||
public new Bindable<IReadOnlyList<Mod>> SelectedMods => base.SelectedMods;
|
public new Bindable<IReadOnlyList<Mod>> SelectedMods => base.SelectedMods;
|
||||||
|
|
||||||
|
public new Storage Storage => base.Storage;
|
||||||
|
|
||||||
public new SpectatorClient SpectatorClient => base.SpectatorClient;
|
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.
|
// 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)
|
public TestOsuGame(Storage storage, IAPIProvider api, string[] args = null)
|
||||||
: base(args)
|
: base(args)
|
||||||
{
|
{
|
||||||
Storage = storage;
|
base.Storage = storage;
|
||||||
API = api;
|
API = api;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ namespace osu.Game.Tests.Visual
|
|||||||
PauseOnFocusLost = pauseOnFocusLost;
|
PauseOnFocusLost = pauseOnFocusLost;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool HandleTokenRetrievalFailure(Exception exception) => false;
|
protected override bool ShouldExitOnTokenRetrievalFailure(Exception exception) => false;
|
||||||
|
|
||||||
protected override APIRequest<APIScoreToken> CreateTokenRequest()
|
protected override APIRequest<APIScoreToken> CreateTokenRequest()
|
||||||
{
|
{
|
||||||
|
@ -166,6 +166,9 @@ namespace osu.Game.Users
|
|||||||
globalRankDisplay = new ProfileValueDisplay(true)
|
globalRankDisplay = new ProfileValueDisplay(true)
|
||||||
{
|
{
|
||||||
Title = UsersStrings.ShowRankGlobalSimple,
|
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)
|
countryRankDisplay = new ProfileValueDisplay(true)
|
||||||
{
|
{
|
||||||
|
@ -49,5 +49,15 @@ namespace osu.Game.Utils
|
|||||||
|
|
||||||
return precision;
|
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>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Realm" Version="11.5.0" />
|
<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="ppy.osu.Game.Resources" Version="2024.207.0" />
|
||||||
<PackageReference Include="Sentry" Version="3.41.3" />
|
<PackageReference Include="Sentry" Version="3.41.3" />
|
||||||
<!-- Held back due to 0.34.0 failing AOT compilation on ZstdSharp.dll dependency. -->
|
<!-- Held back due to 0.34.0 failing AOT compilation on ZstdSharp.dll dependency. -->
|
||||||
|
@ -23,6 +23,6 @@
|
|||||||
<RuntimeIdentifier>iossimulator-x64</RuntimeIdentifier>
|
<RuntimeIdentifier>iossimulator-x64</RuntimeIdentifier>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2024.215.0" />
|
<PackageReference Include="ppy.osu.Framework.iOS" Version="2024.221.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -102,6 +102,19 @@
|
|||||||
<string>osz</string>
|
<string>osz</string>
|
||||||
</dict>
|
</dict>
|
||||||
</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>
|
</array>
|
||||||
<key>CFBundleDocumentTypes</key>
|
<key>CFBundleDocumentTypes</key>
|
||||||
<array>
|
<array>
|
||||||
|
Loading…
Reference in New Issue
Block a user