Compare commits
183 Commits
2025.1119.
...
d76d4d9a35
| Author | SHA1 | Date | |
|---|---|---|---|
| d76d4d9a35 | |||
|
|
bb7417c099 | ||
|
|
066e093987 | ||
|
|
56e0c3e65d | ||
|
|
89d7726903 | ||
|
|
36f1bfef07 | ||
|
|
118f07878a | ||
|
|
e144968893 | ||
|
|
d2ffea41c6 | ||
|
|
a8be9b1381 | ||
| 490a6fd724 | |||
| 237e1828f8 | |||
| 3413f722f7 | |||
| 9f779dac03 | |||
| 0727c53cdc | |||
| a57ff24191 | |||
|
|
fbac5db964 | ||
|
|
5a920d15c1 | ||
|
|
324d088d46 | ||
|
|
1db4b897eb | ||
|
|
8e2230d149 | ||
|
|
8d33c35646 | ||
|
|
c359898a75 | ||
|
|
6343bf7d29 | ||
|
|
b1e27d842b | ||
|
|
28eeb7f743 | ||
|
|
38c3167a9d | ||
|
|
66ebce8c12 | ||
|
|
fed9564b40 | ||
|
|
f595a47059 | ||
|
|
8a9f60df68 | ||
|
|
2d8b1e7152 | ||
|
|
fef8117b5c | ||
|
|
99da986e02 | ||
|
|
1c33291b3f | ||
|
|
c6cc92315c | ||
|
|
12170df80a | ||
|
|
3e4c038a37 | ||
|
|
5d76353ae4 | ||
|
|
0b3ec3f1e1 | ||
|
|
043a1c2793 | ||
|
|
ca8247c667 | ||
|
|
a5ae542502 | ||
|
|
fe5cbc4932 | ||
|
|
0a378e5efd | ||
|
|
1d221c1a7a | ||
| bdb3418b67 | |||
|
|
82f4406c79 | ||
|
|
92e9a36744 | ||
|
|
c6eba26a67 | ||
|
|
8f927ea7b5 | ||
|
|
a8f058141b | ||
|
|
6bb25b2abe | ||
|
|
037743e002 | ||
|
|
6244617e5e | ||
|
|
ddfcb4d6da | ||
|
|
2660f4dcb0 | ||
|
|
5a865476ce | ||
|
|
2472c91924 | ||
|
|
db50019f31 | ||
|
|
1e43509e4a | ||
|
|
ded8aaecfd | ||
|
|
75df8e3639 | ||
|
|
0d9a50e839 | ||
|
|
7473c62949 | ||
|
|
8d30e3d852 | ||
|
|
97fdc89fe3 | ||
|
|
f6a6c9f885 | ||
|
|
26c50b874c | ||
|
|
83706b7fb6 | ||
|
|
9e3486d4e6 | ||
|
|
545b13c3fb | ||
|
|
2c9fc32756 | ||
|
|
0786e619f1 | ||
|
|
c968981697 | ||
|
|
45567f19b7 | ||
|
|
f0f4e7c7a5 | ||
|
|
1d353ef637 | ||
|
|
52af905237 | ||
|
|
64668eafb9 | ||
|
|
b0762fc8ec | ||
|
|
d59e9572d2 | ||
|
|
098da946e1 | ||
|
|
510fc506fb | ||
|
|
da09ad9c46 | ||
|
|
aaff7d358f | ||
|
|
a69b2cd803 | ||
|
|
855d5dba3c | ||
|
|
9c981a52f8 | ||
|
|
96de47ac4f | ||
|
|
43834b55f2 | ||
|
|
8fb402665e | ||
|
|
e4975e8d3b | ||
|
|
dbd9f13f2d | ||
|
|
ec890cd459 | ||
|
|
33c8c4d639 | ||
|
|
83ce56b718 | ||
|
|
9d88c761d3 | ||
|
|
49eb013967 | ||
|
|
b6ccc8cae4 | ||
|
|
d0e09e5b5c | ||
|
|
8900c79758 | ||
|
|
fd652982ce | ||
|
|
a2bfb409d2 | ||
| 936640edeb | |||
| 1187d03333 | |||
|
|
26da75ecfb | ||
|
|
9f8554cc13 | ||
|
|
713b6453c0 | ||
|
|
1b3ac49f2a | ||
|
|
d8b71423b0 | ||
|
|
2c40e116e1 | ||
|
|
90e7faf271 | ||
|
|
fc74726d11 | ||
|
|
5d3997152a | ||
|
|
41b56971e5 | ||
|
|
98e7a10e1e | ||
|
|
e99b9984d0 | ||
|
|
38504fed22 | ||
|
|
13dab24d41 | ||
|
|
67530b39cf | ||
|
|
19f5e5ba7c | ||
|
|
56ce955e0c | ||
|
|
73349ab182 | ||
|
|
a6a98fc078 | ||
|
|
a8594f1c08 | ||
|
|
d3860f1630 | ||
|
|
15ee49348d | ||
|
|
908a950cd2 | ||
|
|
df79269e6f | ||
|
|
34146b8bcb | ||
|
|
08ed2844b4 | ||
|
|
871c0ebe3d | ||
|
|
6362cdb675 | ||
|
|
8e78f4dac4 | ||
|
|
fa8d303922 | ||
|
|
d465bee0ab | ||
|
|
721ba8aeba | ||
|
|
1dd026c0f0 | ||
|
|
a873f2be65 | ||
|
|
edf7a126c8 | ||
|
|
f0f33b6df4 | ||
|
|
6052ed790d | ||
|
|
107c481fb9 | ||
|
|
aba567d258 | ||
|
|
094454499c | ||
|
|
c7e1a5770d | ||
|
|
a8ac82aa1f | ||
|
|
47faf774b0 | ||
|
|
be77257ddb | ||
|
|
397041099e | ||
|
|
4b59a4657f | ||
|
|
02090bf6c4 | ||
|
|
603c77e3e9 | ||
|
|
f284864f96 | ||
|
|
fa1bf7bd96 | ||
|
|
ef4408a73e | ||
|
|
6f7f9802bd | ||
|
|
277f4268db | ||
|
|
fbd83cb048 | ||
|
|
843c318ec1 | ||
|
|
0c341c1f3e | ||
|
|
ae5584bd88 | ||
|
|
fe56ba2921 | ||
|
|
1ca4c8860b | ||
|
|
1e05613859 | ||
|
|
424ef9237f | ||
|
|
e541e917a4 | ||
|
|
7796394685 | ||
|
|
32900f563c | ||
|
|
e349a597ba | ||
|
|
1e91dde92e | ||
|
|
435cd272ea | ||
|
|
72507b80c7 | ||
|
|
c56c528824 | ||
|
|
1df640898f | ||
|
|
7b55b9e4f2 | ||
|
|
6a6c7ad3ba | ||
|
|
afdebcf188 | ||
|
|
1ec6735a35 | ||
|
|
245ade004a | ||
|
|
6cb46106fe | ||
|
|
3666e4c332 |
@@ -1,8 +1,10 @@
|
||||
name: Update osu-web mod definitions
|
||||
name: Update osu-web mod definitions (DO NOT USE YET!!!!!)
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
workflow_dispatch:
|
||||
# push:
|
||||
# tags:
|
||||
# - '*'
|
||||
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
@@ -1,4 +1,10 @@
|
||||
on: [push, pull_request]
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
workflow_dispatch:
|
||||
|
||||
|
||||
name: Continuous Integration
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
3
.github/workflows/deploy.yml
vendored
3
.github/workflows/deploy.yml
vendored
@@ -4,6 +4,7 @@ on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
notify_pending_production_deploy:
|
||||
@@ -12,7 +13,7 @@ jobs:
|
||||
- name: Submit pending deployment notification
|
||||
run: |
|
||||
export TITLE="Pending osu Production Deployment: $GITHUB_REF_NAME"
|
||||
export URL="https://github.com/ppy/osu/actions/runs/$GITHUB_RUN_ID"
|
||||
export URL="https://github.com/jvnkosu-dev/client/actions/runs/$GITHUB_RUN_ID"
|
||||
export DESCRIPTION="Awaiting approval for building NuGet packages for tag $GITHUB_REF_NAME:
|
||||
[View Workflow Run]($URL)"
|
||||
export ACTOR_ICON="https://avatars.githubusercontent.com/u/$GITHUB_ACTOR_ID"
|
||||
|
||||
4
.github/workflows/sentry-release.yml
vendored
4
.github/workflows/sentry-release.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_ORG: ppy
|
||||
SENTRY_PROJECT: osu
|
||||
SENTRY_URL: https://sentry.ppy.sh/
|
||||
SENTRY_URL: https://satellite.jvnko.boats/
|
||||
with:
|
||||
environment: production
|
||||
version: osu@${{ github.ref_name }}
|
||||
version: jvnkosu@${{ github.ref_name }}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2025.1118.1" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2025.1209.0" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="sh.ppy.osulazer" android:installLocation="auto">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="boats.jvnko.osu.android" android:installLocation="auto">
|
||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="34" />
|
||||
<application android:allowBackup="true"
|
||||
android:supportsRtl="true"
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
||||
private double placementStartTime;
|
||||
private double placementEndTime;
|
||||
|
||||
protected override bool IsValidForPlacement => Precision.DefinitelyBigger(HitObject.Duration, 0);
|
||||
protected override bool IsValidForPlacement => base.IsValidForPlacement && (PlacementActive == PlacementState.Waiting || Precision.DefinitelyBigger(HitObject.Duration, 0));
|
||||
|
||||
public BananaShowerPlacementBlueprint()
|
||||
{
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
||||
|
||||
private InputManager inputManager = null!;
|
||||
|
||||
protected override bool IsValidForPlacement => Precision.DefinitelyBigger(HitObject.Duration, 0);
|
||||
protected override bool IsValidForPlacement => base.IsValidForPlacement && (PlacementActive == PlacementState.Waiting || Precision.DefinitelyBigger(HitObject.Duration, 0));
|
||||
|
||||
public JuiceStreamPlacementBlueprint()
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using osu.Framework.Allocation;
|
||||
@@ -224,7 +225,8 @@ namespace osu.Game.Rulesets.Catch.Edit
|
||||
#region Clipboard handling
|
||||
|
||||
public override string ConvertSelectionToString()
|
||||
=> string.Join(',', EditorBeatmap.SelectedHitObjects.Cast<CatchHitObject>().OrderBy(h => h.StartTime).Select(h => (h.IndexInCurrentCombo + 1).ToString()));
|
||||
=> string.Join(',', EditorBeatmap.SelectedHitObjects.Cast<CatchHitObject>().OrderBy(h => h.StartTime)
|
||||
.Select(h => (h.IndexInCurrentCombo + 1).ToString(CultureInfo.InvariantCulture)));
|
||||
|
||||
// 1,2,3,4 ...
|
||||
private static readonly Regex selection_regex = new Regex(@"^\d+(,\d+)*$", RegexOptions.Compiled);
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
<PropertyGroup Label="Nuget">
|
||||
<Title>osu!catch (ruleset)</Title>
|
||||
<PackageId>ppy.osu.Game.Rulesets.Catch</PackageId>
|
||||
<PackageId>jvnkosu.Client.Rulesets.Catch</PackageId>
|
||||
<IsPackable>true</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
@@ -16,6 +17,7 @@ using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK.Input;
|
||||
|
||||
@@ -36,21 +38,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
||||
[Test]
|
||||
public void TestPlaceBeforeCurrentTimeDownwards()
|
||||
{
|
||||
AddStep("seek to 200", () => HitObjectContainer.Dependencies.Get<EditorClock>().Seek(200));
|
||||
AddStep("move mouse before current time", () =>
|
||||
{
|
||||
var column = this.ChildrenOfType<Column>().Single();
|
||||
InputManager.MoveMouseTo(column.ScreenSpacePositionAtTime(-100));
|
||||
});
|
||||
|
||||
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
AddAssert("note start time < 0", () => getNote().StartTime < 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPlaceAfterCurrentTimeDownwards()
|
||||
{
|
||||
AddStep("move mouse after current time", () =>
|
||||
{
|
||||
var column = this.ChildrenOfType<Column>().Single();
|
||||
InputManager.MoveMouseTo(column.ScreenSpacePositionAtTime(100));
|
||||
@@ -58,7 +47,22 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
||||
|
||||
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
AddAssert("note start time > 0", () => getNote().StartTime > 0);
|
||||
AddAssert("note start time < 200", () => getNote().StartTime < 200);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPlaceAfterCurrentTimeDownwards()
|
||||
{
|
||||
AddStep("seek to 200", () => HitObjectContainer.Dependencies.Get<EditorClock>().Seek(200));
|
||||
AddStep("move mouse after current time", () =>
|
||||
{
|
||||
var column = this.ChildrenOfType<Column>().Single();
|
||||
InputManager.MoveMouseTo(column.ScreenSpacePositionAtTime(300));
|
||||
});
|
||||
|
||||
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
AddAssert("note start time > 200", () => getNote().StartTime > 200);
|
||||
}
|
||||
|
||||
private Note getNote() => this.ChildrenOfType<DrawableNote>().FirstOrDefault()?.HitObject;
|
||||
|
||||
@@ -18,15 +18,11 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
||||
public void TestNormalSelection()
|
||||
{
|
||||
addStepClickLink("00:05:920 (5920|3,6623|3,6857|2,7326|1)");
|
||||
AddAssert("selected group", () => checkSnapAndSelectColumn(5_920, new List<(int, int)>
|
||||
{ (5_920, 3), (6_623, 3), (6_857, 2), (7_326, 1) }
|
||||
));
|
||||
AddAssert("selected group", () => checkSnapAndSelectColumn(5_920, [(5_920, 3), (6_623, 3), (6_857, 2), (7_326, 1)]));
|
||||
|
||||
addReset();
|
||||
addStepClickLink("00:42:716 (42716|3,43420|2,44123|0,44357|1,45295|1)");
|
||||
AddAssert("selected ungrouped", () => checkSnapAndSelectColumn(42_716, new List<(int, int)>
|
||||
{ (42_716, 3), (43_420, 2), (44_123, 0), (44_357, 1), (45_295, 1) }
|
||||
));
|
||||
AddAssert("selected ungrouped", () => checkSnapAndSelectColumn(42_716, [(42_716, 3), (43_420, 2), (44_123, 0), (44_357, 1), (45_295, 1)]));
|
||||
|
||||
addReset();
|
||||
AddStep("add notes to row", () =>
|
||||
@@ -41,15 +37,20 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
||||
EditorBeatmap.AddRange(new[] { second, third, forth });
|
||||
});
|
||||
addStepClickLink("00:11:545 (11545|0,11545|1,11545|2,11545|3)");
|
||||
AddAssert("selected in row", () => checkSnapAndSelectColumn(11_545, new List<(int, int)>
|
||||
{ (11_545, 0), (11_545, 1), (11_545, 2), (11_545, 3) }
|
||||
));
|
||||
AddAssert("selected in row", () => checkSnapAndSelectColumn(11_545, [(11_545, 0), (11_545, 1), (11_545, 2), (11_545, 3)]));
|
||||
|
||||
addReset();
|
||||
addStepClickLink("01:36:623 (96623|1,97560|1,97677|1,97795|1,98966|1)");
|
||||
AddAssert("selected in column", () => checkSnapAndSelectColumn(96_623, new List<(int, int)>
|
||||
{ (96_623, 1), (97_560, 1), (97_677, 1), (97_795, 1), (98_966, 1) }
|
||||
));
|
||||
AddAssert("selected in column", () => checkSnapAndSelectColumn(96_623, [(96_623, 1), (97_560, 1), (97_677, 1), (97_795, 1), (98_966, 1)]));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRoundingToNearestMillisecondApplied()
|
||||
{
|
||||
AddStep("resnap note to have fractional coordinates",
|
||||
() => EditorBeatmap.HitObjects.OfType<ManiaHitObject>().Single(ho => ho.StartTime == 85_373 && ho.Column == 1).StartTime = 85_373.125);
|
||||
addStepClickLink("01:25:373 (85373|1)");
|
||||
AddAssert("selected note", () => checkSnapAndSelectColumn(85_373.125, [(85_373.125, 1)]));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -75,7 +76,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
||||
|
||||
private void addReset() => addStepClickLink("00:00:000", "reset", false);
|
||||
|
||||
private bool checkSnapAndSelectColumn(double startTime, IReadOnlyCollection<(int, int)>? columnPairs = null)
|
||||
private bool checkSnapAndSelectColumn(double startTime, IReadOnlyCollection<(double, int)>? columnPairs = null)
|
||||
{
|
||||
bool checkColumns = columnPairs != null
|
||||
? EditorBeatmap.SelectedHitObjects.All(x => columnPairs.Any(col => isNoteAt(x, col.Item1, col.Item2)))
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||
[Resolved]
|
||||
private IScrollingInfo scrollingInfo { get; set; } = null!;
|
||||
|
||||
protected override bool IsValidForPlacement => Precision.DefinitelyBigger(HitObject.Duration, 0);
|
||||
protected override bool IsValidForPlacement => base.IsValidForPlacement && (PlacementActive == PlacementState.Waiting || Precision.DefinitelyBigger(HitObject.Duration, 0));
|
||||
|
||||
public HoldNotePlacementBlueprint()
|
||||
: base(new HoldNote())
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Edit.Tools;
|
||||
@@ -54,7 +56,8 @@ namespace osu.Game.Rulesets.Mania.Edit
|
||||
};
|
||||
|
||||
public override string ConvertSelectionToString()
|
||||
=> string.Join(',', EditorBeatmap.SelectedHitObjects.Cast<ManiaHitObject>().OrderBy(h => h.StartTime).Select(h => $"{h.StartTime}|{h.Column}"));
|
||||
=> string.Join(',', EditorBeatmap.SelectedHitObjects.Cast<ManiaHitObject>().OrderBy(h => h.StartTime)
|
||||
.Select(h => FormattableString.Invariant($"{Math.Round(h.StartTime)}|{h.Column}")));
|
||||
|
||||
// 123|0,456|1,789|2 ...
|
||||
private static readonly Regex selection_regex = new Regex(@"^\d+\|\d+(,\d+\|\d+)*$", RegexOptions.Compiled);
|
||||
@@ -73,10 +76,10 @@ namespace osu.Game.Rulesets.Mania.Edit
|
||||
if (split.Length != 2)
|
||||
continue;
|
||||
|
||||
if (!double.TryParse(split[0], out double time) || !int.TryParse(split[1], out int column))
|
||||
if (!int.TryParse(split[0], out int time) || !int.TryParse(split[1], out int column))
|
||||
continue;
|
||||
|
||||
ManiaHitObject? current = remainingHitObjects.FirstOrDefault(h => h.StartTime == time && h.Column == column);
|
||||
ManiaHitObject? current = remainingHitObjects.FirstOrDefault(h => Precision.AlmostEquals(h.StartTime, time, 0.5) && h.Column == column);
|
||||
|
||||
if (current == null)
|
||||
continue;
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
<PropertyGroup Label="Nuget">
|
||||
<Title>osu!mania (ruleset)</Title>
|
||||
<PackageId>ppy.osu.Game.Rulesets.Mania</PackageId>
|
||||
<PackageId>jvnkosu.Client.Rulesets.Mania</PackageId>
|
||||
<IsPackable>true</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -245,13 +245,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
AddAssert("grid spacing is distance to slider tail", () =>
|
||||
{
|
||||
var composer = Editor.ChildrenOfType<RectangularPositionSnapGrid>().Single();
|
||||
return Precision.AlmostEquals(composer.Spacing.Value.X, 32.05, 0.01)
|
||||
return Precision.AlmostEquals(composer.Spacing.Value.X, 32.05, 0.1)
|
||||
&& Precision.AlmostEquals(composer.Spacing.Value.X, composer.Spacing.Value.Y);
|
||||
});
|
||||
AddAssert("grid rotation points to slider tail", () =>
|
||||
{
|
||||
var composer = Editor.ChildrenOfType<RectangularPositionSnapGrid>().Single();
|
||||
return Precision.AlmostEquals(composer.GridLineRotation.Value, 0.09, 0.01);
|
||||
return Precision.AlmostEquals(composer.GridLineRotation.Value, 0.09, 0.1);
|
||||
});
|
||||
|
||||
AddStep("start grid placement", () => InputManager.Key(Key.Number5));
|
||||
@@ -280,9 +280,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
AddAssert("grid spacing and rotation unchanged", () =>
|
||||
{
|
||||
var composer = Editor.ChildrenOfType<RectangularPositionSnapGrid>().Single();
|
||||
return Precision.AlmostEquals(composer.Spacing.Value.X, 32.05, 0.01)
|
||||
return Precision.AlmostEquals(composer.Spacing.Value.X, 32.05, 0.1)
|
||||
&& Precision.AlmostEquals(composer.Spacing.Value.X, composer.Spacing.Value.Y)
|
||||
&& Precision.AlmostEquals(composer.GridLineRotation.Value, 0.09, 0.01);
|
||||
&& Precision.AlmostEquals(composer.GridLineRotation.Value, 0.09, 0.1);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.UI;
|
||||
@@ -22,7 +23,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
[TestFixture]
|
||||
public partial class TestSceneSliderDrawing : TestSceneOsuEditor
|
||||
{
|
||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false);
|
||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
|
||||
{
|
||||
var beatmap = new TestBeatmap(ruleset, false);
|
||||
beatmap.ControlPointInfo.Add(0, new TimingControlPoint());
|
||||
return beatmap;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestTouchInputPlaceHitCircleDirectly()
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints
|
||||
{
|
||||
this.gridToolboxGroup = gridToolboxGroup;
|
||||
originalOrigin = gridToolboxGroup.StartPosition.Value;
|
||||
originalSpacing = gridToolboxGroup.Spacing.Value;
|
||||
originalSpacing = gridToolboxGroup.GridLineSpacing.Value;
|
||||
originalRotation = gridToolboxGroup.GridLinesRotation.Value;
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints
|
||||
{
|
||||
// Reset the grid to the default values.
|
||||
gridToolboxGroup.StartPosition.Value = gridToolboxGroup.StartPosition.Default;
|
||||
gridToolboxGroup.Spacing.Value = gridToolboxGroup.Spacing.Default;
|
||||
gridToolboxGroup.GridLineSpacing.Value = gridToolboxGroup.GridLineSpacing.Default;
|
||||
if (!gridToolboxGroup.GridLinesRotation.Disabled)
|
||||
gridToolboxGroup.GridLinesRotation.Value = gridToolboxGroup.GridLinesRotation.Default;
|
||||
EndPlacement(true);
|
||||
@@ -112,7 +112,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints
|
||||
// Default to the original spacing and rotation if the distance is too small.
|
||||
if (Vector2.Distance(gridToolboxGroup.StartPosition.Value, pos) < 2)
|
||||
{
|
||||
gridToolboxGroup.Spacing.Value = originalSpacing;
|
||||
gridToolboxGroup.GridLineSpacing.Value = originalSpacing;
|
||||
if (!gridToolboxGroup.GridLinesRotation.Disabled)
|
||||
gridToolboxGroup.GridLinesRotation.Value = originalRotation;
|
||||
}
|
||||
@@ -134,7 +134,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints
|
||||
private void resetGridState()
|
||||
{
|
||||
gridToolboxGroup.StartPosition.Value = originalOrigin;
|
||||
gridToolboxGroup.Spacing.Value = originalSpacing;
|
||||
gridToolboxGroup.GridLineSpacing.Value = originalSpacing;
|
||||
if (!gridToolboxGroup.GridLinesRotation.Disabled)
|
||||
gridToolboxGroup.GridLinesRotation.Value = originalRotation;
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
|
||||
private readonly IncrementalBSplineBuilder bSplineBuilder = new IncrementalBSplineBuilder { Degree = 4 };
|
||||
|
||||
protected override bool IsValidForPlacement => HitObject.Path.HasValidLengthForPlacement;
|
||||
protected override bool IsValidForPlacement => base.IsValidForPlacement && (PlacementActive == PlacementState.Waiting || HitObject.Path.HasValidLengthForPlacement);
|
||||
|
||||
public SliderPlacementBlueprint()
|
||||
: base(new Slider())
|
||||
|
||||
@@ -5,8 +5,10 @@ using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
@@ -42,25 +44,31 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
private readonly BindableInt displayTolerance = new BindableInt(90)
|
||||
{
|
||||
MinValue = 5,
|
||||
MaxValue = 100
|
||||
MaxValue = 100,
|
||||
Precision = 1,
|
||||
};
|
||||
|
||||
private readonly BindableInt displayCornerThreshold = new BindableInt(40)
|
||||
{
|
||||
MinValue = 5,
|
||||
MaxValue = 100
|
||||
MaxValue = 100,
|
||||
Precision = 1,
|
||||
};
|
||||
|
||||
private readonly BindableInt displayCircleThreshold = new BindableInt(30)
|
||||
{
|
||||
MinValue = 0,
|
||||
MaxValue = 100
|
||||
MaxValue = 100,
|
||||
Precision = 1,
|
||||
};
|
||||
|
||||
private ExpandableSlider<int> toleranceSlider = null!;
|
||||
private ExpandableSlider<int> cornerThresholdSlider = null!;
|
||||
private ExpandableSlider<int> circleThresholdSlider = null!;
|
||||
|
||||
[Resolved]
|
||||
private IExpandingContainer? expandingContainer { get; set; }
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
@@ -68,15 +76,18 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
toleranceSlider = new ExpandableSlider<int>
|
||||
{
|
||||
Current = displayTolerance
|
||||
Current = displayTolerance,
|
||||
ExpandedLabelText = "Control point spacing",
|
||||
},
|
||||
cornerThresholdSlider = new ExpandableSlider<int>
|
||||
{
|
||||
Current = displayCornerThreshold
|
||||
Current = displayCornerThreshold,
|
||||
ExpandedLabelText = "Corner bias",
|
||||
},
|
||||
circleThresholdSlider = new ExpandableSlider<int>
|
||||
{
|
||||
Current = displayCircleThreshold
|
||||
Current = displayCircleThreshold,
|
||||
ExpandedLabelText = "Perfect curve bias"
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -88,24 +99,18 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
displayTolerance.BindValueChanged(tolerance =>
|
||||
{
|
||||
toleranceSlider.ContractedLabelText = $"C. P. S.: {tolerance.NewValue:N0}";
|
||||
toleranceSlider.ExpandedLabelText = $"Control Point Spacing: {tolerance.NewValue:N0}";
|
||||
|
||||
Tolerance.Value = displayToInternalTolerance(tolerance.NewValue);
|
||||
}, true);
|
||||
|
||||
displayCornerThreshold.BindValueChanged(threshold =>
|
||||
{
|
||||
cornerThresholdSlider.ContractedLabelText = $"C. T.: {threshold.NewValue:N0}";
|
||||
cornerThresholdSlider.ExpandedLabelText = $"Corner Threshold: {threshold.NewValue:N0}";
|
||||
|
||||
cornerThresholdSlider.ContractedLabelText = $"C. B.: {threshold.NewValue:N0}";
|
||||
CornerThreshold.Value = displayToInternalCornerThreshold(threshold.NewValue);
|
||||
}, true);
|
||||
|
||||
displayCircleThreshold.BindValueChanged(threshold =>
|
||||
{
|
||||
circleThresholdSlider.ContractedLabelText = $"P. C. T.: {threshold.NewValue:N0}";
|
||||
circleThresholdSlider.ExpandedLabelText = $"Perfect Curve Threshold: {threshold.NewValue:N0}";
|
||||
|
||||
circleThresholdSlider.ContractedLabelText = $"P. C. B.: {threshold.NewValue:N0}";
|
||||
CircleThreshold.Value = displayToInternalCircleThreshold(threshold.NewValue);
|
||||
}, true);
|
||||
|
||||
@@ -119,6 +124,11 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
displayCircleThreshold.Value = internalToDisplayCircleThreshold(threshold.NewValue)
|
||||
);
|
||||
|
||||
expandingContainer?.Expanded.BindValueChanged(v =>
|
||||
{
|
||||
Spacing = v.NewValue ? new Vector2(5) : new Vector2(15);
|
||||
}, true);
|
||||
|
||||
float displayToInternalTolerance(float v) => v / 50f;
|
||||
int internalToDisplayTolerance(float v) => (int)Math.Round(v * 50f);
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
MinValue = 0f,
|
||||
MaxValue = OsuPlayfield.BASE_SIZE.X,
|
||||
Precision = 0.01f,
|
||||
Precision = 0.1f,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@@ -48,17 +48,17 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
MinValue = 0f,
|
||||
MaxValue = OsuPlayfield.BASE_SIZE.Y,
|
||||
Precision = 0.01f,
|
||||
Precision = 0.1f,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The spacing between grid lines.
|
||||
/// </summary>
|
||||
public BindableFloat Spacing { get; } = new BindableFloat(4f)
|
||||
public BindableFloat GridLineSpacing { get; } = new BindableFloat(4f)
|
||||
{
|
||||
MinValue = 4f,
|
||||
MaxValue = 256f,
|
||||
Precision = 0.01f,
|
||||
Precision = 0.1f,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
MinValue = -180f,
|
||||
MaxValue = 180f,
|
||||
Precision = 0.01f,
|
||||
Precision = 0.1f,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@@ -115,7 +115,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
float dist = Vector2.Distance(point1, point2);
|
||||
while (dist >= max_automatic_spacing)
|
||||
dist /= 2;
|
||||
Spacing.Value = dist;
|
||||
GridLineSpacing.Value = dist;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@@ -127,21 +127,25 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
Current = StartPositionX,
|
||||
KeyboardStep = 1,
|
||||
ExpandedLabelText = "X offset",
|
||||
},
|
||||
startPositionYSlider = new ExpandableSlider<float>
|
||||
{
|
||||
Current = StartPositionY,
|
||||
KeyboardStep = 1,
|
||||
ExpandedLabelText = "Y offset",
|
||||
},
|
||||
spacingSlider = new ExpandableSlider<float>
|
||||
{
|
||||
Current = Spacing,
|
||||
Current = GridLineSpacing,
|
||||
KeyboardStep = 1,
|
||||
ExpandedLabelText = "Spacing",
|
||||
},
|
||||
gridLinesRotationSlider = new ExpandableSlider<float>
|
||||
{
|
||||
Current = GridLinesRotation,
|
||||
KeyboardStep = 1,
|
||||
ExpandedLabelText = "Rotation",
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
@@ -170,7 +174,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
},
|
||||
};
|
||||
|
||||
Spacing.Value = editorBeatmap.GridSize;
|
||||
GridLineSpacing.Value = editorBeatmap.GridSize;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@@ -182,14 +186,12 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
StartPositionX.BindValueChanged(x =>
|
||||
{
|
||||
startPositionXSlider.ContractedLabelText = $"X: {x.NewValue:#,0.##}";
|
||||
startPositionXSlider.ExpandedLabelText = $"X Offset: {x.NewValue:#,0.##}";
|
||||
StartPosition.Value = new Vector2(x.NewValue, StartPosition.Value.Y);
|
||||
}, true);
|
||||
|
||||
StartPositionY.BindValueChanged(y =>
|
||||
{
|
||||
startPositionYSlider.ContractedLabelText = $"Y: {y.NewValue:#,0.##}";
|
||||
startPositionYSlider.ExpandedLabelText = $"Y Offset: {y.NewValue:#,0.##}";
|
||||
StartPosition.Value = new Vector2(StartPosition.Value.X, y.NewValue);
|
||||
}, true);
|
||||
|
||||
@@ -199,10 +201,9 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
StartPositionY.Value = pos.NewValue.Y;
|
||||
});
|
||||
|
||||
Spacing.BindValueChanged(spacing =>
|
||||
GridLineSpacing.BindValueChanged(spacing =>
|
||||
{
|
||||
spacingSlider.ContractedLabelText = $"S: {spacing.NewValue:#,0.##}";
|
||||
spacingSlider.ExpandedLabelText = $"Spacing: {spacing.NewValue:#,0.##}";
|
||||
SpacingVector.Value = new Vector2(spacing.NewValue);
|
||||
editorBeatmap.GridSize = (int)spacing.NewValue;
|
||||
}, true);
|
||||
@@ -210,7 +211,6 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
GridLinesRotation.BindValueChanged(rotation =>
|
||||
{
|
||||
gridLinesRotationSlider.ContractedLabelText = $"R: {rotation.NewValue:#,0.##}";
|
||||
gridLinesRotationSlider.ExpandedLabelText = $"Rotation: {rotation.NewValue:#,0.##}";
|
||||
}, true);
|
||||
|
||||
GridType.BindValueChanged(v =>
|
||||
@@ -239,6 +239,8 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
gridTypeButtons.FadeTo(v.NewValue ? 1f : 0f, 500, Easing.OutQuint);
|
||||
gridTypeButtons.BypassAutoSizeAxes = !v.NewValue ? Axes.Y : Axes.None;
|
||||
|
||||
Spacing = v.NewValue ? new Vector2(5) : new Vector2(15);
|
||||
}, true);
|
||||
}
|
||||
|
||||
@@ -252,7 +254,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
switch (e.Action)
|
||||
{
|
||||
case GlobalAction.EditorCycleGridSpacing:
|
||||
Spacing.Value = Spacing.Value * 2 >= max_automatic_spacing ? Spacing.Value / 8 : Spacing.Value * 2;
|
||||
GridLineSpacing.Value = GridLineSpacing.Value * 2 >= max_automatic_spacing ? GridLineSpacing.Value / 8 : GridLineSpacing.Value * 2;
|
||||
return true;
|
||||
|
||||
case GlobalAction.EditorCycleGridType:
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using JetBrains.Annotations;
|
||||
@@ -142,7 +143,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
case PositionSnapGridType.Triangle:
|
||||
var triangularPositionSnapGrid = new TriangularPositionSnapGrid();
|
||||
|
||||
triangularPositionSnapGrid.Spacing.BindTo(OsuGridToolboxGroup.Spacing);
|
||||
triangularPositionSnapGrid.Spacing.BindTo(OsuGridToolboxGroup.GridLineSpacing);
|
||||
triangularPositionSnapGrid.GridLineRotation.BindTo(OsuGridToolboxGroup.GridLinesRotation);
|
||||
|
||||
positionSnapGrid = triangularPositionSnapGrid;
|
||||
@@ -151,7 +152,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
case PositionSnapGridType.Circle:
|
||||
var circularPositionSnapGrid = new CircularPositionSnapGrid();
|
||||
|
||||
circularPositionSnapGrid.Spacing.BindTo(OsuGridToolboxGroup.Spacing);
|
||||
circularPositionSnapGrid.Spacing.BindTo(OsuGridToolboxGroup.GridLineSpacing);
|
||||
|
||||
positionSnapGrid = circularPositionSnapGrid;
|
||||
break;
|
||||
@@ -171,7 +172,8 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
=> new OsuBlueprintContainer(this);
|
||||
|
||||
public override string ConvertSelectionToString()
|
||||
=> string.Join(',', selectedHitObjects.Cast<OsuHitObject>().OrderBy(h => h.StartTime).Select(h => (h.IndexInCurrentCombo + 1).ToString()));
|
||||
=> string.Join(',', selectedHitObjects.Cast<OsuHitObject>().OrderBy(h => h.StartTime)
|
||||
.Select(h => (h.IndexInCurrentCombo + 1).ToString(CultureInfo.InvariantCulture)));
|
||||
|
||||
// 1,2,3,4 ...
|
||||
private static readonly Regex selection_regex = new Regex(@"^\d+(,\d+)*$", RegexOptions.Compiled);
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public override LocalisableString Description => @"Don't use the same key twice in a row!";
|
||||
public override IconUsage? Icon => OsuIcon.ModAlternate;
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModSingleTap) }).ToArray();
|
||||
public override bool Ranked => true;
|
||||
|
||||
protected override bool CheckValidNewAction(OsuAction action) => LastAcceptedAction != action;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public override IconUsage? Icon => OsuIcon.ModSingleTap;
|
||||
public override LocalisableString Description => @"You must only use one key!";
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAlternate) }).ToArray();
|
||||
public override bool Ranked => true;
|
||||
|
||||
protected override bool CheckValidNewAction(OsuAction action) => LastAcceptedAction == null || LastAcceptedAction == action;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics.Lines;
|
||||
using osu.Framework.Graphics.Performance;
|
||||
using osu.Game.Graphics;
|
||||
@@ -12,7 +11,7 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis
|
||||
{
|
||||
public partial class CursorPathContainer : Path
|
||||
public partial class CursorPathContainer : SmoothPath
|
||||
{
|
||||
private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager();
|
||||
private readonly SortedSet<AnalysisFrameEntry> aliveEntries = new SortedSet<AnalysisFrameEntry>(new AimLinePointComparator());
|
||||
@@ -22,14 +21,13 @@ namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis
|
||||
lifetimeManager.EntryBecameAlive += entryBecameAlive;
|
||||
lifetimeManager.EntryBecameDead += entryBecameDead;
|
||||
|
||||
PathRadius = 0.5f;
|
||||
PathRadius = 1f;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
Colour = colours.Pink2;
|
||||
BackgroundColour = colours.Pink2.Opacity(0);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
<PropertyGroup Label="Nuget">
|
||||
<Title>osu! (ruleset)</Title>
|
||||
<PackageId>ppy.osu.Game.Rulesets.Osu</PackageId>
|
||||
<PackageId>jvnkosu.Client.Rulesets.Osu</PackageId>
|
||||
<IsPackable>true</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
|
||||
[Resolved]
|
||||
private TaikoHitObjectComposer? composer { get; set; }
|
||||
|
||||
protected override bool IsValidForPlacement => Precision.DefinitelyBigger(spanPlacementObject.Duration, 0);
|
||||
protected override bool IsValidForPlacement => base.IsValidForPlacement && (PlacementActive == PlacementState.Waiting || Precision.DefinitelyBigger(spanPlacementObject.Duration, 0));
|
||||
|
||||
public TaikoSpanPlacementBlueprint(HitObject hitObject)
|
||||
: base(hitObject)
|
||||
|
||||
@@ -13,9 +13,9 @@ namespace osu.Game.Rulesets.Taiko.Edit.Checks
|
||||
protected override IEnumerable<(DifficultyRating rating, double thresholdMs, string name)> GetThresholds()
|
||||
{
|
||||
// See lowest difficulty requirements in https://osu.ppy.sh/wiki/en/Ranking_criteria/osu%21taiko#general
|
||||
yield return (DifficultyRating.Hard, new TimeSpan(0, 3, 30).TotalMilliseconds, "Muzukashii");
|
||||
yield return (DifficultyRating.Insane, new TimeSpan(0, 4, 15).TotalMilliseconds, "Oni");
|
||||
yield return (DifficultyRating.Expert, new TimeSpan(0, 5, 0).TotalMilliseconds, "Inner Oni");
|
||||
yield return (DifficultyRating.Hard, new TimeSpan(0, 2, 30).TotalMilliseconds, "Muzukashii");
|
||||
yield return (DifficultyRating.Insane, new TimeSpan(0, 3, 15).TotalMilliseconds, "Oni");
|
||||
yield return (DifficultyRating.Expert, new TimeSpan(0, 4, 0).TotalMilliseconds, "Inner Oni");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
public override IconUsage? Icon => OsuIcon.ModSingleTap;
|
||||
public override LocalisableString Description => @"One key for dons, one key for kats.";
|
||||
|
||||
public override bool Ranked => true;
|
||||
public override double ScoreMultiplier => 1.0;
|
||||
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModRelax), typeof(TaikoModCinema) };
|
||||
public override ModType Type => ModType.Conversion;
|
||||
|
||||
@@ -22,6 +22,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
public override ModType Type => ModType.Conversion;
|
||||
public override double ScoreMultiplier => 1;
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModRandom)).ToArray();
|
||||
public override bool Ranked => true;
|
||||
|
||||
public void ApplyToBeatmap(IBeatmap beatmap)
|
||||
{
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
<PropertyGroup Label="Nuget">
|
||||
<Title>osu!taiko (ruleset)</Title>
|
||||
<PackageId>ppy.osu.Game.Rulesets.Taiko</PackageId>
|
||||
<PackageId>jvnkosu.Client.Rulesets.Taiko</PackageId>
|
||||
<IsPackable>true</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -220,6 +220,31 @@ namespace osu.Game.Tests.Chat
|
||||
AddAssert("channel has no messages", () => channel.Messages, () => Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPrivateChannelsPurgedOnUserChange()
|
||||
{
|
||||
var pmChannel = createChannel(1002, ChannelType.PM);
|
||||
AddStep("join a few private channels", () =>
|
||||
{
|
||||
channelManager.JoinChannel(createChannel(1001, ChannelType.PM));
|
||||
channelManager.JoinChannel(createChannel(1003, ChannelType.Team));
|
||||
channelManager.JoinChannel(pmChannel);
|
||||
});
|
||||
AddStep("close a PM channel", () => channelManager.LeaveChannel(pmChannel));
|
||||
|
||||
AddStep("switch user", () =>
|
||||
{
|
||||
((DummyAPIAccess.DummyLocalUserState)API.LocalUserState).User.Value = new APIUser
|
||||
{
|
||||
Id = 9009,
|
||||
Username = "someone_else"
|
||||
};
|
||||
});
|
||||
|
||||
AddAssert("not joined to private channels of previous user",
|
||||
() => !channelManager.JoinedChannels.Select(ch => ch.Id).Any(id => id >= 1001 && id <= 1003));
|
||||
}
|
||||
|
||||
private void handlePostMessageRequest(PostMessageRequest request)
|
||||
{
|
||||
var message = new Message(++currentMessageId)
|
||||
@@ -250,7 +275,7 @@ namespace osu.Game.Tests.Chat
|
||||
}
|
||||
}
|
||||
|
||||
private Channel createChannel(int id, ChannelType type) => new Channel(new APIUser())
|
||||
private Channel createChannel(int id, ChannelType type) => new Channel(new APIUser { Id = id })
|
||||
{
|
||||
Id = id,
|
||||
Name = $"Channel {id}",
|
||||
|
||||
@@ -738,6 +738,16 @@ namespace osu.Game.Tests.NonVisual.Filtering
|
||||
new object[] { "submitted=99999", false },
|
||||
new object[] { "submitted>=2012-03-05-04", false },
|
||||
new object[] { "submitted>=2012/03.05-04", false },
|
||||
|
||||
new object[] { "created<2012", true },
|
||||
new object[] { "created<2012.03", true },
|
||||
new object[] { "created<2012/03/05", true },
|
||||
new object[] { "created<2012-3-5", true },
|
||||
|
||||
new object[] { "created<0", false },
|
||||
new object[] { "created=99999", false },
|
||||
new object[] { "created>=2012-03-05-04", false },
|
||||
new object[] { "created>=2012/03.05-04", false },
|
||||
};
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Multiplayer.MatchTypes.Matchmaking;
|
||||
@@ -149,5 +150,33 @@ namespace osu.Game.Tests.Online.Matchmaking
|
||||
Assert.AreEqual(5, state.Users.GetOrAdd(5).Placement);
|
||||
Assert.AreEqual(6, state.Users.GetOrAdd(6).Placement);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AbandonOrder()
|
||||
{
|
||||
var state = new MatchmakingRoomState();
|
||||
|
||||
state.AdvanceRound();
|
||||
state.RecordScores(
|
||||
[
|
||||
new SoloScoreInfo { UserID = 1, TotalScore = 1000 },
|
||||
new SoloScoreInfo { UserID = 2, TotalScore = 500 },
|
||||
], placement_points);
|
||||
|
||||
Assert.AreEqual(1, state.Users.GetOrAdd(1).Placement);
|
||||
Assert.AreEqual(2, state.Users.GetOrAdd(2).Placement);
|
||||
|
||||
state.Users.GetOrAdd(1).AbandonedAt = DateTimeOffset.Now;
|
||||
state.RecordScores([], placement_points);
|
||||
|
||||
Assert.AreEqual(2, state.Users.GetOrAdd(1).Placement);
|
||||
Assert.AreEqual(1, state.Users.GetOrAdd(2).Placement);
|
||||
|
||||
state.Users.GetOrAdd(2).AbandonedAt = DateTimeOffset.Now - TimeSpan.FromMinutes(1);
|
||||
state.RecordScores([], placement_points);
|
||||
|
||||
Assert.AreEqual(1, state.Users.GetOrAdd(1).Placement);
|
||||
Assert.AreEqual(2, state.Users.GetOrAdd(2).Placement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,7 +266,11 @@ namespace osu.Game.Tests.Visual.Background
|
||||
|
||||
FadeAccessibleResults results = null;
|
||||
|
||||
AddStep("Transition to Results", () => player.Push(results = new FadeAccessibleResults(TestResources.CreateTestScoreInfo())));
|
||||
AddStep("Transition to Results", () =>
|
||||
{
|
||||
player.ValidForResume = false;
|
||||
player.Push(results = new FadeAccessibleResults(TestResources.CreateTestScoreInfo()));
|
||||
});
|
||||
|
||||
AddUntilStep("Wait for results is current", () => results.IsCurrentScreen());
|
||||
|
||||
|
||||
@@ -134,7 +134,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
double lastStarRating = 0;
|
||||
double lastLength = 0;
|
||||
|
||||
AddStep("Add timing point", () => EditorBeatmap.ControlPointInfo.Add(200, new TimingControlPoint { BeatLength = 600 }));
|
||||
AddStep("Add timing point", () => EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 600 }));
|
||||
AddStep("Change to placement mode", () => InputManager.Key(Key.Number2));
|
||||
AddStep("Move to playfield", () => InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.Centre));
|
||||
AddStep("Place single hitcircle", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
@@ -7,6 +7,7 @@ using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Rulesets;
|
||||
@@ -27,7 +28,12 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
{
|
||||
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
|
||||
|
||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false);
|
||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
|
||||
{
|
||||
var beatmap = new TestBeatmap(ruleset, false);
|
||||
beatmap.ControlPointInfo.Add(0, new TimingControlPoint());
|
||||
return beatmap;
|
||||
}
|
||||
|
||||
private GlobalActionContainer globalActionContainer => this.ChildrenOfType<GlobalActionContainer>().Single();
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ using NUnit.Framework;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
@@ -27,7 +28,12 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
{
|
||||
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
|
||||
|
||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false);
|
||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
|
||||
{
|
||||
var beatmap = new TestBeatmap(ruleset, false);
|
||||
beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
return beatmap;
|
||||
}
|
||||
|
||||
private TimelineBlueprintContainer blueprintContainer
|
||||
=> Editor.ChildrenOfType<TimelineBlueprintContainer>().First();
|
||||
@@ -80,7 +86,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
{
|
||||
InputManager.Key(Key.Number1);
|
||||
blueprint = this.ChildrenOfType<TimelineHitObjectBlueprint>().First();
|
||||
InputManager.MoveMouseTo(blueprint);
|
||||
InputManager.MoveMouseTo(blueprint, new Vector2(-1, 0));
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
// 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.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Screens.Select.Leaderboards;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public partial class TestSceneDrawableGameplayLeaderboardScore : OsuTestScene
|
||||
{
|
||||
private readonly APIUser user = new APIUser { Username = "user" };
|
||||
private readonly BindableLong totalScore = new BindableLong();
|
||||
private readonly Bindable<int?> position = new Bindable<int?>();
|
||||
private readonly BindableBool quit = new BindableBool();
|
||||
private readonly BindableBool expanded = new BindableBool();
|
||||
|
||||
public TestSceneDrawableGameplayLeaderboardScore()
|
||||
{
|
||||
AddSliderStep("total score", 0, 1_000_000, 500_000, s => totalScore.Value = s);
|
||||
AddSliderStep("position", 1, 100, 5, s => position.Value = s);
|
||||
AddToggleStep("toggle quit", q => quit.Value = q);
|
||||
AddToggleStep("toggle expanded", e => expanded.Value = e);
|
||||
}
|
||||
|
||||
private static readonly OsuColour osu_colour = new OsuColour();
|
||||
|
||||
private static readonly object?[][] leaderboard_variants =
|
||||
{
|
||||
new object?[] { false, null },
|
||||
new object?[] { true, null },
|
||||
new object?[] { false, osu_colour.TeamColourRed },
|
||||
new object?[] { true, osu_colour.TeamColourRed },
|
||||
new object?[] { false, osu_colour.TeamColourBlue },
|
||||
new object?[] { true, osu_colour.TeamColourBlue },
|
||||
};
|
||||
|
||||
[TestCaseSource(nameof(leaderboard_variants))]
|
||||
public void TestVariants(bool tracked, Color4? teamColour)
|
||||
{
|
||||
AddStep("show", () =>
|
||||
{
|
||||
GameplayLeaderboardScore score = new GameplayLeaderboardScore(user, tracked, totalScore)
|
||||
{
|
||||
Position = { BindTarget = position },
|
||||
HasQuit = { BindTarget = quit },
|
||||
TeamColour = teamColour,
|
||||
};
|
||||
Child = new Container
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Width = 250,
|
||||
Child = new DrawableGameplayLeaderboardScore(score)
|
||||
{
|
||||
Expanded = { BindTarget = expanded },
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
34
osu.Game.Tests/Visual/Matchmaking/MatchmakingTestScene.cs
Normal file
34
osu.Game.Tests/Visual/Matchmaking/MatchmakingTestScene.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
// 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.Game.Overlays;
|
||||
using osu.Game.Screens;
|
||||
using osu.Game.Screens.OnlinePlay.Matchmaking.Match;
|
||||
using osu.Game.Tests.Visual.Multiplayer;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Matchmaking
|
||||
{
|
||||
public abstract partial class MatchmakingTestScene : MultiplayerTestScene
|
||||
{
|
||||
protected override Container<Drawable> Content { get; }
|
||||
|
||||
[Cached]
|
||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Plum);
|
||||
|
||||
protected MatchmakingTestScene()
|
||||
{
|
||||
BackgroundScreenStack backgroundStack;
|
||||
|
||||
base.Content.AddRange(new Drawable[]
|
||||
{
|
||||
backgroundStack = new BackgroundScreenStack(),
|
||||
Content = new Container { RelativeSizeAxes = Axes.Both }
|
||||
});
|
||||
|
||||
backgroundStack.Push(new MatchmakingBackgroundScreen(colourProvider));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@ using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
@@ -17,12 +16,11 @@ using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect;
|
||||
using osu.Game.Tests.Visual.OnlinePlay;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Matchmaking
|
||||
{
|
||||
public partial class TestSceneBeatmapSelectGrid : OnlinePlayTestScene
|
||||
public partial class TestSceneBeatmapSelectGrid : MatchmakingTestScene
|
||||
{
|
||||
private MatchmakingPlaylistItem[] items = null!;
|
||||
|
||||
@@ -131,7 +129,7 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
{
|
||||
var (candidateItems, finalItem) = pickRandomItems(5);
|
||||
|
||||
grid.RollAndDisplayFinalBeatmap(candidateItems, finalItem);
|
||||
grid.RollAndDisplayFinalBeatmap(candidateItems, finalItem, finalItem);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -160,7 +158,7 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
grid.ArrangeItemsForRollAnimation(duration: 0, stagger: 0);
|
||||
grid.PlayRollAnimation(finalItem, duration: 0);
|
||||
|
||||
Scheduler.AddDelayed(() => grid.PresentRolledBeatmap(finalItem), 500);
|
||||
Scheduler.AddDelayed(() => grid.PresentRolledBeatmap(finalItem, finalItem), 500);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -175,7 +173,25 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
grid.ArrangeItemsForRollAnimation(duration: 0, stagger: 0);
|
||||
grid.PlayRollAnimation(finalItem, duration: 0);
|
||||
|
||||
Scheduler.AddDelayed(() => grid.PresentUnanimouslyChosenBeatmap(finalItem), 500);
|
||||
Scheduler.AddDelayed(() => grid.PresentUnanimouslyChosenBeatmap(finalItem, finalItem), 500);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPresentRandomItem()
|
||||
{
|
||||
AddStep("present random item panel", () =>
|
||||
{
|
||||
var (candidateItems, finalItem) = pickRandomItems(4);
|
||||
|
||||
grid.TransferCandidatePanelsToRollContainer(candidateItems.Append(-1).ToArray(), duration: 0);
|
||||
grid.ArrangeItemsForRollAnimation(duration: 0, stagger: 0);
|
||||
grid.PlayRollAnimation(-1, duration: 0);
|
||||
|
||||
Scheduler.AddDelayed(() =>
|
||||
{
|
||||
grid.PresentRolledBeatmap(-1, finalItem);
|
||||
}, 500);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -220,20 +236,19 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPresentRandomItem()
|
||||
public void TestRollAnimationFinalRandom()
|
||||
{
|
||||
AddStep("present random item panel", () =>
|
||||
AddStep("play animation", () =>
|
||||
{
|
||||
grid.TransferCandidatePanelsToRollContainer(pickRandomItems(4).candidateItems.Append(-1).ToArray(), duration: 0);
|
||||
grid.ArrangeItemsForRollAnimation(duration: 0, stagger: 0);
|
||||
grid.PlayRollAnimation(-1, duration: 0);
|
||||
(long[] candidateItems, _) = pickRandomItems(5);
|
||||
|
||||
Scheduler.AddDelayed(() => grid.PresentUnanimouslyChosenBeatmap(-1), 500);
|
||||
candidateItems = candidateItems.Append(-1).ToArray();
|
||||
long finalItem = items.First(i => !candidateItems.Contains(i.ID)).ID;
|
||||
|
||||
grid.RollAndDisplayFinalBeatmap(candidateItems, -1, finalItem);
|
||||
});
|
||||
|
||||
AddWaitStep("wait for animation", 5);
|
||||
|
||||
AddStep("reveal beatmap", () => grid.RevealRandomItem(items[RNG.Next(items.Length)].PlaylistItem));
|
||||
AddWaitStep("wait for animation", 10);
|
||||
}
|
||||
|
||||
private (long[] candidateItems, long finalItem) pickRandomItems(int count)
|
||||
|
||||
@@ -12,11 +12,10 @@ using osu.Game.Online.Rooms;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect;
|
||||
using osu.Game.Tests.Visual.Multiplayer;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Matchmaking
|
||||
{
|
||||
public partial class TestSceneBeatmapSelectPanel : MultiplayerTestScene
|
||||
public partial class TestSceneBeatmapSelectPanel : MatchmakingTestScene
|
||||
{
|
||||
[Cached]
|
||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
|
||||
@@ -99,7 +98,7 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
|
||||
AddToggleStep("allow selection", value => panel!.AllowSelection = value);
|
||||
|
||||
AddStep("reveal beatmap", () => panel!.RevealBeatmap(CreateAPIBeatmap(), []));
|
||||
AddStep("reveal beatmap", () => panel!.PresentAsChosenBeatmap(new MatchmakingPlaylistItem(new MultiplayerPlaylistItem(), CreateAPIBeatmap(), [])));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -11,7 +11,7 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Matchmaking
|
||||
{
|
||||
public partial class TestSceneMatchmakingChatDisplay : ScreenTestScene
|
||||
public partial class TestSceneMatchmakingChatDisplay : MatchmakingTestScene
|
||||
{
|
||||
private MatchmakingChatDisplay? chat;
|
||||
|
||||
|
||||
@@ -22,11 +22,11 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
{
|
||||
Value =
|
||||
[
|
||||
new MatchmakingPool { Id = 0, RulesetId = 0, Name = "osu!" },
|
||||
new MatchmakingPool { Id = 1, RulesetId = 1, Name = "osu!taiko" },
|
||||
new MatchmakingPool { Id = 2, RulesetId = 2, Name = "osu!catch" },
|
||||
new MatchmakingPool { Id = 3, RulesetId = 3, Variant = 4, Name = "osu!mania (4k)" },
|
||||
new MatchmakingPool { Id = 4, RulesetId = 3, Variant = 7, Name = "osu!mania (7k)" },
|
||||
new MatchmakingPool { Id = 0, RulesetId = 0, Name = "Free-for-all" },
|
||||
new MatchmakingPool { Id = 1, RulesetId = 1, Name = "1v1" },
|
||||
new MatchmakingPool { Id = 2, RulesetId = 2, Name = "1v1" },
|
||||
new MatchmakingPool { Id = 3, RulesetId = 3, Variant = 4, Name = "1v1" },
|
||||
new MatchmakingPool { Id = 4, RulesetId = 3, Variant = 7, Name = "1v1" },
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
@@ -110,6 +110,7 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
|
||||
state.CandidateItems = beatmaps.Select(b => b.ID).ToArray();
|
||||
state.CandidateItem = beatmaps[0].ID;
|
||||
state.GameplayItem = beatmaps[0].ID;
|
||||
}, waitTime: 35);
|
||||
|
||||
changeStage(MatchmakingStage.WaitingForClientsBeatmapDownload);
|
||||
|
||||
@@ -3,11 +3,10 @@
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Screens.OnlinePlay.Matchmaking.Match.Results;
|
||||
using osu.Game.Tests.Visual.Multiplayer;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Matchmaking
|
||||
{
|
||||
public partial class TestScenePanelRoomAward : MultiplayerTestScene
|
||||
public partial class TestScenePanelRoomAward : MatchmakingTestScene
|
||||
{
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
|
||||
@@ -6,16 +6,16 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect;
|
||||
using osu.Game.Tests.Visual.Multiplayer;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Matchmaking
|
||||
{
|
||||
public partial class TestScenePickScreen : MultiplayerTestScene
|
||||
public partial class TestScenePickScreen : MatchmakingTestScene
|
||||
{
|
||||
private readonly IReadOnlyList<APIUser> users = new[]
|
||||
{
|
||||
@@ -104,8 +104,28 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
long[] candidateItems = selectedItems.ToArray();
|
||||
long finalItem = candidateItems[Random.Shared.Next(candidateItems.Length)];
|
||||
|
||||
screen.RollFinalBeatmap(candidateItems, finalItem);
|
||||
screen.RollFinalBeatmap(candidateItems, finalItem, finalItem);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestExpiredBeatmapNotShown()
|
||||
{
|
||||
SubScreenBeatmapSelect screen = null!;
|
||||
|
||||
AddStep("add screen with expired items", () =>
|
||||
{
|
||||
MultiplayerClient.ClientRoom!.Playlist =
|
||||
[
|
||||
new MultiplayerPlaylistItem(items[0]) { Expired = true },
|
||||
new MultiplayerPlaylistItem(items[1])
|
||||
];
|
||||
|
||||
Child = new ScreenStack(screen = new SubScreenBeatmapSelect());
|
||||
});
|
||||
|
||||
AddUntilStep("items displayed", () => screen.ChildrenOfType<MatchmakingSelectPanelBeatmap>().Any());
|
||||
AddAssert("expired item not shown", () => screen.ChildrenOfType<MatchmakingSelectPanelBeatmap>().Count(), () => Is.EqualTo(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,12 +11,11 @@ using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Multiplayer.MatchTypes.Matchmaking;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Screens.OnlinePlay.Matchmaking.Match;
|
||||
using osu.Game.Tests.Visual.Multiplayer;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Matchmaking
|
||||
{
|
||||
public partial class TestScenePlayerPanel : MultiplayerTestScene
|
||||
public partial class TestScenePlayerPanel : MatchmakingTestScene
|
||||
{
|
||||
private PlayerPanel panel = null!;
|
||||
|
||||
|
||||
@@ -15,12 +15,11 @@ using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Multiplayer.MatchTypes.Matchmaking;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Screens.OnlinePlay.Matchmaking.Match;
|
||||
using osu.Game.Tests.Visual.Multiplayer;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Matchmaking
|
||||
{
|
||||
public partial class TestScenePlayerPanelOverlay : MultiplayerTestScene
|
||||
public partial class TestScenePlayerPanelOverlay : MatchmakingTestScene
|
||||
{
|
||||
private PlayerPanelOverlay list = null!;
|
||||
|
||||
|
||||
@@ -11,12 +11,11 @@ using osu.Game.Online.Multiplayer.MatchTypes.Matchmaking;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.OnlinePlay.Matchmaking.Match.Results;
|
||||
using osu.Game.Tests.Visual.Multiplayer;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Matchmaking
|
||||
{
|
||||
public partial class TestSceneResultsScreen : MultiplayerTestScene
|
||||
public partial class TestSceneResultsScreen : MatchmakingTestScene
|
||||
{
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Utils;
|
||||
@@ -14,12 +15,11 @@ using osu.Game.Online.Rooms;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.OnlinePlay.Matchmaking.Match.RoundResults;
|
||||
using osu.Game.Tests.Visual.Multiplayer;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Matchmaking
|
||||
{
|
||||
public partial class TestSceneRoundResultsScreen : MultiplayerTestScene
|
||||
public partial class TestSceneRoundResultsScreen : MatchmakingTestScene
|
||||
{
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
@@ -27,8 +27,15 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
|
||||
AddStep("join room", () => JoinRoom(CreateDefaultRoom(MatchType.Matchmaking)));
|
||||
WaitForJoined();
|
||||
}
|
||||
|
||||
setupRequestHandler();
|
||||
[TestCase(2)]
|
||||
[TestCase(4)]
|
||||
[TestCase(8)]
|
||||
[TestCase(16)]
|
||||
public void TestDisplayScores(int scoreCount)
|
||||
{
|
||||
setupRequestHandler(scoreCount);
|
||||
|
||||
AddStep("load screen", () =>
|
||||
{
|
||||
@@ -41,7 +48,7 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
});
|
||||
}
|
||||
|
||||
private void setupRequestHandler()
|
||||
private void setupRequestHandler(int scoreCount)
|
||||
{
|
||||
AddStep("setup request handler", () =>
|
||||
{
|
||||
@@ -72,7 +79,7 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
case IndexPlaylistScoresRequest index:
|
||||
var result = new IndexedMultiplayerScores();
|
||||
|
||||
for (int i = 0; i < 8; ++i)
|
||||
for (int i = 0; i < scoreCount; ++i)
|
||||
{
|
||||
result.Scores.Add(new MultiplayerScore
|
||||
{
|
||||
|
||||
@@ -9,11 +9,10 @@ using osu.Game.Online.Multiplayer.MatchTypes.Matchmaking;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens.OnlinePlay.Matchmaking.Match;
|
||||
using osu.Game.Tests.Visual.Multiplayer;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Matchmaking
|
||||
{
|
||||
public partial class TestSceneStageDisplay : MultiplayerTestScene
|
||||
public partial class TestSceneStageDisplay : MatchmakingTestScene
|
||||
{
|
||||
[Cached]
|
||||
protected readonly OverlayColourProvider ColourProvider = new OverlayColourProvider(OverlayColourScheme.Plum);
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
// 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.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
@@ -33,6 +35,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new MultiplayerSkipOverlay(120000)
|
||||
{
|
||||
RequestSkip = () => MultiplayerClient.VoteToSkipIntro().WaitSafely(),
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -47,26 +52,83 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
int i2 = i;
|
||||
int userId = i;
|
||||
|
||||
AddStep($"join user {i2}", () =>
|
||||
AddStep($"join user {userId}", () =>
|
||||
{
|
||||
MultiplayerClient.AddUser(new APIUser
|
||||
{
|
||||
Id = i2,
|
||||
Username = $"User {i2}"
|
||||
Id = userId,
|
||||
Username = $"User {userId}"
|
||||
});
|
||||
|
||||
MultiplayerClient.ChangeUserState(i2, MultiplayerUserState.Playing);
|
||||
MultiplayerClient.ChangeUserState(userId, MultiplayerUserState.Playing);
|
||||
});
|
||||
}
|
||||
|
||||
AddStep("local user votes", () => MultiplayerClient.VoteToSkipIntro().WaitSafely());
|
||||
AddStep("user 0 votes", () => MultiplayerClient.UserVoteToSkipIntro(0).WaitSafely());
|
||||
AddStep("local user votes", () => this.ChildrenOfType<MultiplayerSkipOverlay.Button>().Single().TriggerClick());
|
||||
AddStep("user 1 votes", () => MultiplayerClient.UserVoteToSkipIntro(1).WaitSafely());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLeavingBeforeLocalVote()
|
||||
{
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
int i2 = i;
|
||||
AddStep($"user {i2} votes", () => MultiplayerClient.UserVoteToSkipIntro(i2).WaitSafely());
|
||||
int userId = i;
|
||||
|
||||
AddStep($"join user {userId}", () =>
|
||||
{
|
||||
MultiplayerClient.AddUser(new APIUser
|
||||
{
|
||||
Id = userId,
|
||||
Username = $"User {userId}"
|
||||
});
|
||||
|
||||
MultiplayerClient.ChangeUserState(userId, MultiplayerUserState.Playing);
|
||||
});
|
||||
}
|
||||
|
||||
AddStep("user 0 votes", () => MultiplayerClient.UserVoteToSkipIntro(0).WaitSafely());
|
||||
AddStep("user 1 leaves", () => MultiplayerClient.RemoveUser(new APIUser { Id = 1 }));
|
||||
AddStep("user 2 leaves", () => MultiplayerClient.RemoveUser(new APIUser { Id = 2 }));
|
||||
AddStep("user 3 leaves", () => MultiplayerClient.RemoveUser(new APIUser { Id = 3 }));
|
||||
AddStep("user 0 leaves", () => MultiplayerClient.RemoveUser(new APIUser { Id = 0 }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLeavingAfterLocalVote()
|
||||
{
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
int userId = i;
|
||||
|
||||
AddStep($"join user {userId}", () =>
|
||||
{
|
||||
MultiplayerClient.AddUser(new APIUser
|
||||
{
|
||||
Id = userId,
|
||||
Username = $"User {userId}"
|
||||
});
|
||||
|
||||
MultiplayerClient.ChangeUserState(userId, MultiplayerUserState.Playing);
|
||||
});
|
||||
}
|
||||
|
||||
AddStep("local user votes", () => this.ChildrenOfType<MultiplayerSkipOverlay.Button>().Single().TriggerClick());
|
||||
AddStep("user 0 votes", () => MultiplayerClient.UserVoteToSkipIntro(0).WaitSafely());
|
||||
AddStep("user 1 leaves", () => MultiplayerClient.RemoveUser(new APIUser { Id = 1 }));
|
||||
AddStep("user 2 leaves", () => MultiplayerClient.RemoveUser(new APIUser { Id = 2 }));
|
||||
AddStep("user 3 leaves", () => MultiplayerClient.RemoveUser(new APIUser { Id = 3 }));
|
||||
AddStep("user 0 leaves", () => MultiplayerClient.RemoveUser(new APIUser { Id = 0 }));
|
||||
}
|
||||
|
||||
public partial class TestMultiplayerSkipOverlay : MultiplayerSkipOverlay
|
||||
{
|
||||
public TestMultiplayerSkipOverlay()
|
||||
: base(120000)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,5 +146,165 @@ namespace osu.Game.Tests.Visual.Online
|
||||
checkCount++;
|
||||
}, 10);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAlternatingBackgroundDoesNotChangeAtMaxHistory()
|
||||
{
|
||||
AddStep("fill up the channel", () =>
|
||||
{
|
||||
for (int i = 0; i < Channel.MAX_HISTORY; i++)
|
||||
{
|
||||
channel.AddNewMessages(new Message
|
||||
{
|
||||
ChannelId = channel.Id,
|
||||
Content = $"Message {i}",
|
||||
Timestamp = DateTimeOffset.Now,
|
||||
Sender = new APIUser
|
||||
{
|
||||
Id = 3,
|
||||
Username = "LocalUser " + RNG.Next(0, int.MaxValue - 100).ToString("N")
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
AddUntilStep($"{Channel.MAX_HISTORY} messages present", () => drawableChannel.ChildrenOfType<ChatLine>().Count(), () => Is.EqualTo(Channel.MAX_HISTORY));
|
||||
|
||||
ChatLine? lastLine = null;
|
||||
bool lastLineAlternatingBackground = false;
|
||||
|
||||
AddStep("grab last line", () =>
|
||||
{
|
||||
lastLine = drawableChannel.ChildrenOfType<ChatLine>().Last();
|
||||
lastLineAlternatingBackground = lastLine.AlternatingBackground;
|
||||
});
|
||||
|
||||
AddStep("add another message", () => channel.AddNewMessages(new Message
|
||||
{
|
||||
ChannelId = channel.Id,
|
||||
Content = "One final message",
|
||||
Timestamp = DateTimeOffset.Now,
|
||||
Sender = new APIUser
|
||||
{
|
||||
Id = 3,
|
||||
Username = "LocalUser " + RNG.Next(0, int.MaxValue - 100).ToString("N")
|
||||
}
|
||||
}));
|
||||
|
||||
AddAssert("second-last message has same background", () => lastLine!.AlternatingBackground, () => Is.EqualTo(lastLineAlternatingBackground));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAlternatingBackgroundUpdatedOnRemoval()
|
||||
{
|
||||
AddStep("add 3 messages", () =>
|
||||
{
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
channel.AddNewMessages(new Message
|
||||
{
|
||||
ChannelId = channel.Id,
|
||||
Content = $"Message {i}",
|
||||
Timestamp = DateTimeOffset.Now,
|
||||
Sender = new APIUser
|
||||
{
|
||||
Id = i,
|
||||
Username = "LocalUser " + RNG.Next(0, int.MaxValue - 100).ToString("N")
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
AddUntilStep("3 messages present", () => drawableChannel.ChildrenOfType<ChatLine>().Count(), () => Is.EqualTo(3));
|
||||
assertAlternatingBackground(0, false);
|
||||
assertAlternatingBackground(1, true);
|
||||
assertAlternatingBackground(2, false);
|
||||
|
||||
AddStep("remove middle message", () => channel.RemoveMessagesFromUser(1));
|
||||
AddUntilStep("2 messages present", () => drawableChannel.ChildrenOfType<ChatLine>().Count(), () => Is.EqualTo(2));
|
||||
assertAlternatingBackground(0, true);
|
||||
assertAlternatingBackground(1, false);
|
||||
|
||||
void assertAlternatingBackground(int lineIndex, bool shouldBeAlternating)
|
||||
=> AddAssert($"line {lineIndex} {(shouldBeAlternating ? "has" : "does not have")} alternating background",
|
||||
() => drawableChannel.ChildrenOfType<ChatLine>().ElementAt(lineIndex).AlternatingBackground,
|
||||
() => Is.EqualTo(shouldBeAlternating));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestTimestampsUpdateOnRemoval()
|
||||
{
|
||||
AddStep("add 3 messages", () =>
|
||||
{
|
||||
channel.AddNewMessages(
|
||||
new Message
|
||||
{
|
||||
ChannelId = channel.Id,
|
||||
Content = "Message 0",
|
||||
Timestamp = new DateTimeOffset(2022, 11, 21, 20, 0, 0, TimeSpan.Zero),
|
||||
Sender = new APIUser
|
||||
{
|
||||
Id = 0,
|
||||
Username = "LocalUser " + RNG.Next(0, int.MaxValue - 100).ToString("N")
|
||||
}
|
||||
},
|
||||
new Message
|
||||
{
|
||||
ChannelId = channel.Id,
|
||||
Content = "Message 1",
|
||||
Timestamp = new DateTimeOffset(2022, 11, 21, 20, 0, 0, TimeSpan.Zero).AddSeconds(1),
|
||||
Sender = new APIUser
|
||||
{
|
||||
Id = 1,
|
||||
Username = "LocalUser " + RNG.Next(0, int.MaxValue - 100).ToString("N")
|
||||
}
|
||||
},
|
||||
new Message
|
||||
{
|
||||
ChannelId = channel.Id,
|
||||
Content = "Message 2",
|
||||
Timestamp = new DateTimeOffset(2022, 11, 21, 20, 0, 0, TimeSpan.Zero).AddMinutes(1),
|
||||
Sender = new APIUser
|
||||
{
|
||||
Id = 2,
|
||||
Username = "LocalUser " + RNG.Next(0, int.MaxValue - 100).ToString("N")
|
||||
}
|
||||
},
|
||||
new Message
|
||||
{
|
||||
ChannelId = channel.Id,
|
||||
Content = "Message 3",
|
||||
Timestamp = new DateTimeOffset(2022, 11, 21, 20, 0, 0, TimeSpan.Zero).AddMinutes(1).AddSeconds(1),
|
||||
Sender = new APIUser
|
||||
{
|
||||
Id = 3,
|
||||
Username = "LocalUser " + RNG.Next(0, int.MaxValue - 100).ToString("N")
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
AddUntilStep("4 messages present", () => drawableChannel.ChildrenOfType<ChatLine>().Count(), () => Is.EqualTo(4));
|
||||
assertTimestamp(0, true);
|
||||
assertTimestamp(1, false);
|
||||
assertTimestamp(2, true);
|
||||
assertTimestamp(3, false);
|
||||
|
||||
AddStep("remove message 0", () => channel.RemoveMessagesFromUser(0));
|
||||
AddUntilStep("3 messages present", () => drawableChannel.ChildrenOfType<ChatLine>().Count(), () => Is.EqualTo(3));
|
||||
assertTimestamp(0, true);
|
||||
assertTimestamp(1, true);
|
||||
assertTimestamp(2, false);
|
||||
|
||||
AddStep("remove message 2", () => channel.RemoveMessagesFromUser(2));
|
||||
AddUntilStep("2 messages present", () => drawableChannel.ChildrenOfType<ChatLine>().Count(), () => Is.EqualTo(2));
|
||||
assertTimestamp(0, true);
|
||||
assertTimestamp(1, true);
|
||||
|
||||
void assertTimestamp(int lineIndex, bool shouldHaveTimestamp)
|
||||
=> AddAssert($"line {lineIndex} {(shouldHaveTimestamp ? "has" : "does not have")} timestamp",
|
||||
() => drawableChannel.ChildrenOfType<ChatLine>().ElementAt(lineIndex).RequiresTimestamp,
|
||||
() => Is.EqualTo(shouldHaveTimestamp));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
scrollToAndStartBinding("Increase volume");
|
||||
AddStep("press shift", () => InputManager.PressKey(Key.ShiftLeft));
|
||||
AddStep("release shift", () => InputManager.ReleaseKey(Key.ShiftLeft));
|
||||
checkBinding("Increase volume", "LShift");
|
||||
checkBinding("Increase volume", "Shift");
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -77,7 +77,7 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
AddStep("press shift", () => InputManager.PressKey(Key.ShiftLeft));
|
||||
AddStep("press k", () => InputManager.Key(Key.K));
|
||||
AddStep("release shift", () => InputManager.ReleaseKey(Key.ShiftLeft));
|
||||
checkBinding("Increase volume", "LShift-K");
|
||||
checkBinding("Increase volume", "Shift-K");
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -121,7 +121,7 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
|
||||
AddStep("schedule button clicks", () =>
|
||||
{
|
||||
var clearButton = firstRow.ChildrenOfType<KeyBindingRow.ClearButton>().Single();
|
||||
var clearButton = firstRow.ChildrenOfType<DangerousRoundedButton>().Single();
|
||||
|
||||
InputManager.MoveMouseTo(clearButton);
|
||||
|
||||
@@ -179,7 +179,7 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
{
|
||||
AddStep("click clear button", () =>
|
||||
{
|
||||
var clearButton = multiBindingRow.ChildrenOfType<KeyBindingRow.ClearButton>().Single();
|
||||
var clearButton = multiBindingRow.ChildrenOfType<DangerousRoundedButton>().Single();
|
||||
|
||||
InputManager.MoveMouseTo(clearButton);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
@@ -386,7 +386,7 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
AddStep("clear binding", () =>
|
||||
{
|
||||
var row = panel.ChildrenOfType<KeyBindingRow>().First(r => r.ChildrenOfType<OsuSpriteText>().Any(s => s.Text.ToString() == "Left (centre)"));
|
||||
row.ChildrenOfType<KeyBindingRow.ClearButton>().Single().TriggerClick();
|
||||
row.ChildrenOfType<DangerousRoundedButton>().Single().TriggerClick();
|
||||
});
|
||||
scrollToAndStartBinding("Left (rim)");
|
||||
AddStep("bind M1", () => InputManager.Click(MouseButton.Left));
|
||||
@@ -462,7 +462,7 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
AddStep("clear binding", () =>
|
||||
{
|
||||
var row = panel.ChildrenOfType<KeyBindingRow>().First(r => r.ChildrenOfType<OsuSpriteText>().Any(s => s.Text.ToString() == "Left (centre)"));
|
||||
row.ChildrenOfType<KeyBindingRow.ClearButton>().Single().TriggerClick();
|
||||
row.ChildrenOfType<DangerousRoundedButton>().Single().TriggerClick();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ using osu.Game.Screens.Menu;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Ranking;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.Select.Filter;
|
||||
using osu.Game.Screens.Select.Leaderboards;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osu.Game.Tests.Resources;
|
||||
@@ -144,41 +143,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
void onScreenPushed(IScreen lastScreen, IScreen newScreen) => screensPushed.Add(lastScreen);
|
||||
}
|
||||
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public void TestHoveringLeftSideReexpandsGroupSelectionIsIn(bool mouseOverPanel)
|
||||
{
|
||||
ImportBeatmapForRuleset(0);
|
||||
|
||||
LoadSongSelect();
|
||||
SortAndGroupBy(SortMode.Difficulty, GroupMode.Difficulty);
|
||||
|
||||
AddStep("move mouse to carousel", () => InputManager.MoveMouseTo(Carousel));
|
||||
|
||||
AddUntilStep("expanded group is below 1 star",
|
||||
() => (Carousel.ChildrenOfType<PanelGroupStarDifficulty>().SingleOrDefault(p => p.Expanded.Value)?.Item?.Model as StarDifficultyGroupDefinition)?.Difficulty.Stars,
|
||||
() => Is.EqualTo(0));
|
||||
|
||||
AddStep("select next group", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.ShiftLeft);
|
||||
InputManager.Key(Key.Right);
|
||||
InputManager.ReleaseKey(Key.ShiftLeft);
|
||||
});
|
||||
AddUntilStep("expanded group is 3 star",
|
||||
() => (Carousel.ChildrenOfType<PanelGroupStarDifficulty>().SingleOrDefault(p => p.Expanded.Value)?.Item?.Model as StarDifficultyGroupDefinition)?.Difficulty.Stars,
|
||||
() => Is.EqualTo(3));
|
||||
|
||||
if (mouseOverPanel)
|
||||
AddStep("move mouse over left panel", () => InputManager.MoveMouseTo(this.ChildrenOfType<BeatmapTitleWedge>().Single()));
|
||||
else
|
||||
AddStep("move mouse to left side container", () => InputManager.MoveMouseTo(this.ChildrenOfType<Screens.Select.SongSelect.LeftSideInteractionContainer>().Single()));
|
||||
|
||||
AddUntilStep("expanded group is below 1 star",
|
||||
() => (Carousel.ChildrenOfType<PanelGroupStarDifficulty>().Single(p => p.Expanded.Value).Item?.Model as StarDifficultyGroupDefinition)?.Difficulty.Stars,
|
||||
() => Is.EqualTo(0));
|
||||
}
|
||||
|
||||
#region Hotkeys
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
#nullable disable
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Settings.Sections;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
@@ -19,9 +19,12 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
private TestExpandingContainer container;
|
||||
private SettingsToolboxGroup toolboxGroup;
|
||||
|
||||
private ExpandableSlider<float, SizeSlider<float>> slider1;
|
||||
private ExpandableSlider<float> slider1;
|
||||
private ExpandableSlider<double> slider2;
|
||||
|
||||
[Cached]
|
||||
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
{
|
||||
@@ -36,7 +39,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
Width = 1,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
slider1 = new ExpandableSlider<float, SizeSlider<float>>
|
||||
slider1 = new ExpandableSlider<float>
|
||||
{
|
||||
Current = new BindableFloat
|
||||
{
|
||||
@@ -62,13 +65,13 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
|
||||
slider1.Current.BindValueChanged(v =>
|
||||
{
|
||||
slider1.ExpandedLabelText = $"Slider One ({v.NewValue:0.##x})";
|
||||
slider1.ExpandedLabelText = "Slider One";
|
||||
slider1.ContractedLabelText = $"S. 1. ({v.NewValue:0.##x})";
|
||||
}, true);
|
||||
|
||||
slider2.Current.BindValueChanged(v =>
|
||||
{
|
||||
slider2.ExpandedLabelText = $"Slider Two ({v.NewValue:N2})";
|
||||
slider2.ExpandedLabelText = "Slider Two";
|
||||
slider2.ContractedLabelText = $"S. 2. ({v.NewValue:N2})";
|
||||
}, true);
|
||||
});
|
||||
|
||||
@@ -1,20 +1,24 @@
|
||||
// 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.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
public partial class TestSceneFormSliderBar : OsuTestScene
|
||||
public partial class TestSceneFormSliderBar : OsuManualInputManagerTestScene
|
||||
{
|
||||
[Cached]
|
||||
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
|
||||
@@ -59,5 +63,49 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
slider.TransferValueOnCommit = b;
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNubDoubleClickRevertToDefault()
|
||||
{
|
||||
FormSliderBar<float> slider = null!;
|
||||
|
||||
AddStep("create content", () =>
|
||||
{
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Width = 0.5f,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(10),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
slider = new FormSliderBar<float>
|
||||
{
|
||||
Caption = "Slider",
|
||||
Current = new BindableFloat
|
||||
{
|
||||
MinValue = 0,
|
||||
MaxValue = 10,
|
||||
Precision = 0.1f,
|
||||
Default = 5f,
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
});
|
||||
AddStep("set slider to 1", () => slider.Current.Value = 1);
|
||||
|
||||
AddStep("move mouse to nub", () => InputManager.MoveMouseTo(slider.ChildrenOfType<Circle>().Single()));
|
||||
|
||||
AddStep("double click nub", () =>
|
||||
{
|
||||
InputManager.Click(MouseButton.Left);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddAssert("slider is default", () => slider.Current.IsDefault);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,10 @@ using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps.Legacy;
|
||||
using osu.Game.Rulesets.Catch;
|
||||
using osu.Game.Rulesets.Mania;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Taiko;
|
||||
using osu.Game.Tournament.Components;
|
||||
using osu.Game.Tournament.Models;
|
||||
|
||||
@@ -52,6 +56,7 @@ namespace osu.Game.Tournament.Tests.Components
|
||||
beatmap.ApproachRate = 6.8f;
|
||||
beatmap.OverallDifficulty = 5.5f;
|
||||
beatmap.StarRating = 4.56f;
|
||||
beatmap.DrainRate = 1.23f;
|
||||
beatmap.Length = 123456;
|
||||
beatmap.BPM = 133;
|
||||
beatmap.OnlineID = ladderBeatmap.OnlineID;
|
||||
@@ -61,11 +66,18 @@ namespace osu.Game.Tournament.Tests.Components
|
||||
|
||||
AddStep("set mods to HR", () => songBar.Mods = LegacyMods.HardRock);
|
||||
AddStep("set mods to DT", () => songBar.Mods = LegacyMods.DoubleTime);
|
||||
AddStep("set mods to HDHRDT", () => songBar.Mods = LegacyMods.Hidden | LegacyMods.HardRock | LegacyMods.DoubleTime);
|
||||
|
||||
AddStep("unset mods", () => songBar.Mods = LegacyMods.None);
|
||||
|
||||
AddToggleStep("toggle expanded", expanded => songBar.Expanded = expanded);
|
||||
|
||||
AddStep("set null beatmap", () => songBar.Beatmap = null);
|
||||
|
||||
AddStep("set ruleset to osu", () => Ruleset.Value = new OsuRuleset().RulesetInfo);
|
||||
AddStep("set ruleset to taiko", () => Ruleset.Value = new TaikoRuleset().RulesetInfo);
|
||||
AddStep("set ruleset to catch", () => Ruleset.Value = new CatchRuleset().RulesetInfo);
|
||||
AddStep("set ruleset to mania", () => Ruleset.Value = new ManiaRuleset().RulesetInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Chat;
|
||||
using osu.Game.Overlays.Chat;
|
||||
@@ -61,14 +63,33 @@ namespace osu.Game.Tournament.Tests.Components
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
});
|
||||
|
||||
chatDisplay.Channel.Value = testChannel;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
AddStep("set up API", () =>
|
||||
{
|
||||
((DummyAPIAccess)API).HandleRequest = req =>
|
||||
{
|
||||
switch (req)
|
||||
{
|
||||
case JoinChannelRequest joinChannelRequest:
|
||||
joinChannelRequest.TriggerSuccess();
|
||||
return true;
|
||||
|
||||
case LeaveChannelRequest leaveChannelRequest:
|
||||
leaveChannelRequest.TriggerSuccess();
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
});
|
||||
AddStep("set channel", () => chatDisplay.Channel.Value = testChannel);
|
||||
|
||||
AddStep("message from admin", () => testChannel.AddNewMessages(new Message(nextMessageId())
|
||||
{
|
||||
Sender = admin,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
@@ -14,6 +15,7 @@ using osu.Game.Extensions;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Models;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Screens.Menu;
|
||||
using osu.Game.Utils;
|
||||
using osuTK;
|
||||
@@ -123,27 +125,19 @@ namespace osu.Game.Tournament.Components
|
||||
},
|
||||
};
|
||||
|
||||
double bpm = beatmap.BPM;
|
||||
double length = beatmap.Length;
|
||||
string hardRockExtra = "";
|
||||
var rulesetInstance = ruleset.Value.CreateInstance();
|
||||
|
||||
var convertedMods = rulesetInstance.ConvertFromLegacyMods(mods).ToList();
|
||||
var adjustedDifficulty = rulesetInstance.GetAdjustedDisplayDifficulty(beatmap, convertedMods);
|
||||
|
||||
double rate = ModUtils.CalculateRateWithMods(convertedMods);
|
||||
double bpm = FormatUtils.RoundBPM(beatmap.BPM, rate);
|
||||
double length = beatmap.Length / rate;
|
||||
|
||||
string srExtra = "";
|
||||
|
||||
float ar = beatmap.Difficulty.ApproachRate;
|
||||
|
||||
if ((mods & LegacyMods.HardRock) > 0)
|
||||
if (convertedMods.Any(x => x is ModHardRock) || convertedMods.Any(x => x is ModDoubleTime))
|
||||
{
|
||||
hardRockExtra = "*";
|
||||
srExtra = "*";
|
||||
}
|
||||
|
||||
if ((mods & LegacyMods.DoubleTime) > 0)
|
||||
{
|
||||
// temporary local calculation (taken from OsuDifficultyCalculator)
|
||||
double preempt = (int)IBeatmapDifficultyInfo.DifficultyRange(ar, 1800, 1200, 450) / 1.5;
|
||||
ar = (float)(preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5);
|
||||
|
||||
bpm *= 1.5f;
|
||||
length /= 1.5f;
|
||||
srExtra = "*";
|
||||
}
|
||||
|
||||
@@ -154,9 +148,9 @@ namespace osu.Game.Tournament.Components
|
||||
default:
|
||||
stats = new (string heading, string content)[]
|
||||
{
|
||||
("CS", $"{beatmap.Difficulty.CircleSize:0.#}{hardRockExtra}"),
|
||||
("AR", $"{ar:0.#}{hardRockExtra}"),
|
||||
("OD", $"{beatmap.Difficulty.OverallDifficulty:0.#}{hardRockExtra}"),
|
||||
("CS", $"{adjustedDifficulty.CircleSize:0.#}"),
|
||||
("AR", $"{adjustedDifficulty.ApproachRate:0.#}"),
|
||||
("OD", $"{adjustedDifficulty.OverallDifficulty:0.#}"),
|
||||
};
|
||||
break;
|
||||
|
||||
@@ -164,16 +158,16 @@ namespace osu.Game.Tournament.Components
|
||||
case 3:
|
||||
stats = new (string heading, string content)[]
|
||||
{
|
||||
("OD", $"{beatmap.Difficulty.OverallDifficulty:0.#}{hardRockExtra}"),
|
||||
("HP", $"{beatmap.Difficulty.DrainRate:0.#}{hardRockExtra}")
|
||||
("OD", $"{adjustedDifficulty.OverallDifficulty:0.#}"),
|
||||
("HP", $"{adjustedDifficulty.DrainRate:0.#}")
|
||||
};
|
||||
break;
|
||||
|
||||
case 2:
|
||||
stats = new (string heading, string content)[]
|
||||
{
|
||||
("CS", $"{beatmap.Difficulty.CircleSize:0.#}{hardRockExtra}"),
|
||||
("AR", $"{ar:0.#}"),
|
||||
("CS", $"{adjustedDifficulty.CircleSize:0.#}"),
|
||||
("AR", $"{adjustedDifficulty.ApproachRate:0.#}"),
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace osu.Game.Tournament.Components
|
||||
{
|
||||
public partial class TournamentMatchChatDisplay : StandAloneChatDisplay
|
||||
{
|
||||
private readonly Bindable<string> chatChannel = new Bindable<string>();
|
||||
private readonly Bindable<string> channelName = new Bindable<string>();
|
||||
|
||||
private ChannelManager? manager;
|
||||
|
||||
@@ -34,39 +34,33 @@ namespace osu.Game.Tournament.Components
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(MatchIPCInfo? ipc, IAPIProvider api)
|
||||
private void load(MatchIPCInfo ipc, IAPIProvider api)
|
||||
{
|
||||
if (ipc != null)
|
||||
AddInternal(manager = new ChannelManager(api));
|
||||
Channel.BindTo(manager.CurrentChannel);
|
||||
|
||||
channelName.BindTo(ipc.ChatChannel);
|
||||
channelName.BindValueChanged(c =>
|
||||
{
|
||||
chatChannel.BindTo(ipc.ChatChannel);
|
||||
chatChannel.BindValueChanged(c =>
|
||||
if (int.TryParse(c.OldValue, out int oldChannelId) && oldChannelId > 0)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(c.NewValue))
|
||||
return;
|
||||
|
||||
int id = int.Parse(c.NewValue);
|
||||
|
||||
if (id <= 0) return;
|
||||
|
||||
if (manager == null)
|
||||
{
|
||||
AddInternal(manager = new ChannelManager(api));
|
||||
Channel.BindTo(manager.CurrentChannel);
|
||||
}
|
||||
|
||||
foreach (var ch in manager.JoinedChannels.ToList())
|
||||
manager.LeaveChannel(ch);
|
||||
var joinedChannel = manager.JoinedChannels.SingleOrDefault(ch => ch.Id == oldChannelId);
|
||||
if (joinedChannel != null)
|
||||
manager.LeaveChannel(joinedChannel);
|
||||
}
|
||||
|
||||
if (int.TryParse(c.NewValue, out int newChannelId) && newChannelId > 0)
|
||||
{
|
||||
var channel = new Channel
|
||||
{
|
||||
Id = id,
|
||||
Id = newChannelId,
|
||||
Type = ChannelType.Public
|
||||
};
|
||||
|
||||
manager.JoinChannel(channel);
|
||||
manager.CurrentChannel.Value = channel;
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
|
||||
public void Expand() => this.FadeIn(300);
|
||||
|
||||
@@ -6,6 +6,7 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Rulesets;
|
||||
using static osu.Game.Online.API.Requests.Responses.APIBeatmap;
|
||||
|
||||
namespace osu.Game.Tournament.Models
|
||||
{
|
||||
@@ -31,6 +32,8 @@ namespace osu.Game.Tournament.Models
|
||||
|
||||
public BeatmapSetOnlineCovers Covers { get; set; }
|
||||
|
||||
public IRulesetInfo Ruleset { get; set; } = new APIRuleset();
|
||||
|
||||
public TournamentBeatmap()
|
||||
{
|
||||
}
|
||||
@@ -47,6 +50,7 @@ namespace osu.Game.Tournament.Models
|
||||
Covers = beatmap.BeatmapSet?.Covers ?? new BeatmapSetOnlineCovers();
|
||||
EndTimeObjectCount = beatmap.EndTimeObjectCount;
|
||||
TotalObjectCount = beatmap.TotalObjectCount;
|
||||
Ruleset = beatmap.Ruleset;
|
||||
}
|
||||
|
||||
public bool Equals(IBeatmapInfo? other) => other is TournamentBeatmap b && this.MatchesOnlineID(b);
|
||||
@@ -83,7 +87,7 @@ namespace osu.Game.Tournament.Models
|
||||
|
||||
string IBeatmapInfo.MD5Hash => throw new NotImplementedException();
|
||||
|
||||
IRulesetInfo IBeatmapInfo.Ruleset => throw new NotImplementedException();
|
||||
IRulesetInfo IBeatmapInfo.Ruleset => Ruleset;
|
||||
|
||||
DateTimeOffset IBeatmapSetOnlineInfo.Submitted => throw new NotImplementedException();
|
||||
|
||||
|
||||
@@ -105,6 +105,7 @@ namespace osu.Game.Beatmaps
|
||||
return (beatLength: t.BeatLength, duration: nextTime - currentTime);
|
||||
})
|
||||
// Aggregate durations into a set of (beatLength, duration) tuples for each beat length
|
||||
// Rounding is applied here (to 1e-3 milliseconds) to neutralise potential effects of floating point inaccuracies
|
||||
.GroupBy(t => Math.Round(t.beatLength * 1000) / 1000)
|
||||
.Select(g => (beatLength: g.Key, duration: g.Sum(t => t.duration)))
|
||||
// Get the most common one, or 0 as a suitable default (see handling below)
|
||||
@@ -113,7 +114,12 @@ namespace osu.Game.Beatmaps
|
||||
if (mostCommon.beatLength == 0)
|
||||
return TimingControlPoint.DEFAULT_BEAT_LENGTH;
|
||||
|
||||
return mostCommon.beatLength;
|
||||
// Because of the rounding applied to the beat length above, it is possible for the "most common" beat length as determined by the linq query above
|
||||
// to actually be less or more than the raw range of unrounded beat lengths present in the map
|
||||
// To ensure this does not become a problem anywhere else further, clamp the result to the known raw range
|
||||
double minBeatLength = ControlPointInfo.TimingPoints.Min(t => t.BeatLength);
|
||||
double maxBeatLength = ControlPointInfo.TimingPoints.Max(t => t.BeatLength);
|
||||
return Math.Clamp(mostCommon.beatLength, minBeatLength, maxBeatLength);
|
||||
}
|
||||
|
||||
public double AudioLeadIn { get; set; }
|
||||
|
||||
@@ -192,6 +192,8 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
"2412260 Koto Spirit - Locus of Hexagram.osz",
|
||||
"2412232 Will Stetson - Of Our Time.osz",
|
||||
"2412292 ArXe - Locus Amoenus (feat. Megurine Luka).osz",
|
||||
"2412328 Akiri - Vespera Stella.osz",
|
||||
"2412331 takehirotei - Haiboku no Altra Vita.osz",
|
||||
};
|
||||
|
||||
private static readonly string[] bundled_osu =
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace osu.Game.Configuration
|
||||
Circles,
|
||||
Welcome,
|
||||
Triangles,
|
||||
None,
|
||||
Random
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,6 +158,7 @@ namespace osu.Game.Configuration
|
||||
SetDefault(OsuSetting.ReplayPlaybackControlsExpanded, true);
|
||||
SetDefault(OsuSetting.GameplayLeaderboard, true);
|
||||
SetDefault(OsuSetting.AlwaysPlayFirstComboBreak, true);
|
||||
SetDefault(OsuSetting.AlwaysPlayComboBreak, false);
|
||||
|
||||
SetDefault(OsuSetting.FloatingComments, false);
|
||||
|
||||
@@ -369,6 +370,7 @@ namespace osu.Game.Configuration
|
||||
GameplayLeaderboard,
|
||||
PositionalHitsoundsLevel,
|
||||
AlwaysPlayFirstComboBreak,
|
||||
AlwaysPlayComboBreak,
|
||||
FloatingComments,
|
||||
HUDVisibilityMode,
|
||||
|
||||
|
||||
@@ -206,12 +206,25 @@ namespace osu.Game.Database
|
||||
if (!Filename.EndsWith(realm_extension, StringComparison.Ordinal))
|
||||
Filename += realm_extension;
|
||||
|
||||
// TODO: fix
|
||||
// #if DEBUG
|
||||
// since I'm an idiot, I will have to suffer
|
||||
#if DEBUG
|
||||
if (!DebugUtils.IsNUnitRunning)
|
||||
applyFilenameSchemaSuffix(ref Filename);
|
||||
// #endif
|
||||
|
||||
#endif
|
||||
#if !DEBUG
|
||||
// in the lazer. straight up "migrating it". and by "it", haha, well. let's just say. My realm .
|
||||
string altFilename = filename;
|
||||
applyFilenameSchemaSuffix(ref altFilename); // it also migrates older versions automagically! (sorry, I only used that word for irony...)
|
||||
if (storage.Exists(altFilename) && !storage.Exists(Filename))
|
||||
{
|
||||
using (var previous = storage.GetStream(altFilename))
|
||||
using (var current = storage.CreateFileSafely(Filename))
|
||||
{
|
||||
Logger.Log($@"Migrating production build DB: {altFilename} -> {Filename}");
|
||||
previous.CopyTo(current);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// `prepareFirstRealmAccess()` triggers the first `getRealmInstance` call, which will implicitly run realm migrations and bring the schema up-to-date.
|
||||
using (var realm = prepareFirstRealmAccess())
|
||||
cleanupPendingDeletions(realm);
|
||||
|
||||
@@ -33,6 +33,7 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; }
|
||||
|
||||
private readonly IBindable<APIState> apiState = new Bindable<APIState>();
|
||||
private Bindable<bool> useSeasonalBackgrounds;
|
||||
private Bindable<string> selectedCategory;
|
||||
private Bindable<APISeasonalBackgrounds> currentBackgrounds;
|
||||
@@ -40,6 +41,7 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
private int currentBackgroundIndex;
|
||||
|
||||
private bool shouldShowCustomBackgrounds => useSeasonalBackgrounds.Value;
|
||||
private bool shouldFetchCustomBackgrounds = false;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuConfigManager config, SessionStatics sessionStatics)
|
||||
@@ -54,6 +56,9 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
|
||||
if (shouldShowCustomBackgrounds)
|
||||
fetchCategories(true);
|
||||
|
||||
apiState.BindTo(api.State);
|
||||
apiState.BindValueChanged(d => shouldFetchCustomBackgrounds = d.NewValue == APIState.Online, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -116,18 +121,13 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
BackgroundChanged?.Invoke();
|
||||
};
|
||||
|
||||
request.Failure += exception =>
|
||||
{
|
||||
OnLoadFailure?.Invoke(exception);
|
||||
};
|
||||
|
||||
api.PerformAsync(request);
|
||||
}
|
||||
|
||||
public Background LoadNextBackground()
|
||||
public SeasonalBackground LoadNextBackground()
|
||||
{
|
||||
if (!shouldShowCustomBackgrounds || currentBackgrounds.Value?.Backgrounds?.Any() != true)
|
||||
return null;
|
||||
if (!shouldShowCustomBackgrounds || !shouldFetchCustomBackgrounds || currentBackgrounds.Value?.Backgrounds?.Any() != true)
|
||||
return (SeasonalBackground)(new Background($@"Menu/menu-background-{RNG.Next(1, 9)}"));
|
||||
|
||||
var backgrounds = currentBackgrounds.Value.Backgrounds;
|
||||
currentBackgroundIndex = (currentBackgroundIndex + 1) % backgrounds.Count;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Threading;
|
||||
|
||||
namespace osu.Game.Graphics.Containers
|
||||
@@ -58,6 +58,19 @@ namespace osu.Game.Graphics.Containers
|
||||
|
||||
protected virtual OsuScrollContainer CreateScrollContainer() => new OsuScrollContainer();
|
||||
|
||||
private InputManager inputManager = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Tracks whether the mouse was in bounds of this expanding container in the last frame.
|
||||
/// </summary>
|
||||
private bool? lastMouseInBounds;
|
||||
|
||||
/// <summary>
|
||||
/// Tracks whether the last expansion of the container was caused by the mouse moving into its bounds
|
||||
/// (as opposed to an external set of `Expanded`, in which case moving the mouse outside of its bounds should not contract).
|
||||
/// </summary>
|
||||
private bool? expandedByMouse;
|
||||
|
||||
private ScheduledDelegate? hoverExpandEvent;
|
||||
|
||||
protected override void LoadComplete()
|
||||
@@ -68,37 +81,43 @@ namespace osu.Game.Graphics.Containers
|
||||
{
|
||||
this.ResizeWidthTo(v.NewValue ? expandedWidth : contractedWidth, TRANSITION_DURATION, Easing.OutQuint);
|
||||
}, true);
|
||||
|
||||
inputManager = GetContainingInputManager()!;
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
protected override void Update()
|
||||
{
|
||||
updateHoverExpansion();
|
||||
return true;
|
||||
base.Update();
|
||||
|
||||
bool mouseInBounds = Contains(inputManager.CurrentState.Mouse.Position);
|
||||
|
||||
if (lastMouseInBounds != mouseInBounds)
|
||||
updateExpansionState(mouseInBounds);
|
||||
|
||||
lastMouseInBounds = mouseInBounds;
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
if (hoverExpandEvent != null)
|
||||
{
|
||||
hoverExpandEvent?.Cancel();
|
||||
hoverExpandEvent = null;
|
||||
|
||||
Expanded.Value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
base.OnHoverLost(e);
|
||||
}
|
||||
|
||||
private void updateHoverExpansion()
|
||||
private void updateExpansionState(bool mouseInBounds)
|
||||
{
|
||||
if (!ExpandOnHover)
|
||||
return;
|
||||
|
||||
hoverExpandEvent?.Cancel();
|
||||
hoverExpandEvent = null;
|
||||
|
||||
if (IsHovered && !Expanded.Value)
|
||||
if (mouseInBounds && !Expanded.Value)
|
||||
{
|
||||
hoverExpandEvent = Scheduler.AddDelayed(() => Expanded.Value = true, HoverExpansionDelay);
|
||||
expandedByMouse = true;
|
||||
}
|
||||
|
||||
if (!mouseInBounds && Expanded.Value)
|
||||
{
|
||||
if (expandedByMouse == true)
|
||||
Expanded.Value = false;
|
||||
|
||||
expandedByMouse = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ using osu.Game.Graphics.UserInterfaceV2;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
public partial class DangerousRoundedButton : RoundedButton
|
||||
public sealed partial class DangerousRoundedButton : RoundedButton
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
|
||||
@@ -10,6 +10,7 @@ using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using Vector2 = osuTK.Vector2;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterface
|
||||
@@ -19,49 +20,27 @@ namespace osu.Game.Graphics.UserInterface
|
||||
/// </summary>
|
||||
public partial class ExpandableSlider<T, TSlider> : CompositeDrawable, IExpandable, IHasCurrentValue<T>
|
||||
where T : struct, INumber<T>, IMinMaxValue<T>
|
||||
where TSlider : RoundedSliderBar<T>, new()
|
||||
where TSlider : FormSliderBar<T>, new()
|
||||
{
|
||||
private readonly OsuSpriteText label;
|
||||
private readonly OsuSpriteText contractedLabel;
|
||||
private readonly TSlider slider;
|
||||
|
||||
private LocalisableString contractedLabelText;
|
||||
|
||||
/// <summary>
|
||||
/// The label text to display when this slider is in a contracted state.
|
||||
/// </summary>
|
||||
public LocalisableString ContractedLabelText
|
||||
{
|
||||
get => contractedLabelText;
|
||||
set
|
||||
{
|
||||
if (value == contractedLabelText)
|
||||
return;
|
||||
|
||||
contractedLabelText = value;
|
||||
|
||||
if (!Expanded.Value)
|
||||
label.Text = value;
|
||||
}
|
||||
get => contractedLabel.Text;
|
||||
set => contractedLabel.Text = value;
|
||||
}
|
||||
|
||||
private LocalisableString expandedLabelText;
|
||||
|
||||
/// <summary>
|
||||
/// The label text to display when this slider is in an expanded state.
|
||||
/// </summary>
|
||||
public LocalisableString ExpandedLabelText
|
||||
{
|
||||
get => expandedLabelText;
|
||||
set
|
||||
{
|
||||
if (value == expandedLabelText)
|
||||
return;
|
||||
|
||||
expandedLabelText = value;
|
||||
|
||||
if (Expanded.Value)
|
||||
label.Text = value;
|
||||
}
|
||||
get => slider.Caption;
|
||||
set => slider.Caption = value;
|
||||
}
|
||||
|
||||
public Bindable<T> Current
|
||||
@@ -95,7 +74,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
Spacing = new Vector2(0f, 10f),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
label = new OsuSpriteText(),
|
||||
contractedLabel = new OsuSpriteText(),
|
||||
slider = new TSlider
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
@@ -118,7 +97,8 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
Expanded.BindValueChanged(v =>
|
||||
{
|
||||
label.Text = v.NewValue ? expandedLabelText : contractedLabelText;
|
||||
contractedLabel.FadeTo(v.NewValue ? 0 : 1);
|
||||
|
||||
slider.FadeTo(v.NewValue ? Current.Disabled ? 0.3f : 1f : 0f, 500, Easing.OutQuint);
|
||||
slider.BypassAutoSizeAxes = !v.NewValue ? Axes.Y : Axes.None;
|
||||
}, true);
|
||||
@@ -133,7 +113,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
/// <summary>
|
||||
/// An <see cref="IExpandable"/> implementation for the UI slider bar control.
|
||||
/// </summary>
|
||||
public partial class ExpandableSlider<T> : ExpandableSlider<T, RoundedSliderBar<T>>
|
||||
public partial class ExpandableSlider<T> : ExpandableSlider<T, FormSliderBar<T>>
|
||||
where T : struct, INumber<T>, IMinMaxValue<T>
|
||||
{
|
||||
}
|
||||
|
||||
@@ -56,7 +56,8 @@ namespace osu.Game.Graphics.UserInterface
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
CornerRadius = 5,
|
||||
CornerRadius = 10,
|
||||
CornerExponent = 2.5f,
|
||||
Masking = true,
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
|
||||
@@ -357,7 +357,8 @@ namespace osu.Game.Graphics.UserInterface
|
||||
Icon = FontAwesome.Solid.ChevronDown,
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
Size = new Vector2(16),
|
||||
Size = new Vector2(10),
|
||||
Margin = new MarginPadding { Right = 2 },
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
public bool Seeking { get; private set; }
|
||||
|
||||
public Action<double> OnSeek;
|
||||
public Action<double> OnCommit;
|
||||
|
||||
private readonly Box fill;
|
||||
private readonly Box background;
|
||||
@@ -80,12 +81,14 @@ namespace osu.Game.Graphics.UserInterface
|
||||
protected override void OnUserChange(double value)
|
||||
{
|
||||
Seeking = true;
|
||||
OnSeek?.Invoke(value);
|
||||
base.OnUserChange(value);
|
||||
}
|
||||
|
||||
protected override bool Commit()
|
||||
{
|
||||
OnSeek?.Invoke(CurrentNumber.Value);
|
||||
Seeking = false;
|
||||
OnCommit?.Invoke(CurrentNumber.Value);
|
||||
return base.Commit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,7 +178,8 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
Size = new Vector2(70);
|
||||
|
||||
Masking = true;
|
||||
CornerRadius = 35;
|
||||
CornerRadius = 10;
|
||||
CornerExponent = 2.5f;
|
||||
Action = this.ShowPopover;
|
||||
|
||||
Children = new Drawable[]
|
||||
|
||||
@@ -58,26 +58,49 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
}
|
||||
}
|
||||
|
||||
private LocalisableString caption;
|
||||
|
||||
/// <summary>
|
||||
/// Caption describing this slider bar, displayed on top of the controls.
|
||||
/// </summary>
|
||||
public LocalisableString Caption { get; init; }
|
||||
public LocalisableString Caption
|
||||
{
|
||||
get => caption;
|
||||
set
|
||||
{
|
||||
caption = value;
|
||||
|
||||
if (IsLoaded)
|
||||
captionText.Caption = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hint text containing an extended description of this slider bar, displayed in a tooltip when hovering the caption.
|
||||
/// </summary>
|
||||
public LocalisableString HintText { get; init; }
|
||||
|
||||
private float keyboardStep;
|
||||
|
||||
/// <summary>
|
||||
/// A custom step value for each key press which actuates a change on this control.
|
||||
/// </summary>
|
||||
public float KeyboardStep { get; init; }
|
||||
public float KeyboardStep
|
||||
{
|
||||
get => keyboardStep;
|
||||
set
|
||||
{
|
||||
keyboardStep = value;
|
||||
if (IsLoaded)
|
||||
slider.KeyboardStep = value;
|
||||
}
|
||||
}
|
||||
|
||||
private Box background = null!;
|
||||
private Box flashLayer = null!;
|
||||
private FormTextBox.InnerTextBox textBox = null!;
|
||||
private InnerSlider slider = null!;
|
||||
private FormFieldCaption caption = null!;
|
||||
private FormFieldCaption captionText = null!;
|
||||
private IFocusManager focusManager = null!;
|
||||
|
||||
[Resolved]
|
||||
@@ -117,11 +140,10 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
},
|
||||
Children = new Drawable[]
|
||||
{
|
||||
caption = new FormFieldCaption
|
||||
captionText = new FormFieldCaption
|
||||
{
|
||||
Anchor = Anchor.TopLeft,
|
||||
Origin = Anchor.TopLeft,
|
||||
Caption = Caption,
|
||||
TooltipText = HintText,
|
||||
},
|
||||
textBox = new FormNumberBox.InnerNumberBox(allowDecimals: true)
|
||||
@@ -145,7 +167,6 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
Origin = Anchor.CentreRight,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Width = 0.5f,
|
||||
KeyboardStep = KeyboardStep,
|
||||
Current = currentNumberInstantaneous,
|
||||
OnCommit = () => current.Value = currentNumberInstantaneous.Value,
|
||||
}
|
||||
@@ -161,6 +182,9 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
slider.KeyboardStep = keyboardStep;
|
||||
captionText.Caption = caption;
|
||||
|
||||
focusManager = GetContainingFocusManager()!;
|
||||
|
||||
textBox.Focused.BindValueChanged(_ => updateState());
|
||||
@@ -270,7 +294,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
textBox.Alpha = 1;
|
||||
|
||||
background.Colour = currentNumberInstantaneous.Disabled ? colourProvider.Background4 : colourProvider.Background5;
|
||||
caption.Colour = currentNumberInstantaneous.Disabled ? colourProvider.Foreground1 : colourProvider.Content2;
|
||||
captionText.Colour = currentNumberInstantaneous.Disabled ? colourProvider.Foreground1 : colourProvider.Content2;
|
||||
textBox.Colour = currentNumberInstantaneous.Disabled ? colourProvider.Foreground1 : colourProvider.Content1;
|
||||
|
||||
BorderThickness = childHasFocus || IsHovered || slider.IsDragging.Value ? 2 : 0;
|
||||
@@ -300,8 +324,8 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
|
||||
private Box leftBox = null!;
|
||||
private Box rightBox = null!;
|
||||
private Circle nub = null!;
|
||||
private const float nub_width = 10;
|
||||
private InnerSliderNub nub = null!;
|
||||
public const float NUB_WIDTH = 10;
|
||||
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||
@@ -311,7 +335,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
{
|
||||
Height = 40;
|
||||
RelativeSizeAxes = Axes.X;
|
||||
RangePadding = nub_width / 2;
|
||||
RangePadding = NUB_WIDTH / 2;
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
@@ -340,12 +364,13 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Horizontal = RangePadding, },
|
||||
Child = nub = new Circle
|
||||
Child = nub = new InnerSliderNub
|
||||
{
|
||||
Width = nub_width,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
RelativePositionAxes = Axes.X,
|
||||
Origin = Anchor.TopCentre,
|
||||
ResetToDefault = () =>
|
||||
{
|
||||
if (!Current.Disabled)
|
||||
Current.SetDefault();
|
||||
}
|
||||
}
|
||||
},
|
||||
new HoverClickSounds()
|
||||
@@ -428,5 +453,27 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private partial class InnerSliderNub : Circle
|
||||
{
|
||||
public Action? ResetToDefault { get; set; }
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Width = InnerSlider.NUB_WIDTH;
|
||||
RelativeSizeAxes = Axes.Y;
|
||||
RelativePositionAxes = Axes.X;
|
||||
Origin = Anchor.TopCentre;
|
||||
}
|
||||
|
||||
protected override bool OnClick(ClickEvent e) => true; // must be handled for double click handler to ever fire
|
||||
|
||||
protected override bool OnDoubleClick(DoubleClickEvent e)
|
||||
{
|
||||
ResetToDefault?.Invoke();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,18 +26,6 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
|
||||
private Color4? triangleGradientSecondColour;
|
||||
|
||||
public override float Height
|
||||
{
|
||||
get => base.Height;
|
||||
set
|
||||
{
|
||||
base.Height = value;
|
||||
|
||||
if (IsLoaded)
|
||||
updateCornerRadius();
|
||||
}
|
||||
}
|
||||
|
||||
public override Color4 BackgroundColour
|
||||
{
|
||||
get => base.BackgroundColour;
|
||||
@@ -61,7 +49,10 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
updateCornerRadius();
|
||||
// This doesn't match the latest design spec (should be 5) but is an in-between that feels right to the eye
|
||||
// until we move everything over to Form controls.
|
||||
Content.CornerRadius = 10;
|
||||
Content.CornerExponent = 2.5f;
|
||||
|
||||
Add(Triangles = new TrianglesV2
|
||||
{
|
||||
@@ -98,8 +89,6 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
base.OnHoverLost(e);
|
||||
}
|
||||
|
||||
private void updateCornerRadius() => Content.CornerRadius = DrawHeight / 2;
|
||||
|
||||
public virtual IEnumerable<LocalisableString> FilterTerms => new[] { Text };
|
||||
|
||||
public bool MatchingFilter
|
||||
|
||||
@@ -199,6 +199,11 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString Mapper => new TranslatableString(getKey(@"mapper"), @"Mapper");
|
||||
|
||||
/// <summary>
|
||||
/// "Delete..."
|
||||
/// </summary>
|
||||
public static LocalisableString DeleteWithConfirmation => new TranslatableString(getKey(@"delete_with_confrmation"), @"Delete...");
|
||||
|
||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,16 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString Prefer24HourTimeDisplay => new TranslatableString(getKey(@"prefer_24_hour_time_display"), @"Prefer 24-hour time display");
|
||||
|
||||
/// <summary>
|
||||
/// "Installation"
|
||||
/// </summary>
|
||||
public static LocalisableString InstallationHeader => new TranslatableString(getKey(@"installation_header"), @"Installation");
|
||||
|
||||
/// <summary>
|
||||
/// "Quick Actions"
|
||||
/// </summary>
|
||||
public static LocalisableString QuickActionsHeader => new TranslatableString(getKey(@"quick_actions_header"), @"Quick Actions");
|
||||
|
||||
/// <summary>
|
||||
/// "Updates"
|
||||
/// </summary>
|
||||
@@ -79,6 +89,16 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString LearnMoreAboutLazerTooltip => new TranslatableString(getKey(@"check_out_the_feature_comparison"), @"Check out the feature comparison and FAQ");
|
||||
|
||||
/// <summary>
|
||||
/// "Report an issue"
|
||||
/// </summary>
|
||||
public static LocalisableString ReportIssue => new TranslatableString(getKey(@"report_issue"), @"Report an issue");
|
||||
|
||||
/// <summary>
|
||||
/// "Report a problem with the game to the developers."
|
||||
/// </summary>
|
||||
public static LocalisableString ReportIssueTooltip => new TranslatableString(getKey(@"report_issue_tooltip"), @"Report a problem with the game to the developers.");
|
||||
|
||||
/// <summary>
|
||||
/// "Check with your package manager / provider for other release streams."
|
||||
/// </summary>
|
||||
|
||||
@@ -109,6 +109,11 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString UseTheseMods => new TranslatableString(getKey(@"use_these_mods"), @"Use these mods");
|
||||
|
||||
/// <summary>
|
||||
/// "Watch replay"
|
||||
/// </summary>
|
||||
public static LocalisableString WatchReplay => new TranslatableString(getKey(@"watch_replay"), @"Watch replay");
|
||||
|
||||
/// <summary>
|
||||
/// "For all difficulties"
|
||||
/// </summary>
|
||||
@@ -139,11 +144,6 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString ClearAllLocalScores => new TranslatableString(getKey(@"clear_all_local_scores"), @"Clear all local scores");
|
||||
|
||||
/// <summary>
|
||||
/// "Delete beatmap"
|
||||
/// </summary>
|
||||
public static LocalisableString DeleteBeatmap => new TranslatableString(getKey(@"delete_beatmap"), @"Delete beatmap");
|
||||
|
||||
/// <summary>
|
||||
/// "Restore all hidden"
|
||||
/// </summary>
|
||||
|
||||
@@ -603,7 +603,7 @@ namespace osu.Game.Online.API
|
||||
cancellationToken.Cancel();
|
||||
}
|
||||
|
||||
private class WebRequestFlushedException : Exception
|
||||
internal class WebRequestFlushedException : Exception
|
||||
{
|
||||
public WebRequestFlushedException(APIState state)
|
||||
: base($@"Request failed from flush operation (state {state})")
|
||||
|
||||
@@ -62,6 +62,10 @@ namespace osu.Game.Online.API
|
||||
localUser.Value = me;
|
||||
configSupporter.Value = me.IsSupporter;
|
||||
|
||||
// `last_visit` is assumed to be `null` if and only if the web-side "hide online presence toggle" is enabled
|
||||
if (me.LastVisit == null)
|
||||
configStatus.Value = UserStatus.Offline;
|
||||
|
||||
UpdateFriends();
|
||||
UpdateBlocks();
|
||||
UpdateFavouriteBeatmapSets();
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
}
|
||||
|
||||
public GetSeasonalBackgroundsRequest(string? category)
|
||||
public GetSeasonalBackgroundsRequest(string? category = null)
|
||||
{
|
||||
this.category = category;
|
||||
}
|
||||
|
||||
@@ -146,6 +146,7 @@ namespace osu.Game.Online.API.Requests.Responses
|
||||
|
||||
public string Name => $@"{nameof(APIRuleset)} (ID: {OnlineID})";
|
||||
|
||||
[JsonIgnore]
|
||||
public string ShortName
|
||||
{
|
||||
get
|
||||
|
||||
@@ -70,6 +70,7 @@ namespace osu.Game.Online.Chat
|
||||
[Resolved]
|
||||
private UserLookupCache users { get; set; }
|
||||
|
||||
private readonly IBindable<APIUser> localUser = new Bindable<APIUser>();
|
||||
private readonly IBindable<APIState> apiState = new Bindable<APIState>();
|
||||
private readonly IBindableList<APIRelation> localUserBlocks = new BindableList<APIRelation>();
|
||||
private ScheduledDelegate scheduledAck;
|
||||
@@ -95,6 +96,9 @@ namespace osu.Game.Online.Chat
|
||||
chatClient.PresenceReceived += () => Schedule(initializeChannels);
|
||||
chatClient.RequestPresence();
|
||||
|
||||
localUser.BindTo(api.LocalUser);
|
||||
localUser.BindValueChanged(userChanged);
|
||||
|
||||
apiState.BindTo(api.State);
|
||||
apiState.BindValueChanged(_ => SendAck(), true);
|
||||
|
||||
@@ -102,6 +106,22 @@ namespace osu.Game.Online.Chat
|
||||
localUserBlocks.BindCollectionChanged((_, args) => Schedule(() => onBlocksChanged(args)));
|
||||
}
|
||||
|
||||
private void userChanged(ValueChangedEvent<APIUser> userChange)
|
||||
{
|
||||
if (userChange.OldValue?.Equals(userChange.NewValue) == true)
|
||||
return;
|
||||
|
||||
CurrentChannel.Value = null;
|
||||
|
||||
foreach (var joinedChannel in joinedChannels)
|
||||
joinedChannel.Joined.Value = false;
|
||||
|
||||
joinedChannels.Clear();
|
||||
// additionally clear the history of last joined channels so that the new user can't reopen the old user's channels
|
||||
// (would likely fail web-side on perms anyway, but why even get that far)
|
||||
closedChannels.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens a channel or switches to the channel if already opened.
|
||||
/// </summary>
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace osu.Game.Online.Chat
|
||||
private GameHost host { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private Clipboard clipboard { get; set; } = null!;
|
||||
private OsuGame? game { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private IDialogOverlay? dialogOverlay { get; set; }
|
||||
@@ -88,7 +88,7 @@ namespace osu.Game.Online.Chat
|
||||
}
|
||||
|
||||
if (dialogOverlay != null && shouldWarn)
|
||||
dialogOverlay.Push(new ExternalLinkDialog(url, () => host.OpenUrlExternally(url), () => clipboard.SetText(url)));
|
||||
dialogOverlay.Push(new ExternalLinkDialog(url, () => host.OpenUrlExternally(url), () => game?.CopyToClipboard(url)));
|
||||
else
|
||||
host.OpenUrlExternally(url);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
@@ -76,7 +75,7 @@ namespace osu.Game.Online.Leaderboards
|
||||
private SongSelect songSelect { get; set; }
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
private Clipboard clipboard { get; set; }
|
||||
private OsuGame game { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; }
|
||||
@@ -459,7 +458,7 @@ namespace osu.Game.Online.Leaderboards
|
||||
items.Add(new OsuMenuItem("Use these mods", MenuItemType.Highlighted, () => songSelect.Mods.Value = copyableMods));
|
||||
|
||||
if (Score.OnlineID > 0)
|
||||
items.Add(new OsuMenuItem(CommonStrings.CopyLink, MenuItemType.Standard, () => clipboard?.SetText($@"{api.Endpoints.WebsiteUrl}/scores/{Score.OnlineID}")));
|
||||
items.Add(new OsuMenuItem(CommonStrings.CopyLink, MenuItemType.Standard, () => game?.CopyToClipboard($@"{api.Endpoints.WebsiteUrl}/scores/{Score.OnlineID}")));
|
||||
|
||||
if (Score.Files.Count > 0)
|
||||
{
|
||||
|
||||
@@ -235,15 +235,13 @@ namespace osu.Game.Online.Metadata
|
||||
{
|
||||
if (userId == api.LocalUser.Value.OnlineID)
|
||||
localUserPresence = presence.Value;
|
||||
else
|
||||
userPresences[userId] = presence.Value;
|
||||
userPresences[userId] = presence.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (userId == api.LocalUser.Value.OnlineID)
|
||||
localUserPresence = default;
|
||||
else
|
||||
userPresences.Remove(userId);
|
||||
userPresences.Remove(userId);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -153,7 +153,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
/// <summary>
|
||||
/// Signals that a user has requested to skip the beatmap intro.
|
||||
/// </summary>
|
||||
Task UserVotedToSkipIntro(int userId);
|
||||
Task UserVotedToSkipIntro(int userId, bool voted);
|
||||
|
||||
/// <summary>
|
||||
/// Signals that the vote to skip the beatmap intro has passed.
|
||||
|
||||
@@ -28,14 +28,20 @@ namespace osu.Game.Online.Multiplayer.MatchTypes.Matchmaking
|
||||
public int CurrentRound { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The playlist items that were picked as gameplay candidates.
|
||||
/// The playlist items that were picked as candidates by user.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// May contain <c>-1</c> when any users picked the "random" playlist item.
|
||||
/// </remarks>
|
||||
[Key(2)]
|
||||
public long[] CandidateItems { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// The final gameplay candidate.
|
||||
/// A playlist item from <see cref="CandidateItems"/> that was randomly picked by the server.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// May be <c>-1</c> to indicate the "random" playlist item was chosen.
|
||||
/// </remarks>
|
||||
[Key(3)]
|
||||
public long CandidateItem { get; set; }
|
||||
|
||||
@@ -45,6 +51,15 @@ namespace osu.Game.Online.Multiplayer.MatchTypes.Matchmaking
|
||||
[Key(4)]
|
||||
public MatchmakingUserList Users { get; set; } = new MatchmakingUserList();
|
||||
|
||||
/// <summary>
|
||||
/// A playlist item from the room's playlist that will be played in the current round.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The value of this property may not equal <see cref="CandidateItem"/> or exist in <see cref="CandidateItems"/>.
|
||||
/// </remarks>
|
||||
[Key(5)]
|
||||
public long GameplayItem { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Advances to the next round.
|
||||
/// </summary>
|
||||
|
||||
@@ -36,5 +36,11 @@ namespace osu.Game.Online.Multiplayer.MatchTypes.Matchmaking
|
||||
/// </summary>
|
||||
[Key(3)]
|
||||
public MatchmakingRoundList Rounds { get; set; } = new MatchmakingRoundList();
|
||||
|
||||
/// <summary>
|
||||
/// The time at which this user abandoned the match.
|
||||
/// </summary>
|
||||
[Key(4)]
|
||||
public DateTimeOffset? AbandonedAt { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,42 +23,53 @@ namespace osu.Game.Online.Multiplayer.MatchTypes.Matchmaking
|
||||
ArgumentNullException.ThrowIfNull(x);
|
||||
ArgumentNullException.ThrowIfNull(y);
|
||||
|
||||
// X appears earlier in the list if it has more points.
|
||||
if (x.Points > y.Points)
|
||||
return -1;
|
||||
int compare = compareAbandonedAt(x, y);
|
||||
if (compare != 0)
|
||||
return compare;
|
||||
|
||||
// Y appears earlier in the list if it has more points.
|
||||
if (y.Points > x.Points)
|
||||
return 1;
|
||||
compare = comparePoints(x, y);
|
||||
if (compare != 0)
|
||||
return compare;
|
||||
|
||||
// Tiebreaker 1 (likely): From each user's point-of-view, their earliest and best placement.
|
||||
compare = compareRoundPlacements(x, y);
|
||||
if (compare != 0)
|
||||
return compare;
|
||||
|
||||
return compareUserIds(x, y);
|
||||
}
|
||||
|
||||
private int compareAbandonedAt(MatchmakingUser x, MatchmakingUser y)
|
||||
{
|
||||
DateTimeOffset xAbandonedAt = x.AbandonedAt ?? DateTimeOffset.MaxValue;
|
||||
DateTimeOffset yAbandonedAt = y.AbandonedAt ?? DateTimeOffset.MaxValue;
|
||||
return -xAbandonedAt.CompareTo(yAbandonedAt);
|
||||
}
|
||||
|
||||
private int comparePoints(MatchmakingUser x, MatchmakingUser y)
|
||||
{
|
||||
return -x.Points.CompareTo(y.Points);
|
||||
}
|
||||
|
||||
private int compareRoundPlacements(MatchmakingUser x, MatchmakingUser y)
|
||||
{
|
||||
for (int r = 1; r <= rounds; r++)
|
||||
{
|
||||
MatchmakingRound? xRound;
|
||||
x.Rounds.RoundsDictionary.TryGetValue(r, out xRound);
|
||||
x.Rounds.RoundsDictionary.TryGetValue(r, out var xRound);
|
||||
y.Rounds.RoundsDictionary.TryGetValue(r, out var yRound);
|
||||
|
||||
MatchmakingRound? yRound;
|
||||
y.Rounds.RoundsDictionary.TryGetValue(r, out yRound);
|
||||
int xPlacement = xRound?.Placement ?? int.MaxValue;
|
||||
int yPlacement = yRound?.Placement ?? int.MaxValue;
|
||||
|
||||
// Nothing to do if both players haven't played this round.
|
||||
if (xRound == null && yRound == null)
|
||||
continue;
|
||||
|
||||
// X appears later in the list if it hasn't played this round.
|
||||
if (xRound == null)
|
||||
return 1;
|
||||
|
||||
// Y appears later in the list if it hasn't played this round.
|
||||
if (yRound == null)
|
||||
return -1;
|
||||
|
||||
// X appears earlier in the list if it has a better placement in the round.
|
||||
int compare = xRound.Placement.CompareTo(yRound.Placement);
|
||||
int compare = xPlacement.CompareTo(yPlacement);
|
||||
if (compare != 0)
|
||||
return compare;
|
||||
}
|
||||
|
||||
// Tiebreaker 2 (unlikely): User ID.
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int compareUserIds(MatchmakingUser x, MatchmakingUser y)
|
||||
{
|
||||
return x.UserId.CompareTo(y.UserId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
public event Action<int, long>? MatchmakingItemDeselected;
|
||||
public event Action<MatchRoomState>? MatchRoomStateChanged;
|
||||
|
||||
public event Action<int>? UserVotedToSkipIntro;
|
||||
public event Action<int, bool>? UserVotedToSkipIntro;
|
||||
public event Action? VoteToSkipIntroPassed;
|
||||
|
||||
public event Action<MultiplayerRoomUser, BeatmapAvailability>? BeatmapAvailabilityChanged;
|
||||
@@ -854,10 +854,6 @@ namespace osu.Game.Online.Multiplayer
|
||||
handleRoomRequest(() =>
|
||||
{
|
||||
Debug.Assert(Room != null);
|
||||
|
||||
foreach (var user in Room.Users)
|
||||
user.VotedToSkipIntro = false;
|
||||
|
||||
GameplayStarted?.Invoke();
|
||||
});
|
||||
|
||||
@@ -928,7 +924,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
Task IMultiplayerClient.UserVotedToSkipIntro(int userId)
|
||||
Task IMultiplayerClient.UserVotedToSkipIntro(int userId, bool voted)
|
||||
{
|
||||
handleRoomRequest(() =>
|
||||
{
|
||||
@@ -940,9 +936,8 @@ namespace osu.Game.Online.Multiplayer
|
||||
if (user == null)
|
||||
return;
|
||||
|
||||
user.VotedToSkipIntro = true;
|
||||
|
||||
UserVotedToSkipIntro?.Invoke(userId);
|
||||
user.VotedToSkipIntro = voted;
|
||||
UserVotedToSkipIntro?.Invoke(userId, voted);
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
@@ -1117,7 +1112,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
|
||||
Task IMatchmakingClient.MatchmakingItemSelected(int userId, long playlistItemId)
|
||||
{
|
||||
Scheduler.Add(() =>
|
||||
handleRoomRequest(() =>
|
||||
{
|
||||
MatchmakingItemSelected?.Invoke(userId, playlistItemId);
|
||||
RoomUpdated?.Invoke();
|
||||
@@ -1128,7 +1123,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
|
||||
Task IMatchmakingClient.MatchmakingItemDeselected(int userId, long playlistItemId)
|
||||
{
|
||||
Scheduler.Add(() =>
|
||||
handleRoomRequest(() =>
|
||||
{
|
||||
MatchmakingItemDeselected?.Invoke(userId, playlistItemId);
|
||||
RoomUpdated?.Invoke();
|
||||
|
||||
@@ -70,7 +70,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
connection.On<MultiplayerPlaylistItem>(nameof(IMultiplayerClient.PlaylistItemAdded), ((IMultiplayerClient)this).PlaylistItemAdded);
|
||||
connection.On<long>(nameof(IMultiplayerClient.PlaylistItemRemoved), ((IMultiplayerClient)this).PlaylistItemRemoved);
|
||||
connection.On<MultiplayerPlaylistItem>(nameof(IMultiplayerClient.PlaylistItemChanged), ((IMultiplayerClient)this).PlaylistItemChanged);
|
||||
connection.On<int>(nameof(IMultiplayerClient.UserVotedToSkipIntro), ((IMultiplayerClient)this).UserVotedToSkipIntro);
|
||||
connection.On<int, bool>(nameof(IMultiplayerClient.UserVotedToSkipIntro), ((IMultiplayerClient)this).UserVotedToSkipIntro);
|
||||
connection.On(nameof(IMultiplayerClient.VoteToSkipIntroPassed), ((IMultiplayerClient)this).VoteToSkipIntroPassed);
|
||||
|
||||
connection.On(nameof(IMatchmakingClient.MatchmakingQueueJoined), ((IMatchmakingClient)this).MatchmakingQueueJoined);
|
||||
|
||||
@@ -17,6 +17,7 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Development;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Extensions.TypeExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
@@ -1185,8 +1186,8 @@ namespace osu.Game
|
||||
Margin = new MarginPadding(5),
|
||||
}, topMostOverlayContent.Add);
|
||||
|
||||
// if (!IsDeployedBuild) // we're going to have the "developer build" banner for a while
|
||||
loadComponentSingleFile(devBuildBanner = new DevBuildBanner(), ScreenContainer.Add);
|
||||
if (!IsDeployedBuild && DebugUtils.IsDebugBuild)
|
||||
loadComponentSingleFile(devBuildBanner = new DevBuildBanner(), ScreenContainer.Add);
|
||||
|
||||
loadComponentSingleFile(osuLogo, _ =>
|
||||
{
|
||||
|
||||
@@ -84,8 +84,12 @@ namespace osu.Game.Overlays
|
||||
|
||||
performAfterFetch(() =>
|
||||
{
|
||||
string versionPart = version.Split('-')[0];
|
||||
string updateStream = version.Split('-')[1];
|
||||
string[] versionIdentifier = version.Split('-');
|
||||
|
||||
string versionPart = versionIdentifier[0];
|
||||
string updateStream = versionIdentifier.Length >= 2
|
||||
? versionIdentifier[1]
|
||||
: "lazer"; // let's assume it's lazer by default, seems like the most sane option
|
||||
|
||||
var build = builds.Find(b => b.Version == versionPart && b.UpdateStream.Name == updateStream)
|
||||
?? Streams.Find(s => s.Name == updateStream)?.LatestBuild;
|
||||
|
||||
@@ -79,25 +79,6 @@ namespace osu.Game.Overlays.Chat
|
||||
highlightedMessage.BindValueChanged(_ => processMessageHighlighting(), true);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
long? lastMinutes = null;
|
||||
|
||||
for (int i = 0; i < ChatLineFlow.Count; i++)
|
||||
{
|
||||
if (ChatLineFlow[i] is ChatLine chatline)
|
||||
{
|
||||
long minutes = chatline.Message.Timestamp.ToUnixTimeSeconds() / 60;
|
||||
|
||||
chatline.AlternatingBackground = i % 2 == 0;
|
||||
chatline.RequiresTimestamp = minutes != lastMinutes;
|
||||
lastMinutes = minutes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes any pending message in <see cref="highlightedMessage"/>.
|
||||
/// </summary>
|
||||
@@ -145,19 +126,28 @@ namespace osu.Game.Overlays.Chat
|
||||
// Add up to last Channel.MAX_HISTORY messages
|
||||
var displayMessages = newMessages.Skip(Math.Max(0, newMessages.Count() - Channel.MAX_HISTORY));
|
||||
|
||||
Message lastMessage = chatLines.LastOrDefault()?.Message;
|
||||
ChatLine lastLine = chatLines.LastOrDefault();
|
||||
Message lastMessage = lastLine?.Message;
|
||||
|
||||
foreach (var message in displayMessages)
|
||||
{
|
||||
addDaySeparatorIfRequired(lastMessage, message);
|
||||
|
||||
var chatLine = CreateChatLine(message);
|
||||
ChatLine line = CreateChatLine(message);
|
||||
|
||||
if (chatLine != null)
|
||||
{
|
||||
ChatLineFlow.Add(chatLine);
|
||||
lastMessage = message;
|
||||
}
|
||||
if (line == null)
|
||||
continue;
|
||||
|
||||
long minutes = line.Message.Timestamp.ToUnixTimeSeconds() / 60;
|
||||
long? lastMinutes = lastLine?.Message.Timestamp.ToUnixTimeSeconds() / 60;
|
||||
|
||||
line.AlternatingBackground = lastLine?.AlternatingBackground == false;
|
||||
line.RequiresTimestamp = minutes != lastMinutes;
|
||||
|
||||
ChatLineFlow.Add(line);
|
||||
|
||||
lastMessage = message;
|
||||
lastLine = line;
|
||||
}
|
||||
|
||||
var staleMessages = chatLines.Where(c => c.LifetimeEnd == double.MaxValue).ToArray();
|
||||
@@ -232,7 +222,41 @@ namespace osu.Game.Overlays.Chat
|
||||
|
||||
private void messageRemoved(Message removed) => Schedule(() =>
|
||||
{
|
||||
chatLines.FirstOrDefault(c => c.Message == removed)?.FadeColour(Color4.Red, 400).FadeOut(600).Expire();
|
||||
const double fade_time = 600;
|
||||
|
||||
ChatLine removedLine = chatLines.FirstOrDefault(c => c.Message == removed);
|
||||
|
||||
if (removedLine == null)
|
||||
return;
|
||||
|
||||
removedLine.FadeColour(Color4.Red, 400).FadeOut(fade_time).Expire();
|
||||
|
||||
// Resolve new colours and timestamps resulting from the removal.
|
||||
this.Delay(fade_time).Schedule(() =>
|
||||
{
|
||||
ChatLine lastLine = null;
|
||||
|
||||
// Preserve the colours of most-recent messages while updating the ones upwards in the list.
|
||||
foreach (var line in chatLines.Reverse().Except([removedLine]))
|
||||
{
|
||||
if (lastLine != null)
|
||||
line.AlternatingBackground = !lastLine.AlternatingBackground;
|
||||
|
||||
lastLine = line;
|
||||
}
|
||||
|
||||
lastLine = null;
|
||||
|
||||
// Timestamps may migrate to more recent messages.
|
||||
foreach (var line in chatLines.Except([removedLine]))
|
||||
{
|
||||
long minutes = line.Message.Timestamp.ToUnixTimeSeconds() / 60;
|
||||
long? lastMinutes = lastLine?.Message.Timestamp.ToUnixTimeSeconds() / 60;
|
||||
line.RequiresTimestamp = minutes != lastMinutes;
|
||||
|
||||
lastLine = line;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
private IEnumerable<ChatLine> chatLines => ChatLineFlow.Children.OfType<ChatLine>();
|
||||
|
||||
@@ -20,13 +20,11 @@ using System.Collections.Specialized;
|
||||
using System.Diagnostics;
|
||||
using osu.Framework.Extensions.LocalisationExtensions;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Overlays.Comments.Buttons;
|
||||
using osu.Game.Overlays.Dialog;
|
||||
using osu.Game.Overlays.OSD;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
|
||||
namespace osu.Game.Overlays.Comments
|
||||
@@ -83,10 +81,7 @@ namespace osu.Game.Overlays.Comments
|
||||
private IAPIProvider api { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private Clipboard clipboard { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private OnScreenDisplay? onScreenDisplay { get; set; }
|
||||
private OsuGame? game { get; set; }
|
||||
|
||||
public DrawableComment(Comment comment, IReadOnlyList<CommentableMeta> meta)
|
||||
{
|
||||
@@ -329,7 +324,7 @@ namespace osu.Game.Overlays.Comments
|
||||
if (WasDeleted)
|
||||
makeDeleted();
|
||||
|
||||
actionsContainer.AddLink(CommonStrings.ButtonsPermalink, copyUrl);
|
||||
actionsContainer.AddLink(CommonStrings.ButtonsPermalink, () => game?.CopyToClipboard($@"{api.Endpoints.APIUrl}/comments/{Comment.Id}"));
|
||||
actionsContainer.AddArbitraryDrawable(Empty().With(d => d.Width = 10));
|
||||
actionsContainer.AddLink(CommonStrings.ButtonsReply.ToLower(), toggleReply);
|
||||
actionsContainer.AddArbitraryDrawable(Empty().With(d => d.Width = 10));
|
||||
@@ -417,12 +412,6 @@ namespace osu.Game.Overlays.Comments
|
||||
api.Queue(request);
|
||||
}
|
||||
|
||||
private void copyUrl()
|
||||
{
|
||||
clipboard.SetText($@"{api.Endpoints.APIUrl}/comments/{Comment.Id}");
|
||||
onScreenDisplay?.Display(new CopiedToClipboardToast());
|
||||
}
|
||||
|
||||
private void toggleReply()
|
||||
{
|
||||
if (replyEditorContainer.Count == 0)
|
||||
|
||||
@@ -29,6 +29,8 @@ namespace osu.Game.Overlays
|
||||
{
|
||||
public partial class NowPlayingOverlay : OsuFocusedOverlayContainer, INamedOverlayComponent
|
||||
{
|
||||
public const double TRACK_DRAG_SEEK_DEBOUNCE = 200;
|
||||
|
||||
public IconUsage Icon => OsuIcon.Music;
|
||||
public LocalisableString Title => NowPlayingStrings.HeaderTitle;
|
||||
public LocalisableString Description => NowPlayingStrings.HeaderDescription;
|
||||
@@ -207,7 +209,8 @@ namespace osu.Game.Overlays
|
||||
Height = progress_height / 2,
|
||||
FillColour = colours.Yellow,
|
||||
BackgroundColour = colours.YellowDarker.Opacity(0.5f),
|
||||
OnSeek = musicController.SeekTo
|
||||
OnSeek = onSeek,
|
||||
OnCommit = onCommit,
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -221,6 +224,23 @@ namespace osu.Game.Overlays
|
||||
};
|
||||
}
|
||||
|
||||
private double? lastSeekTime;
|
||||
|
||||
private void onSeek(double progress)
|
||||
{
|
||||
if (lastSeekTime == null || Time.Current - lastSeekTime > TRACK_DRAG_SEEK_DEBOUNCE)
|
||||
{
|
||||
musicController.SeekTo(progress);
|
||||
lastSeekTime = Time.Current;
|
||||
}
|
||||
}
|
||||
|
||||
private void onCommit(double progress)
|
||||
{
|
||||
musicController.SeekTo(progress);
|
||||
lastSeekTime = null;
|
||||
}
|
||||
|
||||
private void togglePlaylist()
|
||||
{
|
||||
if (playlist == null)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user