mirror of
https://github.com/ppy/osu.git
synced 2025-02-22 19:12:56 +08:00
Merge branch 'master' into mania-hp-bar-position
This commit is contained in:
commit
855fa4e658
@ -10,7 +10,7 @@
|
||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.1206.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.1224.0" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||
|
@ -134,7 +134,6 @@ namespace osu.Desktop
|
||||
if (iconStream != null)
|
||||
host.Window.SetIconFromStream(iconStream);
|
||||
|
||||
host.Window.CursorState |= CursorState.Hidden;
|
||||
host.Window.Title = Name;
|
||||
}
|
||||
|
||||
|
15
osu.Game.Rulesets.Catch.Tests.iOS/AppDelegate.cs
Normal file
15
osu.Game.Rulesets.Catch.Tests.iOS/AppDelegate.cs
Normal file
@ -0,0 +1,15 @@
|
||||
// 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 Foundation;
|
||||
using osu.Framework.iOS;
|
||||
using osu.Game.Tests;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Tests.iOS
|
||||
{
|
||||
[Register("AppDelegate")]
|
||||
public class AppDelegate : GameApplicationDelegate
|
||||
{
|
||||
protected override Framework.Game CreateGame() => new OsuTestBrowser();
|
||||
}
|
||||
}
|
@ -1,16 +1,15 @@
|
||||
// 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.iOS;
|
||||
using osu.Game.Tests;
|
||||
using UIKit;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Tests.iOS
|
||||
{
|
||||
public static class Application
|
||||
public static class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
GameApplication.Main(new OsuTestBrowser());
|
||||
UIApplication.Main(args, null, typeof(AppDelegate));
|
||||
}
|
||||
}
|
||||
}
|
15
osu.Game.Rulesets.Mania.Tests.iOS/AppDelegate.cs
Normal file
15
osu.Game.Rulesets.Mania.Tests.iOS/AppDelegate.cs
Normal file
@ -0,0 +1,15 @@
|
||||
// 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 Foundation;
|
||||
using osu.Framework.iOS;
|
||||
using osu.Game.Tests;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests.iOS
|
||||
{
|
||||
[Register("AppDelegate")]
|
||||
public class AppDelegate : GameApplicationDelegate
|
||||
{
|
||||
protected override Framework.Game CreateGame() => new OsuTestBrowser();
|
||||
}
|
||||
}
|
@ -1,16 +1,15 @@
|
||||
// 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.iOS;
|
||||
using osu.Game.Tests;
|
||||
using UIKit;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests.iOS
|
||||
{
|
||||
public static class Application
|
||||
public static class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
GameApplication.Main(new OsuTestBrowser());
|
||||
UIApplication.Main(args, null, typeof(AppDelegate));
|
||||
}
|
||||
}
|
||||
}
|
@ -22,9 +22,11 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
|
||||
[TestCase("basic")]
|
||||
[TestCase("zero-length-slider")]
|
||||
[TestCase("mania-specific-spinner")]
|
||||
[TestCase("20544")]
|
||||
[TestCase("100374")]
|
||||
[TestCase("1450162")]
|
||||
[TestCase("4869637")]
|
||||
public void Test(string name) => base.Test(name);
|
||||
|
||||
protected override IEnumerable<ConvertValue> CreateConvertValue(HitObject hitObject)
|
||||
|
File diff suppressed because one or more lines are too long
1442
osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/4869637.osu
Normal file
1442
osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/4869637.osu
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,60 @@
|
||||
{
|
||||
"Mappings": [
|
||||
{
|
||||
"RandomW": 273071671,
|
||||
"RandomX": 842502087,
|
||||
"RandomY": 3579807591,
|
||||
"RandomZ": 273326509,
|
||||
"StartTime": 11783.0,
|
||||
"Objects": [
|
||||
{
|
||||
"StartTime": 11783.0,
|
||||
"EndTime": 15116.0,
|
||||
"Column": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"RandomW": 2659271247,
|
||||
"RandomX": 3579807591,
|
||||
"RandomY": 273326509,
|
||||
"RandomZ": 273071671,
|
||||
"StartTime": 91545.0,
|
||||
"Objects": [
|
||||
{
|
||||
"StartTime": 91545.0,
|
||||
"EndTime": 92735.0,
|
||||
"Column": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"RandomW": 3083635271,
|
||||
"RandomX": 273326509,
|
||||
"RandomY": 273071671,
|
||||
"RandomZ": 2659271247,
|
||||
"StartTime": 152497.0,
|
||||
"Objects": [
|
||||
{
|
||||
"StartTime": 152497.0,
|
||||
"EndTime": 153687.0,
|
||||
"Column": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"RandomW": 4073591514,
|
||||
"RandomX": 273071671,
|
||||
"RandomY": 2659271247,
|
||||
"RandomZ": 3083635271,
|
||||
"StartTime": 231545.0,
|
||||
"Objects": [
|
||||
{
|
||||
"StartTime": 231545.0,
|
||||
"EndTime": 232974.0,
|
||||
"Column": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
osu file format v14
|
||||
|
||||
[General]
|
||||
Mode: 3
|
||||
|
||||
[Difficulty]
|
||||
HPDrainRate:5
|
||||
CircleSize:4
|
||||
OverallDifficulty:5
|
||||
ApproachRate:0
|
||||
SliderMultiplier:2.6
|
||||
SliderTickRate:1
|
||||
|
||||
[TimingPoints]
|
||||
355,476.190476190476,4,2,1,60,1,0
|
||||
60652,-100,4,2,1,60,0,1
|
||||
92735,-100,4,2,1,60,0,0
|
||||
121485,-100,4,2,1,60,0,1
|
||||
153688,-100,4,2,1,60,0,0
|
||||
182497,-100,4,2,1,60,0,1
|
||||
213688,-100,4,2,1,60,0,0
|
||||
|
||||
[HitObjects]
|
||||
256,192,11783,12,0,15116,0:0:0:0:
|
||||
256,192,91545,12,0,92735,0:0:0:0:
|
||||
256,192,152497,12,0,153687,0:0:0:0:
|
||||
256,192,231545,12,0,232974,0:0:0:0:
|
@ -7,11 +7,13 @@ using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Legacy;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps.Patterns;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects.Legacy;
|
||||
using osu.Game.Rulesets.Scoring.Legacy;
|
||||
using osu.Game.Utils;
|
||||
using osuTK;
|
||||
@ -124,16 +126,109 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
||||
|
||||
protected override IEnumerable<ManiaHitObject> ConvertHitObject(HitObject original, IBeatmap beatmap, CancellationToken cancellationToken)
|
||||
{
|
||||
if (original is ManiaHitObject maniaOriginal)
|
||||
{
|
||||
yield return maniaOriginal;
|
||||
LegacyHitObjectType legacyType;
|
||||
|
||||
yield break;
|
||||
switch (original)
|
||||
{
|
||||
case ManiaHitObject maniaObj:
|
||||
{
|
||||
yield return maniaObj;
|
||||
|
||||
yield break;
|
||||
}
|
||||
|
||||
case IHasLegacyHitObjectType legacy:
|
||||
legacyType = legacy.LegacyType & LegacyHitObjectType.ObjectTypes;
|
||||
break;
|
||||
|
||||
case IHasPath:
|
||||
legacyType = LegacyHitObjectType.Slider;
|
||||
break;
|
||||
|
||||
case IHasDuration:
|
||||
legacyType = LegacyHitObjectType.Hold;
|
||||
break;
|
||||
|
||||
default:
|
||||
legacyType = LegacyHitObjectType.Circle;
|
||||
break;
|
||||
}
|
||||
|
||||
var objects = IsForCurrentRuleset ? generateSpecific(original, beatmap) : generateConverted(original, beatmap);
|
||||
foreach (ManiaHitObject obj in objects)
|
||||
yield return obj;
|
||||
double startTime = original.StartTime;
|
||||
double endTime = (original as IHasDuration)?.EndTime ?? startTime;
|
||||
Vector2 position = (original as IHasPosition)?.Position ?? Vector2.Zero;
|
||||
|
||||
PatternGenerator conversion;
|
||||
|
||||
switch (legacyType)
|
||||
{
|
||||
case LegacyHitObjectType.Circle:
|
||||
if (IsForCurrentRuleset)
|
||||
{
|
||||
conversion = new PassThroughPatternGenerator(Random, original, beatmap, TotalColumns, lastPattern);
|
||||
recordNote(startTime, position);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Note: The density is used during the pattern generator constructor, and intentionally computed first.
|
||||
computeDensity(startTime);
|
||||
conversion = new HitCirclePatternGenerator(Random, original, beatmap, TotalColumns, lastPattern, lastTime, lastPosition, density, lastStair);
|
||||
recordNote(startTime, position);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case LegacyHitObjectType.Slider:
|
||||
if (IsForCurrentRuleset)
|
||||
{
|
||||
conversion = new PassThroughPatternGenerator(Random, original, beatmap, TotalColumns, lastPattern);
|
||||
recordNote(original.StartTime, position);
|
||||
}
|
||||
else
|
||||
{
|
||||
var generator = new SliderPatternGenerator(Random, original, beatmap, TotalColumns, lastPattern);
|
||||
conversion = generator;
|
||||
|
||||
for (int i = 0; i <= generator.SpanCount; i++)
|
||||
{
|
||||
double time = original.StartTime + generator.SegmentDuration * i;
|
||||
|
||||
recordNote(time, position);
|
||||
computeDensity(time);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case LegacyHitObjectType.Spinner:
|
||||
// Note: Some older mania-specific beatmaps can have spinners that are converted rather than passed through.
|
||||
// Newer beatmaps will usually use the "hold" hitobject type below.
|
||||
conversion = new SpinnerPatternGenerator(Random, original, beatmap, TotalColumns, lastPattern);
|
||||
recordNote(endTime, new Vector2(256, 192));
|
||||
computeDensity(endTime);
|
||||
break;
|
||||
|
||||
case LegacyHitObjectType.Hold:
|
||||
conversion = new PassThroughPatternGenerator(Random, original, beatmap, TotalColumns, lastPattern);
|
||||
recordNote(endTime, position);
|
||||
computeDensity(endTime);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentException($"Invalid legacy object type: {legacyType}", nameof(original));
|
||||
}
|
||||
|
||||
foreach (var newPattern in conversion.Generate())
|
||||
{
|
||||
if (conversion is HitCirclePatternGenerator circleGenerator)
|
||||
lastStair = circleGenerator.StairType;
|
||||
|
||||
if (conversion is HitCirclePatternGenerator || conversion is SliderPatternGenerator)
|
||||
lastPattern = newPattern;
|
||||
|
||||
foreach (var obj in newPattern.HitObjects)
|
||||
yield return obj;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly LimitedCapacityQueue<double> prevNoteTimes = new LimitedCapacityQueue<double>(max_notes_for_density);
|
||||
@ -156,135 +251,5 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
||||
lastTime = time;
|
||||
lastPosition = position;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method that generates hit objects for osu!mania specific beatmaps.
|
||||
/// </summary>
|
||||
/// <param name="original">The original hit object.</param>
|
||||
/// <param name="originalBeatmap">The original beatmap. This is used to look-up any values dependent on a fully-loaded beatmap.</param>
|
||||
/// <returns>The hit objects generated.</returns>
|
||||
private IEnumerable<ManiaHitObject> generateSpecific(HitObject original, IBeatmap originalBeatmap)
|
||||
{
|
||||
var generator = new SpecificBeatmapPatternGenerator(Random, original, originalBeatmap, TotalColumns, lastPattern);
|
||||
|
||||
foreach (var newPattern in generator.Generate())
|
||||
{
|
||||
lastPattern = newPattern;
|
||||
|
||||
foreach (var obj in newPattern.HitObjects)
|
||||
yield return obj;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method that generates hit objects for non-osu!mania beatmaps.
|
||||
/// </summary>
|
||||
/// <param name="original">The original hit object.</param>
|
||||
/// <param name="originalBeatmap">The original beatmap. This is used to look-up any values dependent on a fully-loaded beatmap.</param>
|
||||
/// <returns>The hit objects generated.</returns>
|
||||
private IEnumerable<ManiaHitObject> generateConverted(HitObject original, IBeatmap originalBeatmap)
|
||||
{
|
||||
Patterns.PatternGenerator? conversion = null;
|
||||
|
||||
switch (original)
|
||||
{
|
||||
case IHasPath:
|
||||
{
|
||||
var generator = new PathObjectPatternGenerator(Random, original, originalBeatmap, TotalColumns, lastPattern);
|
||||
conversion = generator;
|
||||
|
||||
var positionData = original as IHasPosition;
|
||||
|
||||
for (int i = 0; i <= generator.SpanCount; i++)
|
||||
{
|
||||
double time = original.StartTime + generator.SegmentDuration * i;
|
||||
|
||||
recordNote(time, positionData?.Position ?? Vector2.Zero);
|
||||
computeDensity(time);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case IHasDuration endTimeData:
|
||||
{
|
||||
conversion = new EndTimeObjectPatternGenerator(Random, original, originalBeatmap, TotalColumns, lastPattern);
|
||||
|
||||
recordNote(endTimeData.EndTime, new Vector2(256, 192));
|
||||
computeDensity(endTimeData.EndTime);
|
||||
break;
|
||||
}
|
||||
|
||||
case IHasPosition positionData:
|
||||
{
|
||||
computeDensity(original.StartTime);
|
||||
|
||||
conversion = new HitObjectPatternGenerator(Random, original, originalBeatmap, TotalColumns, lastPattern, lastTime, lastPosition, density, lastStair);
|
||||
|
||||
recordNote(original.StartTime, positionData.Position);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (conversion == null)
|
||||
yield break;
|
||||
|
||||
foreach (var newPattern in conversion.Generate())
|
||||
{
|
||||
lastPattern = conversion is EndTimeObjectPatternGenerator ? lastPattern : newPattern;
|
||||
lastStair = (conversion as HitObjectPatternGenerator)?.StairType ?? lastStair;
|
||||
|
||||
foreach (var obj in newPattern.HitObjects)
|
||||
yield return obj;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A pattern generator for osu!mania-specific beatmaps.
|
||||
/// </summary>
|
||||
private class SpecificBeatmapPatternGenerator : Patterns.Legacy.PatternGenerator
|
||||
{
|
||||
public SpecificBeatmapPatternGenerator(LegacyRandom random, HitObject hitObject, IBeatmap beatmap, int totalColumns, Pattern previousPattern)
|
||||
: base(random, hitObject, beatmap, previousPattern, totalColumns)
|
||||
{
|
||||
}
|
||||
|
||||
public override IEnumerable<Pattern> Generate()
|
||||
{
|
||||
yield return generate();
|
||||
}
|
||||
|
||||
private Pattern generate()
|
||||
{
|
||||
var positionData = HitObject as IHasXPosition;
|
||||
|
||||
int column = GetColumn(positionData?.X ?? 0);
|
||||
|
||||
var pattern = new Pattern();
|
||||
|
||||
if (HitObject is IHasDuration endTimeData)
|
||||
{
|
||||
pattern.Add(new HoldNote
|
||||
{
|
||||
StartTime = HitObject.StartTime,
|
||||
Duration = endTimeData.Duration,
|
||||
Column = column,
|
||||
Samples = HitObject.Samples,
|
||||
NodeSamples = (HitObject as IHasRepeats)?.NodeSamples ?? HoldNote.CreateDefaultNodeSamples(HitObject)
|
||||
});
|
||||
}
|
||||
else if (HitObject is IHasXPosition)
|
||||
{
|
||||
pattern.Add(new Note
|
||||
{
|
||||
StartTime = HitObject.StartTime,
|
||||
Samples = HitObject.Samples,
|
||||
Column = column
|
||||
});
|
||||
}
|
||||
|
||||
return pattern;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,13 +16,16 @@ using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
{
|
||||
internal class HitObjectPatternGenerator : PatternGenerator
|
||||
/// <summary>
|
||||
/// Converter for legacy "HitCircle" hit objects.
|
||||
/// </summary>
|
||||
internal class HitCirclePatternGenerator : LegacyPatternGenerator
|
||||
{
|
||||
public PatternType StairType { get; private set; }
|
||||
|
||||
private readonly PatternType convertType;
|
||||
|
||||
public HitObjectPatternGenerator(LegacyRandom random, HitObject hitObject, IBeatmap beatmap, int totalColumns, Pattern previousPattern, double previousTime, Vector2 previousPosition,
|
||||
public HitCirclePatternGenerator(LegacyRandom random, HitObject hitObject, IBeatmap beatmap, int totalColumns, Pattern previousPattern, double previousTime, Vector2 previousPosition,
|
||||
double density, PatternType lastStair)
|
||||
: base(random, hitObject, beatmap, previousPattern, totalColumns)
|
||||
{
|
||||
@ -114,10 +117,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
}
|
||||
|
||||
if (convertType.HasFlag(PatternType.Cycle) && PreviousPattern.HitObjects.Count() == 1
|
||||
// If we convert to 7K + 1, let's not overload the special key
|
||||
&& (TotalColumns != 8 || lastColumn != 0)
|
||||
// Make sure the last column was not the centre column
|
||||
&& (TotalColumns % 2 == 0 || lastColumn != TotalColumns / 2))
|
||||
// If we convert to 7K + 1, let's not overload the special key
|
||||
&& (TotalColumns != 8 || lastColumn != 0)
|
||||
// Make sure the last column was not the centre column
|
||||
&& (TotalColumns % 2 == 0 || lastColumn != TotalColumns / 2))
|
||||
{
|
||||
// Generate a new pattern by cycling backwards (similar to Reverse but for only one hit object)
|
||||
int column = RandomStart + TotalColumns - lastColumn - 1;
|
@ -1,8 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
@ -15,7 +13,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
/// <summary>
|
||||
/// A pattern generator for legacy hit objects.
|
||||
/// </summary>
|
||||
internal abstract class PatternGenerator : Patterns.PatternGenerator
|
||||
internal abstract class LegacyPatternGenerator : PatternGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// The column index at which to start generating random notes.
|
||||
@ -27,7 +25,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
/// </summary>
|
||||
protected readonly LegacyRandom Random;
|
||||
|
||||
protected PatternGenerator(LegacyRandom random, HitObject hitObject, IBeatmap beatmap, Pattern previousPattern, int totalColumns)
|
||||
protected LegacyPatternGenerator(LegacyRandom random, HitObject hitObject, IBeatmap beatmap, Pattern previousPattern, int totalColumns)
|
||||
: base(hitObject, beatmap, totalColumns, previousPattern)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(random);
|
||||
@ -96,8 +94,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
if (conversionDifficulty != null)
|
||||
return conversionDifficulty.Value;
|
||||
|
||||
HitObject lastObject = Beatmap.HitObjects.LastOrDefault();
|
||||
HitObject firstObject = Beatmap.HitObjects.FirstOrDefault();
|
||||
HitObject? lastObject = Beatmap.HitObjects.LastOrDefault();
|
||||
HitObject? firstObject = Beatmap.HitObjects.FirstOrDefault();
|
||||
|
||||
// Drain time in seconds
|
||||
int drainTime = (int)(((lastObject?.StartTime ?? 0) - (firstObject?.StartTime ?? 0) - Beatmap.TotalBreakTime) / 1000);
|
||||
@ -132,13 +130,13 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
/// <param name="nextColumn">A function to retrieve the next column. If null, a randomisation scheme will be used.</param>
|
||||
/// <param name="validation">A function to perform additional validation checks to determine if a column is a valid candidate for a <see cref="HitObject"/>.</param>
|
||||
/// <param name="lowerBound">The minimum column index. If null, <see cref="RandomStart"/> is used.</param>
|
||||
/// <param name="upperBound">The maximum column index. If null, <see cref="Patterns.PatternGenerator.TotalColumns">TotalColumns</see> is used.</param>
|
||||
/// <param name="upperBound">The maximum column index. If null, <see cref="PatternGenerator.TotalColumns">TotalColumns</see> is used.</param>
|
||||
/// <param name="patterns">A list of patterns for which the validity of a column should be checked against.
|
||||
/// A column is not a valid candidate if a <see cref="HitObject"/> occupies the same column in any of the patterns.</param>
|
||||
/// <returns>A column which has passed the <paramref name="validation"/> check and for which there are no
|
||||
/// <see cref="HitObject"/>s in any of <paramref name="patterns"/> occupying the same column.</returns>
|
||||
/// <exception cref="NotEnoughColumnsException">If there are no valid candidate columns.</exception>
|
||||
protected int FindAvailableColumn(int initialColumn, int? lowerBound = null, int? upperBound = null, Func<int, int> nextColumn = null, [InstantHandle] Func<int, bool> validation = null,
|
||||
protected int FindAvailableColumn(int initialColumn, int? lowerBound = null, int? upperBound = null, Func<int, int>? nextColumn = null, [InstantHandle] Func<int, bool>? validation = null,
|
||||
params Pattern[] patterns)
|
||||
{
|
||||
lowerBound ??= RandomStart;
|
||||
@ -189,7 +187,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
/// Returns a random column index in the range [<paramref name="lowerBound"/>, <paramref name="upperBound"/>).
|
||||
/// </summary>
|
||||
/// <param name="lowerBound">The minimum column index. If null, <see cref="RandomStart"/> is used.</param>
|
||||
/// <param name="upperBound">The maximum column index. If null, <see cref="Patterns.PatternGenerator.TotalColumns"/> is used.</param>
|
||||
/// <param name="upperBound">The maximum column index. If null, <see cref="PatternGenerator.TotalColumns"/> is used.</param>
|
||||
protected int GetRandomColumn(int? lowerBound = null, int? upperBound = null) => Random.Next(lowerBound ?? RandomStart, upperBound ?? TotalColumns);
|
||||
|
||||
/// <summary>
|
@ -0,0 +1,55 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
{
|
||||
/// <summary>
|
||||
/// A simple generator which, for any object, if the hitobject has an end time
|
||||
/// it becomes a <see cref="HoldNote"/> or otherwise a <see cref="Note"/>.
|
||||
/// </summary>
|
||||
internal class PassThroughPatternGenerator : LegacyPatternGenerator
|
||||
{
|
||||
public PassThroughPatternGenerator(LegacyRandom random, HitObject hitObject, IBeatmap beatmap, int totalColumns, Pattern previousPattern)
|
||||
: base(random, hitObject, beatmap, previousPattern, totalColumns)
|
||||
{
|
||||
}
|
||||
|
||||
public override IEnumerable<Pattern> Generate()
|
||||
{
|
||||
var positionData = HitObject as IHasXPosition;
|
||||
int column = GetColumn(positionData?.X ?? 0);
|
||||
|
||||
var pattern = new Pattern();
|
||||
|
||||
if (HitObject is IHasDuration endTimeData)
|
||||
{
|
||||
pattern.Add(new HoldNote
|
||||
{
|
||||
StartTime = HitObject.StartTime,
|
||||
Duration = endTimeData.Duration,
|
||||
Column = column,
|
||||
Samples = HitObject.Samples,
|
||||
NodeSamples = (HitObject as IHasRepeats)?.NodeSamples ?? HoldNote.CreateDefaultNodeSamples(HitObject)
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
pattern.Add(new Note
|
||||
{
|
||||
StartTime = HitObject.StartTime,
|
||||
Samples = HitObject.Samples,
|
||||
Column = column
|
||||
});
|
||||
}
|
||||
|
||||
yield return pattern;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
@ -19,9 +17,9 @@ using osu.Game.Utils;
|
||||
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
{
|
||||
/// <summary>
|
||||
/// A pattern generator for IHasDistance hit objects.
|
||||
/// Converter for legacy "Slider" hit objects.
|
||||
/// </summary>
|
||||
internal class PathObjectPatternGenerator : PatternGenerator
|
||||
internal class SliderPatternGenerator : LegacyPatternGenerator
|
||||
{
|
||||
public readonly int StartTime;
|
||||
public readonly int EndTime;
|
||||
@ -30,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
|
||||
private PatternType convertType;
|
||||
|
||||
public PathObjectPatternGenerator(LegacyRandom random, HitObject hitObject, IBeatmap beatmap, int totalColumns, Pattern previousPattern)
|
||||
public SliderPatternGenerator(LegacyRandom random, HitObject hitObject, IBeatmap beatmap, int totalColumns, Pattern previousPattern)
|
||||
: base(random, hitObject, beatmap, previousPattern, totalColumns)
|
||||
{
|
||||
convertType = PatternType.None;
|
||||
@ -484,9 +482,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
/// Retrieves the list of node samples that occur at time greater than or equal to <paramref name="time"/>.
|
||||
/// </summary>
|
||||
/// <param name="time">The time to retrieve node samples at.</param>
|
||||
private IList<IList<HitSampleInfo>> nodeSamplesAt(int time)
|
||||
private IList<IList<HitSampleInfo>>? nodeSamplesAt(int time)
|
||||
{
|
||||
if (!(HitObject is IHasPathWithRepeats curveData))
|
||||
if (HitObject is not IHasPathWithRepeats curveData)
|
||||
return null;
|
||||
|
||||
int index = SegmentDuration == 0 ? 0 : (time - StartTime) / SegmentDuration;
|
@ -12,12 +12,15 @@ using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
{
|
||||
internal class EndTimeObjectPatternGenerator : PatternGenerator
|
||||
/// <summary>
|
||||
/// Converter for legacy "Spinner" hit objects.
|
||||
/// </summary>
|
||||
internal class SpinnerPatternGenerator : LegacyPatternGenerator
|
||||
{
|
||||
private readonly int endTime;
|
||||
private readonly PatternType convertType;
|
||||
|
||||
public EndTimeObjectPatternGenerator(LegacyRandom random, HitObject hitObject, IBeatmap beatmap, int totalColumns, Pattern previousPattern)
|
||||
public SpinnerPatternGenerator(LegacyRandom random, HitObject hitObject, IBeatmap beatmap, int totalColumns, Pattern previousPattern)
|
||||
: base(random, hitObject, beatmap, previousPattern, totalColumns)
|
||||
{
|
||||
endTime = (int)((HitObject as IHasDuration)?.EndTime ?? 0);
|
@ -1,9 +1,8 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
|
||||
@ -14,8 +13,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns
|
||||
/// </summary>
|
||||
internal class Pattern
|
||||
{
|
||||
private List<ManiaHitObject> hitObjects;
|
||||
private HashSet<int> containedColumns;
|
||||
private List<ManiaHitObject>? hitObjects;
|
||||
private HashSet<int>? containedColumns;
|
||||
|
||||
/// <summary>
|
||||
/// All the hit objects contained in this pattern.
|
||||
@ -72,6 +71,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns
|
||||
containedColumns?.Clear();
|
||||
}
|
||||
|
||||
[MemberNotNull(nameof(hitObjects), nameof(containedColumns))]
|
||||
private void prepareStorage()
|
||||
{
|
||||
hitObjects ??= new List<ManiaHitObject>();
|
||||
|
15
osu.Game.Rulesets.Osu.Tests.iOS/AppDelegate.cs
Normal file
15
osu.Game.Rulesets.Osu.Tests.iOS/AppDelegate.cs
Normal file
@ -0,0 +1,15 @@
|
||||
// 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 Foundation;
|
||||
using osu.Framework.iOS;
|
||||
using osu.Game.Tests;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests.iOS
|
||||
{
|
||||
[Register("AppDelegate")]
|
||||
public class AppDelegate : GameApplicationDelegate
|
||||
{
|
||||
protected override Framework.Game CreateGame() => new OsuTestBrowser();
|
||||
}
|
||||
}
|
@ -1,16 +1,15 @@
|
||||
// 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.iOS;
|
||||
using osu.Game.Tests;
|
||||
using UIKit;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests.iOS
|
||||
{
|
||||
public static class Application
|
||||
public static class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
GameApplication.Main(new OsuTestBrowser());
|
||||
UIApplication.Main(args, null, typeof(AppDelegate));
|
||||
}
|
||||
}
|
||||
}
|
@ -76,6 +76,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
base.OnDragEnd(e);
|
||||
}
|
||||
|
||||
protected override bool OnMouseDown(MouseDownEvent e) => true;
|
||||
|
||||
protected override bool OnClick(ClickEvent e) => true;
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
Colour = IsHovered || IsDragged ? colours.Red : colours.Yellow;
|
||||
|
@ -61,13 +61,16 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
|
||||
var drawableOsuObject = (DrawableOsuHitObject?)drawableObject;
|
||||
|
||||
// As a precondition, ensure that any prefix lookups are run against the skin which is providing "hitcircle".
|
||||
// As a precondition, prefer that any *prefix* lookups are run against the skin which is providing "hitcircle".
|
||||
// This is to correctly handle a case such as:
|
||||
//
|
||||
// - Beatmap provides `hitcircle`
|
||||
// - User skin provides `sliderstartcircle`
|
||||
//
|
||||
// In such a case, the `hitcircle` should be used for slider start circles rather than the user's skin override.
|
||||
//
|
||||
// Of note, this consideration should only be used to decide whether to continue looking up the prefixed name or not.
|
||||
// The final lookups must still run on the full skin hierarchy as per usual in order to correctly handle fallback cases.
|
||||
var provider = skin.FindProvider(s => s.GetTexture(base_lookup) != null) ?? skin;
|
||||
|
||||
// if a base texture for the specified prefix exists, continue using it for subsequent lookups.
|
||||
@ -81,7 +84,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
// expected behaviour in this scenario is not showing the overlay, rather than using hitcircleoverlay.png.
|
||||
InternalChildren = new[]
|
||||
{
|
||||
CircleSprite = new LegacyKiaiFlashingDrawable(() => new Sprite { Texture = provider.GetTexture(circleName)?.WithMaximumSize(maxSize) })
|
||||
CircleSprite = new LegacyKiaiFlashingDrawable(() => new Sprite { Texture = skin.GetTexture(circleName)?.WithMaximumSize(maxSize) })
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
@ -90,7 +93,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Child = OverlaySprite = new LegacyKiaiFlashingDrawable(() => new Sprite { Texture = provider.GetTexture(@$"{circleName}overlay")?.WithMaximumSize(maxSize) })
|
||||
Child = OverlaySprite = new LegacyKiaiFlashingDrawable(() => new Sprite { Texture = skin.GetTexture(@$"{circleName}overlay")?.WithMaximumSize(maxSize) })
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
|
@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
|
||||
protected override ResumeOverlay CreateResumeOverlay()
|
||||
{
|
||||
if (Mods.Any(m => m is OsuModAutopilot))
|
||||
if (Mods.Any(m => m is OsuModAutopilot or OsuModTouchDevice))
|
||||
return new DelayedResumeOverlay { Scale = new Vector2(0.65f) };
|
||||
|
||||
return new OsuResumeOverlay();
|
||||
|
15
osu.Game.Rulesets.Taiko.Tests.iOS/AppDelegate.cs
Normal file
15
osu.Game.Rulesets.Taiko.Tests.iOS/AppDelegate.cs
Normal file
@ -0,0 +1,15 @@
|
||||
// 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 Foundation;
|
||||
using osu.Framework.iOS;
|
||||
using osu.Game.Tests;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Tests.iOS
|
||||
{
|
||||
[Register("AppDelegate")]
|
||||
public class AppDelegate : GameApplicationDelegate
|
||||
{
|
||||
protected override Framework.Game CreateGame() => new OsuTestBrowser();
|
||||
}
|
||||
}
|
@ -1,16 +1,15 @@
|
||||
// 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.iOS;
|
||||
using osu.Game.Tests;
|
||||
using UIKit;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Tests.iOS
|
||||
{
|
||||
public static class Application
|
||||
public static class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
GameApplication.Main(new OsuTestBrowser());
|
||||
UIApplication.Main(args, null, typeof(AppDelegate));
|
||||
}
|
||||
}
|
||||
}
|
14
osu.Game.Tests.iOS/AppDelegate.cs
Normal file
14
osu.Game.Tests.iOS/AppDelegate.cs
Normal file
@ -0,0 +1,14 @@
|
||||
// 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 Foundation;
|
||||
using osu.Framework.iOS;
|
||||
|
||||
namespace osu.Game.Tests.iOS
|
||||
{
|
||||
[Register("AppDelegate")]
|
||||
public class AppDelegate : GameApplicationDelegate
|
||||
{
|
||||
protected override Framework.Game CreateGame() => new OsuTestBrowser();
|
||||
}
|
||||
}
|
@ -1,15 +1,15 @@
|
||||
// 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.iOS;
|
||||
using UIKit;
|
||||
|
||||
namespace osu.Game.Tests.iOS
|
||||
{
|
||||
public static class Application
|
||||
public static class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
GameApplication.Main(new OsuTestBrowser());
|
||||
UIApplication.Main(args, null, typeof(AppDelegate));
|
||||
}
|
||||
}
|
||||
}
|
BIN
osu.Game.Tests/Resources/Archives/modified-default-20241207.osk
Normal file
BIN
osu.Game.Tests/Resources/Archives/modified-default-20241207.osk
Normal file
Binary file not shown.
@ -67,7 +67,9 @@ namespace osu.Game.Tests.Skins
|
||||
// Covers legacy rank display
|
||||
"Archives/modified-classic-20230809.osk",
|
||||
// Covers legacy key counter
|
||||
"Archives/modified-classic-20240724.osk"
|
||||
"Archives/modified-classic-20240724.osk",
|
||||
// Covers skinnable mod display
|
||||
"Archives/modified-default-20241207.osk"
|
||||
};
|
||||
|
||||
[Resolved]
|
||||
|
@ -131,21 +131,6 @@ namespace osu.Game.Tests.Visual.Background
|
||||
assertNoBackgrounds();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDelayedConnectivity()
|
||||
{
|
||||
registerBackgroundsResponse(DateTimeOffset.Now.AddDays(30));
|
||||
setSeasonalBackgroundMode(SeasonalBackgroundMode.Always);
|
||||
AddStep("go offline", () => dummyAPI.SetState(APIState.Offline));
|
||||
|
||||
createLoader();
|
||||
assertNoBackgrounds();
|
||||
|
||||
AddStep("go online", () => dummyAPI.SetState(APIState.Online));
|
||||
|
||||
assertAnyBackground();
|
||||
}
|
||||
|
||||
private void registerBackgroundsResponse(DateTimeOffset endDate)
|
||||
=> AddStep("setup request handler", () =>
|
||||
{
|
||||
@ -185,7 +170,8 @@ namespace osu.Game.Tests.Visual.Background
|
||||
{
|
||||
previousBackground = (SeasonalBackground)backgroundContainer.SingleOrDefault();
|
||||
background = backgroundLoader.LoadNextBackground();
|
||||
LoadComponentAsync(background, bg => backgroundContainer.Child = bg);
|
||||
if (background != null)
|
||||
LoadComponentAsync(background, bg => backgroundContainer.Child = bg);
|
||||
});
|
||||
|
||||
AddUntilStep("background loaded", () => background.IsLoaded);
|
||||
|
@ -203,12 +203,19 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
[Test]
|
||||
public void TestCreateNewDifficultyWithScrollSpeed_SameRuleset()
|
||||
{
|
||||
string firstDifficultyName = Guid.NewGuid().ToString();
|
||||
string previousDifficultyName = null!;
|
||||
|
||||
AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = previousDifficultyName = Guid.NewGuid().ToString());
|
||||
AddStep("save beatmap", () => Editor.Save());
|
||||
AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new ManiaRuleset().RulesetInfo));
|
||||
|
||||
AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = firstDifficultyName);
|
||||
AddUntilStep("wait for created", () =>
|
||||
{
|
||||
string? difficultyName = Editor.ChildrenOfType<EditorBeatmap>().SingleOrDefault()?.BeatmapInfo.DifficultyName;
|
||||
return difficultyName != null && difficultyName != previousDifficultyName;
|
||||
});
|
||||
|
||||
AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = previousDifficultyName = Guid.NewGuid().ToString());
|
||||
AddStep("add timing point", () => EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 }));
|
||||
AddStep("add effect points", () =>
|
||||
{
|
||||
@ -229,7 +236,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
AddUntilStep("wait for created", () =>
|
||||
{
|
||||
string? difficultyName = Editor.ChildrenOfType<EditorBeatmap>().SingleOrDefault()?.BeatmapInfo.DifficultyName;
|
||||
return difficultyName != null && difficultyName != firstDifficultyName;
|
||||
return difficultyName != null && difficultyName != previousDifficultyName;
|
||||
});
|
||||
|
||||
AddAssert("created difficulty has timing point", () =>
|
||||
|
@ -32,6 +32,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
|
||||
private TimingScreen timingScreen;
|
||||
private EditorBeatmap editorBeatmap;
|
||||
private BeatmapEditorChangeHandler changeHandler;
|
||||
|
||||
protected override bool ScrollUsingMouseWheel => false;
|
||||
|
||||
@ -46,6 +47,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
private void reloadEditorBeatmap()
|
||||
{
|
||||
editorBeatmap = new EditorBeatmap(Beatmap.Value.GetPlayableBeatmap(Ruleset.Value));
|
||||
changeHandler = new BeatmapEditorChangeHandler(editorBeatmap);
|
||||
|
||||
Child = new DependencyProvidingContainer
|
||||
{
|
||||
@ -53,6 +55,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
CachedDependencies = new (Type, object)[]
|
||||
{
|
||||
(typeof(EditorBeatmap), editorBeatmap),
|
||||
(typeof(IEditorChangeHandler), changeHandler),
|
||||
(typeof(IBeatSnapProvider), editorBeatmap)
|
||||
},
|
||||
Child = timingScreen = new TimingScreen
|
||||
@ -72,8 +75,10 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
AddUntilStep("Wait for rows to load", () => Child.ChildrenOfType<EffectRowAttribute>().Any());
|
||||
}
|
||||
|
||||
// TODO: this is best-effort for now, but the comment out test below should probably be how things should work.
|
||||
// Was originally working as of https://github.com/ppy/osu/pull/26141; Regressed at some point.
|
||||
[Test]
|
||||
public void TestSelectedRetainedOverUndo()
|
||||
public void TestSelectionDismissedOnUndo()
|
||||
{
|
||||
AddStep("Select first timing point", () =>
|
||||
{
|
||||
@ -95,25 +100,52 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
return timingScreen.SelectedGroup.Value.ControlPoints.Any(c => c is TimingControlPoint) && timingScreen.SelectedGroup.Value.Time > 2170;
|
||||
});
|
||||
|
||||
AddStep("simulate undo", () =>
|
||||
{
|
||||
var clone = editorBeatmap.ControlPointInfo.DeepClone();
|
||||
AddStep("undo", () => changeHandler?.RestoreState(-1));
|
||||
|
||||
editorBeatmap.ControlPointInfo.Clear();
|
||||
|
||||
foreach (var group in clone.Groups)
|
||||
{
|
||||
foreach (var cp in group.ControlPoints)
|
||||
editorBeatmap.ControlPointInfo.Add(group.Time, cp);
|
||||
}
|
||||
});
|
||||
|
||||
AddUntilStep("selection retained", () =>
|
||||
{
|
||||
return timingScreen.SelectedGroup.Value.ControlPoints.Any(c => c is TimingControlPoint) && timingScreen.SelectedGroup.Value.Time > 2170;
|
||||
});
|
||||
AddUntilStep("selection dismissed", () => timingScreen.SelectedGroup.Value, () => Is.Null);
|
||||
}
|
||||
|
||||
// [Test]
|
||||
// public void TestSelectedRetainedOverUndo()
|
||||
// {
|
||||
// AddStep("Select first timing point", () =>
|
||||
// {
|
||||
// InputManager.MoveMouseTo(Child.ChildrenOfType<TimingRowAttribute>().First());
|
||||
// InputManager.Click(MouseButton.Left);
|
||||
// });
|
||||
//
|
||||
// AddUntilStep("Selection changed", () => timingScreen.SelectedGroup.Value.Time == 2170);
|
||||
// AddUntilStep("Ensure seeked to correct time", () => EditorClock.CurrentTimeAccurate == 2170);
|
||||
//
|
||||
// AddStep("Adjust offset", () =>
|
||||
// {
|
||||
// InputManager.MoveMouseTo(timingScreen.ChildrenOfType<TimingAdjustButton>().First().ScreenSpaceDrawQuad.Centre + new Vector2(20, 0));
|
||||
// InputManager.Click(MouseButton.Left);
|
||||
// });
|
||||
//
|
||||
// AddUntilStep("wait for offset changed", () =>
|
||||
// {
|
||||
// return timingScreen.SelectedGroup.Value.ControlPoints.Any(c => c is TimingControlPoint) && timingScreen.SelectedGroup.Value.Time > 2170;
|
||||
// });
|
||||
//
|
||||
// AddStep("undo", () => changeHandler?.RestoreState(-1));
|
||||
//
|
||||
// AddUntilStep("selection retained", () =>
|
||||
// {
|
||||
// return timingScreen.SelectedGroup.Value.ControlPoints.Any(c => c is TimingControlPoint) && timingScreen.SelectedGroup.Value.Time > 2170;
|
||||
// });
|
||||
//
|
||||
// AddAssert("check group count", () => editorBeatmap.ControlPointInfo.Groups.Count, () => Is.EqualTo(10));
|
||||
//
|
||||
// AddStep("Adjust offset", () =>
|
||||
// {
|
||||
// InputManager.MoveMouseTo(timingScreen.ChildrenOfType<TimingAdjustButton>().First().ScreenSpaceDrawQuad.Centre + new Vector2(20, 0));
|
||||
// InputManager.Click(MouseButton.Left);
|
||||
// });
|
||||
//
|
||||
// AddAssert("check group count", () => editorBeatmap.ControlPointInfo.Groups.Count, () => Is.EqualTo(10));
|
||||
// }
|
||||
|
||||
[Test]
|
||||
public void TestScrollControlGroupIntoView()
|
||||
{
|
||||
|
@ -12,6 +12,7 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
@ -19,6 +20,7 @@ using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Storyboards;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
@ -28,6 +30,12 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
protected new PausePlayer Player => (PausePlayer)base.Player;
|
||||
|
||||
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
|
||||
{
|
||||
beatmap.AudioLeadIn = 4000;
|
||||
return base.CreateWorkingBeatmap(beatmap, storyboard);
|
||||
}
|
||||
|
||||
private readonly Container content;
|
||||
|
||||
protected override Container<Drawable> Content => content;
|
||||
@ -200,8 +208,10 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Ignore("Fails on github runners if they happen to skip too far forward in time.")]
|
||||
public void TestUserPauseDuringCooldownTooSoon()
|
||||
{
|
||||
AddStep("seek to gameplay", () => Player.GameplayClockContainer.Seek(0));
|
||||
AddStep("move cursor outside", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.TopLeft - new Vector2(10)));
|
||||
|
||||
pauseAndConfirm();
|
||||
@ -213,9 +223,23 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
confirmNotExited();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestUserPauseDuringIntroSkipsCooldown()
|
||||
{
|
||||
AddStep("seek before gameplay", () => Player.GameplayClockContainer.Seek(-5000));
|
||||
AddStep("move cursor outside", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.TopLeft - new Vector2(10)));
|
||||
|
||||
pauseAndConfirm();
|
||||
|
||||
resume();
|
||||
pauseViaBackAction();
|
||||
confirmPaused();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestQuickExitDuringCooldownTooSoon()
|
||||
{
|
||||
AddStep("seek to gameplay", () => Player.GameplayClockContainer.Seek(0));
|
||||
AddStep("move cursor outside", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.TopLeft - new Vector2(10)));
|
||||
|
||||
pauseAndConfirm();
|
||||
|
16
osu.Game.Tests/Visual/Menus/TestSceneIntroChristmas.cs
Normal file
16
osu.Game.Tests/Visual/Menus/TestSceneIntroChristmas.cs
Normal file
@ -0,0 +1,16 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Screens.Menu;
|
||||
using osu.Game.Seasonal;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Menus
|
||||
{
|
||||
[TestFixture]
|
||||
public partial class TestSceneIntroChristmas : IntroTestScene
|
||||
{
|
||||
protected override bool IntroReliesOnTrack => true;
|
||||
protected override IntroScreen CreateScreen() => new IntroChristmas();
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
// 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.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Seasonal;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Menus
|
||||
{
|
||||
public partial class TestSceneMainMenuSeasonalLighting : OsuTestScene
|
||||
{
|
||||
[Resolved]
|
||||
private BeatmapManager beatmaps { get; set; } = null!;
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
AddStep("prepare beatmap", () =>
|
||||
{
|
||||
var setInfo = beatmaps.QueryBeatmapSet(b => b.Protected && b.Hash == IntroChristmas.CHRISTMAS_BEATMAP_SET_HASH);
|
||||
|
||||
if (setInfo != null)
|
||||
Beatmap.Value = beatmaps.GetWorkingBeatmap(setInfo.Value.Beatmaps.First());
|
||||
});
|
||||
|
||||
AddStep("create lighting", () => Child = new MainMenuSeasonalLighting());
|
||||
|
||||
AddStep("restart beatmap", () =>
|
||||
{
|
||||
Beatmap.Value.Track.Start();
|
||||
Beatmap.Value.Track.Seek(4000);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBasic()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -14,7 +14,6 @@ using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Online.Rooms.RoomStatuses;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.OnlinePlay.Lounge;
|
||||
@ -76,7 +75,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
createLoungeRoom(new Room
|
||||
{
|
||||
Name = "Multiplayer room",
|
||||
Status = new RoomStatusOpen(),
|
||||
EndDate = DateTimeOffset.Now.AddDays(1),
|
||||
Type = MatchType.HeadToHead,
|
||||
Playlist = [item1],
|
||||
@ -85,7 +83,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
createLoungeRoom(new Room
|
||||
{
|
||||
Name = "Private room",
|
||||
Status = new RoomStatusOpenPrivate(),
|
||||
Password = "*",
|
||||
EndDate = DateTimeOffset.Now.AddDays(1),
|
||||
Type = MatchType.HeadToHead,
|
||||
@ -95,36 +92,38 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
createLoungeRoom(new Room
|
||||
{
|
||||
Name = "Playlist room with multiple beatmaps",
|
||||
Status = new RoomStatusPlaying(),
|
||||
Status = RoomStatus.Playing,
|
||||
EndDate = DateTimeOffset.Now.AddDays(1),
|
||||
Playlist = [item1, item2],
|
||||
CurrentPlaylistItem = item1
|
||||
}),
|
||||
createLoungeRoom(new Room
|
||||
{
|
||||
Name = "Finished room",
|
||||
Status = new RoomStatusEnded(),
|
||||
Name = "Closing soon",
|
||||
EndDate = DateTimeOffset.Now.AddSeconds(5),
|
||||
}),
|
||||
createLoungeRoom(new Room
|
||||
{
|
||||
Name = "Closed room",
|
||||
EndDate = DateTimeOffset.Now,
|
||||
}),
|
||||
createLoungeRoom(new Room
|
||||
{
|
||||
Name = "Spotlight room",
|
||||
Status = new RoomStatusOpen(),
|
||||
Category = RoomCategory.Spotlight,
|
||||
}),
|
||||
createLoungeRoom(new Room
|
||||
{
|
||||
Name = "Featured artist room",
|
||||
Status = new RoomStatusOpen(),
|
||||
Category = RoomCategory.FeaturedArtist,
|
||||
}),
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
AddUntilStep("wait for panel load", () => rooms.Count == 6);
|
||||
AddUntilStep("wait for panel load", () => rooms.Count == 7);
|
||||
AddUntilStep("correct status text", () => rooms.ChildrenOfType<OsuSpriteText>().Count(s => s.Text.ToString().StartsWith("Currently playing", StringComparison.Ordinal)) == 2);
|
||||
AddUntilStep("correct status text", () => rooms.ChildrenOfType<OsuSpriteText>().Count(s => s.Text.ToString().StartsWith("Ready to play", StringComparison.Ordinal)) == 4);
|
||||
AddUntilStep("correct status text", () => rooms.ChildrenOfType<OsuSpriteText>().Count(s => s.Text.ToString().StartsWith("Ready to play", StringComparison.Ordinal)) == 5);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -136,7 +135,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddStep("create room", () => Child = drawableRoom = createLoungeRoom(room = new Room
|
||||
{
|
||||
Name = "Room with password",
|
||||
Status = new RoomStatusOpen(),
|
||||
Type = MatchType.HeadToHead,
|
||||
}));
|
||||
|
||||
|
@ -1,8 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Game.Configuration;
|
||||
@ -58,7 +56,11 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
|
||||
// First scroll makes volume controls appear, second adjusts volume.
|
||||
AddRepeatStep("Adjust volume using mouse wheel", () => InputManager.ScrollVerticalBy(5), 10);
|
||||
AddAssert("Volume is still zero", () => Game.Audio.Volume.Value == 0);
|
||||
AddAssert("Volume is still zero", () => Game.Audio.Volume.Value, () => Is.Zero);
|
||||
|
||||
AddStep("Pause", () => InputManager.PressKey(Key.Escape));
|
||||
AddRepeatStep("Adjust volume using mouse wheel", () => InputManager.ScrollVerticalBy(5), 10);
|
||||
AddAssert("Volume is above zero", () => Game.Audio.Volume.Value > 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -80,8 +82,8 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
|
||||
private void loadToPlayerNonBreakTime()
|
||||
{
|
||||
Player player = null;
|
||||
Screens.Select.SongSelect songSelect = null;
|
||||
Player? player = null;
|
||||
Screens.Select.SongSelect songSelect = null!;
|
||||
PushAndConfirm(() => songSelect = new TestSceneScreenNavigation.TestPlaySongSelect());
|
||||
AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded);
|
||||
|
||||
@ -95,7 +97,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
return (player = Game.ScreenStack.CurrentScreen as Player) != null;
|
||||
});
|
||||
|
||||
AddUntilStep("wait for play time active", () => !player.IsBreakTime.Value);
|
||||
AddUntilStep("wait for play time active", () => player!.IsBreakTime.Value, () => Is.False);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Screens.Edit.Components;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Screens.Play.HUD.HitErrorMeters;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Tests.Beatmaps.IO;
|
||||
@ -212,6 +213,33 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
AddAssert("no mod selected", () => !((Player)Game.ScreenStack.CurrentScreen).Mods.Value.Any());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestGameplaySettingsDoesNotExpandWhenSkinOverlayPresent()
|
||||
{
|
||||
advanceToSongSelect();
|
||||
openSkinEditor();
|
||||
AddStep("select autoplay", () => Game.SelectedMods.Value = new Mod[] { new OsuModAutoplay() });
|
||||
AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely());
|
||||
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
|
||||
switchToGameplayScene();
|
||||
|
||||
AddUntilStep("wait for settings", () => getPlayerSettingsOverlay() != null);
|
||||
AddAssert("settings not visible", () => getPlayerSettingsOverlay().DrawWidth, () => Is.EqualTo(0));
|
||||
|
||||
AddStep("move cursor to right of screen", () => InputManager.MoveMouseTo(InputManager.ScreenSpaceDrawQuad.TopRight));
|
||||
AddAssert("settings not visible", () => getPlayerSettingsOverlay().DrawWidth, () => Is.EqualTo(0));
|
||||
|
||||
toggleSkinEditor();
|
||||
|
||||
AddStep("move cursor slightly", () => InputManager.MoveMouseTo(InputManager.ScreenSpaceDrawQuad.TopRight + new Vector2(1)));
|
||||
AddUntilStep("settings visible", () => getPlayerSettingsOverlay().DrawWidth, () => Is.GreaterThan(0));
|
||||
|
||||
AddStep("move cursor to right of screen too far", () => InputManager.MoveMouseTo(InputManager.ScreenSpaceDrawQuad.TopRight + new Vector2(10240, 0)));
|
||||
AddUntilStep("settings not visible", () => getPlayerSettingsOverlay().DrawWidth, () => Is.EqualTo(0));
|
||||
|
||||
PlayerSettingsOverlay getPlayerSettingsOverlay() => ((Player)Game.ScreenStack.CurrentScreen).ChildrenOfType<PlayerSettingsOverlay>().SingleOrDefault();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCinemaModRemovedOnEnteringGameplay()
|
||||
{
|
||||
|
@ -1,149 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Online.Rooms.RoomStatuses;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.OnlinePlay.Playlists;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osu.Game.Tests.Visual.OnlinePlay;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Playlists
|
||||
{
|
||||
public partial class TestScenePlaylistsRoomSubScreen : OnlinePlayTestScene
|
||||
{
|
||||
private const double track_length = 10000;
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; } = null!;
|
||||
|
||||
protected new TestRoomManager RoomManager => (TestRoomManager)base.RoomManager;
|
||||
|
||||
private BeatmapManager beatmaps = null!;
|
||||
private RulesetStore rulesets = null!;
|
||||
private BeatmapSetInfo? importedSet;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
{
|
||||
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
|
||||
Dependencies.Cache(new ScoreManager(rulesets, () => beatmaps, LocalStorage, Realm, API));
|
||||
Dependencies.Cache(Realm);
|
||||
|
||||
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
|
||||
|
||||
Realm.Write(r =>
|
||||
{
|
||||
foreach (var set in r.All<BeatmapSetInfo>())
|
||||
{
|
||||
foreach (var b in set.Beatmaps)
|
||||
{
|
||||
// These will all have a virtual track length of 1000, see WorkingBeatmap.GetVirtualTrack().
|
||||
b.Length = track_length - 1000;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
importedSet = beatmaps.GetAllUsableBeatmapSets().First();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestStatusUpdateOnEnter()
|
||||
{
|
||||
Room room = null!;
|
||||
PlaylistsRoomSubScreen roomScreen = null!;
|
||||
|
||||
AddStep("create room", () =>
|
||||
{
|
||||
RoomManager.AddRoom(room = new Room
|
||||
{
|
||||
Name = @"Test Room",
|
||||
Host = new APIUser { Username = @"Host" },
|
||||
Category = RoomCategory.Normal,
|
||||
EndDate = DateTimeOffset.Now.AddMinutes(-1)
|
||||
});
|
||||
});
|
||||
|
||||
AddStep("push screen", () => LoadScreen(roomScreen = new PlaylistsRoomSubScreen(room)));
|
||||
AddUntilStep("wait for screen load", () => roomScreen.IsCurrentScreen());
|
||||
AddAssert("status is still ended", () => roomScreen.Room.Status, Is.TypeOf<RoomStatusEnded>);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCloseButtonGoesAwayAfterGracePeriod()
|
||||
{
|
||||
Room room = null!;
|
||||
PlaylistsRoomSubScreen roomScreen = null!;
|
||||
|
||||
AddStep("create room", () =>
|
||||
{
|
||||
RoomManager.AddRoom(room = new Room
|
||||
{
|
||||
Name = @"Test Room",
|
||||
Host = api.LocalUser.Value,
|
||||
Category = RoomCategory.Normal,
|
||||
StartDate = DateTimeOffset.Now.AddMinutes(-5).AddSeconds(3),
|
||||
EndDate = DateTimeOffset.Now.AddMinutes(30)
|
||||
});
|
||||
});
|
||||
|
||||
AddStep("push screen", () => LoadScreen(roomScreen = new PlaylistsRoomSubScreen(room)));
|
||||
AddUntilStep("wait for screen load", () => roomScreen.IsCurrentScreen());
|
||||
AddAssert("close button present", () => roomScreen.ChildrenOfType<DangerousRoundedButton>().Any());
|
||||
AddUntilStep("wait for close button to disappear", () => !roomScreen.ChildrenOfType<DangerousRoundedButton>().Any());
|
||||
}
|
||||
|
||||
[TestCase(120_000, true)] // Definitely enough time.
|
||||
[TestCase(45_000, true)] // Enough time.
|
||||
[TestCase(35_000, false)] // Not enough time to complete beatmap after lenience.
|
||||
[TestCase(20_000, false)] // Not enough time.
|
||||
[TestCase(5_000, false)] // Not enough time to complete beatmap before lenience.
|
||||
[TestCase(37_500, true, 2)] // Enough time to complete beatmap after mods are applied.
|
||||
public void TestReadyButtonEnablementPeriod(int offsetMs, bool enabled, double rate = 1)
|
||||
{
|
||||
Room room = null!;
|
||||
PlaylistsRoomSubScreen roomScreen = null!;
|
||||
|
||||
AddStep("create room", () =>
|
||||
{
|
||||
RoomManager.AddRoom(room = new Room
|
||||
{
|
||||
Name = @"Test Room",
|
||||
Host = api.LocalUser.Value,
|
||||
Category = RoomCategory.Normal,
|
||||
StartDate = DateTimeOffset.Now,
|
||||
EndDate = DateTimeOffset.Now.AddMilliseconds(offsetMs),
|
||||
Playlist =
|
||||
[
|
||||
new PlaylistItem(importedSet!.Beatmaps[0])
|
||||
{
|
||||
RequiredMods = rate == 1
|
||||
? []
|
||||
: [new APIMod(new OsuModDoubleTime { SpeedChange = { Value = rate } })]
|
||||
}
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
AddStep("push screen", () => LoadScreen(roomScreen = new PlaylistsRoomSubScreen(room)));
|
||||
AddUntilStep("wait for screen load", () => roomScreen.IsCurrentScreen());
|
||||
AddUntilStep("ready button enabled", () => roomScreen.ChildrenOfType<PlaylistsReadyButton>().SingleOrDefault()?.Enabled.Value, () => Is.EqualTo(enabled));
|
||||
}
|
||||
}
|
||||
}
|
@ -13,9 +13,12 @@ using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.BeatmapListing;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Catch;
|
||||
using osu.Game.Rulesets.Mania;
|
||||
@ -23,6 +26,8 @@ using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Taiko;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osu.Game.Users;
|
||||
using osu.Game.Utils;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
@ -63,7 +68,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
return 336; // recommended star rating of 2
|
||||
|
||||
case 1:
|
||||
return 928; // SR 3
|
||||
return 973; // SR 3
|
||||
|
||||
case 2:
|
||||
return 1905; // SR 4
|
||||
@ -170,6 +175,45 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
presentAndConfirm(() => maniaSet, 5);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBeatmapListingFilter()
|
||||
{
|
||||
AddStep("set playmode to taiko", () => ((DummyAPIAccess)API).LocalUser.Value.PlayMode = "taiko");
|
||||
|
||||
AddStep("open beatmap listing", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.ControlLeft);
|
||||
InputManager.PressKey(Key.B);
|
||||
InputManager.ReleaseKey(Key.B);
|
||||
InputManager.ReleaseKey(Key.ControlLeft);
|
||||
});
|
||||
|
||||
AddUntilStep("wait for load", () => Game.ChildrenOfType<BeatmapListingOverlay>().SingleOrDefault()?.IsLoaded, () => Is.True);
|
||||
|
||||
checkRecommendedDifficulty(3);
|
||||
|
||||
AddStep("change mode filter to osu!", () => Game.ChildrenOfType<BeatmapSearchRulesetFilterRow>().Single().ChildrenOfType<FilterTabItem<RulesetInfo>>().ElementAt(1).TriggerClick());
|
||||
|
||||
checkRecommendedDifficulty(2);
|
||||
|
||||
AddStep("change mode filter to osu!taiko", () => Game.ChildrenOfType<BeatmapSearchRulesetFilterRow>().Single().ChildrenOfType<FilterTabItem<RulesetInfo>>().ElementAt(2).TriggerClick());
|
||||
|
||||
checkRecommendedDifficulty(3);
|
||||
|
||||
AddStep("change mode filter to osu!catch", () => Game.ChildrenOfType<BeatmapSearchRulesetFilterRow>().Single().ChildrenOfType<FilterTabItem<RulesetInfo>>().ElementAt(3).TriggerClick());
|
||||
|
||||
checkRecommendedDifficulty(4);
|
||||
|
||||
AddStep("change mode filter to osu!mania", () => Game.ChildrenOfType<BeatmapSearchRulesetFilterRow>().Single().ChildrenOfType<FilterTabItem<RulesetInfo>>().ElementAt(4).TriggerClick());
|
||||
|
||||
checkRecommendedDifficulty(5);
|
||||
|
||||
void checkRecommendedDifficulty(double starRating)
|
||||
=> AddAssert($"recommended difficulty is {starRating}",
|
||||
() => Game.ChildrenOfType<BeatmapSearchGeneralFilterRow>().Single().ChildrenOfType<OsuSpriteText>().ElementAt(1).Text.ToString(),
|
||||
() => Is.EqualTo($"Recommended difficulty ({starRating.FormatStarRating()})"));
|
||||
}
|
||||
|
||||
private BeatmapSetInfo importBeatmapSet(IEnumerable<RulesetInfo> difficultyRulesets)
|
||||
{
|
||||
var rulesets = difficultyRulesets.ToArray();
|
||||
|
@ -15,6 +15,7 @@ using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Legacy;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
|
@ -4,20 +4,52 @@
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Screens.Menu;
|
||||
using osu.Game.Seasonal;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
public partial class TestSceneOsuLogo : OsuTestScene
|
||||
{
|
||||
private OsuLogo? logo;
|
||||
|
||||
private float scale = 1;
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
AddSliderStep("scale", 0.1, 2, 1, scale =>
|
||||
{
|
||||
if (logo != null)
|
||||
Child.Scale = new Vector2(this.scale = (float)scale);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBasic()
|
||||
{
|
||||
AddStep("Add logo", () =>
|
||||
{
|
||||
Child = new OsuLogo
|
||||
Child = logo = new OsuLogo
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Scale = new Vector2(scale),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestChristmas()
|
||||
{
|
||||
AddStep("Add logo", () =>
|
||||
{
|
||||
Child = logo = new OsuLogoChristmas
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Scale = new Vector2(scale),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Overlays.Volume;
|
||||
@ -59,13 +60,12 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
[Test]
|
||||
public void TestAltScrollNotBlocked()
|
||||
{
|
||||
bool scrollReceived = false;
|
||||
TestGlobalScrollAdjustsVolume volumeAdjust = null!;
|
||||
|
||||
AddStep("add volume control receptor", () => Add(new VolumeControlReceptor
|
||||
AddStep("add volume control receptor", () => Add(volumeAdjust = new TestGlobalScrollAdjustsVolume
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Depth = float.MaxValue,
|
||||
ScrollActionRequested = (_, _, _) => scrollReceived = true,
|
||||
}));
|
||||
|
||||
AddStep("hold alt", () => InputManager.PressKey(Key.AltLeft));
|
||||
@ -75,10 +75,21 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
InputManager.ScrollVerticalBy(10);
|
||||
});
|
||||
|
||||
AddAssert("receptor received scroll input", () => scrollReceived);
|
||||
AddAssert("receptor received scroll input", () => volumeAdjust.ScrollReceived);
|
||||
AddStep("release alt", () => InputManager.ReleaseKey(Key.AltLeft));
|
||||
}
|
||||
|
||||
public partial class TestGlobalScrollAdjustsVolume : GlobalScrollAdjustsVolume
|
||||
{
|
||||
public bool ScrollReceived { get; private set; }
|
||||
|
||||
protected override bool OnScroll(ScrollEvent e)
|
||||
{
|
||||
ScrollReceived = true;
|
||||
return base.OnScroll(e);
|
||||
}
|
||||
}
|
||||
|
||||
private partial class TestOverlay : OsuFocusedOverlayContainer
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
|
@ -1,8 +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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Volume;
|
||||
@ -11,7 +10,14 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
public partial class TestSceneVolumeOverlay : OsuTestScene
|
||||
{
|
||||
private VolumeOverlay volume;
|
||||
private VolumeOverlay volume = null!;
|
||||
|
||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||
{
|
||||
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
||||
dependencies.CacheAs(volume = new VolumeOverlay());
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
@ -19,12 +25,10 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
|
||||
AddRange(new Drawable[]
|
||||
{
|
||||
volume = new VolumeOverlay(),
|
||||
new VolumeControlReceptor
|
||||
volume,
|
||||
new GlobalScrollAdjustsVolume
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ActionRequested = action => volume.Adjust(action),
|
||||
ScrollActionRequested = (action, amount, isPrecise) => volume.Adjust(action, amount, isPrecise),
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -152,6 +152,12 @@ namespace osu.Game.Tournament.Tests.Components
|
||||
AddStep("change channel to 2", () => chatDisplay.Channel.Value = testChannel2);
|
||||
|
||||
AddStep("change channel to 1", () => chatDisplay.Channel.Value = testChannel);
|
||||
|
||||
AddStep("!mp message (shouldn't display)", () => testChannel.AddNewMessages(new Message(nextMessageId())
|
||||
{
|
||||
Sender = redUser.ToAPIUser(),
|
||||
Content = "!mp wangs"
|
||||
}));
|
||||
}
|
||||
|
||||
private int messageId;
|
||||
|
@ -15,6 +15,7 @@ using osu.Game.Graphics;
|
||||
using osu.Game.Models;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Screens.Menu;
|
||||
using osu.Game.Utils;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
@ -207,7 +208,7 @@ namespace osu.Game.Tournament.Components
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new DiffPiece(stats),
|
||||
new DiffPiece(("Star Rating", $"{beatmap.StarRating:0.00}{srExtra}"))
|
||||
new DiffPiece(("Star Rating", $"{beatmap.StarRating.FormatStarRating()}{srExtra}"))
|
||||
}
|
||||
},
|
||||
new FillFlowContainer
|
||||
|
@ -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 System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
@ -72,7 +73,13 @@ namespace osu.Game.Tournament.Components
|
||||
|
||||
public void Contract() => this.FadeOut(200);
|
||||
|
||||
protected override ChatLine CreateMessage(Message message) => new MatchMessage(message, ladderInfo);
|
||||
protected override ChatLine? CreateMessage(Message message)
|
||||
{
|
||||
if (message.Content.StartsWith("!mp", StringComparison.Ordinal))
|
||||
return null;
|
||||
|
||||
return new MatchMessage(message, ladderInfo);
|
||||
}
|
||||
|
||||
protected override StandAloneDrawableChannel CreateDrawableChannel(Channel channel) => new MatchChannel(channel);
|
||||
|
||||
|
@ -533,6 +533,16 @@ namespace osu.Game.Beatmaps
|
||||
}
|
||||
}
|
||||
|
||||
public void MarkPlayed(BeatmapInfo beatmapSetInfo) => Realm.Run(r =>
|
||||
{
|
||||
using var transaction = r.BeginWrite();
|
||||
|
||||
var beatmap = r.Find<BeatmapInfo>(beatmapSetInfo.ID)!;
|
||||
beatmap.LastPlayed = DateTimeOffset.Now;
|
||||
|
||||
transaction.Commit();
|
||||
});
|
||||
|
||||
#region Implementation of ICanAcceptFiles
|
||||
|
||||
public Task Import(params string[] paths) => beatmapImporter.Import(paths);
|
||||
|
@ -1,12 +1,9 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
@ -23,10 +20,12 @@ namespace osu.Game.Beatmaps
|
||||
/// </summary>
|
||||
public partial class DifficultyRecommender : Component
|
||||
{
|
||||
public event Action? StarRatingUpdated;
|
||||
|
||||
private readonly LocalUserStatisticsProvider statisticsProvider;
|
||||
|
||||
[Resolved]
|
||||
private Bindable<RulesetInfo> gameRuleset { get; set; }
|
||||
private Bindable<RulesetInfo> gameRuleset { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private RulesetStore rulesets { get; set; } = null!;
|
||||
@ -78,10 +77,18 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
private void updateMapping(RulesetInfo ruleset, UserStatistics statistics)
|
||||
{
|
||||
// algorithm taken from https://github.com/ppy/osu-web/blob/e6e2825516449e3d0f3f5e1852c6bdd3428c3437/app/Models/User.php#L1505
|
||||
recommendedDifficultyMapping[ruleset.ShortName] = Math.Pow((double)(statistics.PP ?? 0), 0.4) * 0.195;
|
||||
// algorithm taken from https://github.com/ppy/osu-web/blob/027026fccc91525e39cee5d2f369f1b343eb1bf1/app/Models/UserStatistics/Model.php#L93-L94
|
||||
recommendedDifficultyMapping[ruleset.ShortName] =
|
||||
ruleset.ShortName == @"taiko"
|
||||
? Math.Pow((double)(statistics.PP ?? 0), 0.35) * 0.27
|
||||
: Math.Pow((double)(statistics.PP ?? 0), 0.4) * 0.195;
|
||||
|
||||
StarRatingUpdated?.Invoke();
|
||||
}
|
||||
|
||||
public double? GetRecommendedStarRatingFor(RulesetInfo ruleset)
|
||||
=> recommendedDifficultyMapping.TryGetValue(ruleset.ShortName, out double starRating) ? starRating : null;
|
||||
|
||||
/// <summary>
|
||||
/// Find the recommended difficulty from a selection of available difficulties for the current local user.
|
||||
/// </summary>
|
||||
@ -90,15 +97,14 @@ namespace osu.Game.Beatmaps
|
||||
/// </remarks>
|
||||
/// <param name="beatmaps">A collection of beatmaps to select a difficulty from.</param>
|
||||
/// <returns>The recommended difficulty, or null if a recommendation could not be provided.</returns>
|
||||
[CanBeNull]
|
||||
public BeatmapInfo GetRecommendedBeatmap(IEnumerable<BeatmapInfo> beatmaps)
|
||||
public BeatmapInfo? GetRecommendedBeatmap(IEnumerable<BeatmapInfo> beatmaps)
|
||||
{
|
||||
foreach (string r in orderedRulesets)
|
||||
{
|
||||
if (!recommendedDifficultyMapping.TryGetValue(r, out double recommendation))
|
||||
continue;
|
||||
|
||||
BeatmapInfo beatmapInfo = beatmaps.Where(b => b.Ruleset.ShortName.Equals(r, StringComparison.Ordinal)).MinBy(b =>
|
||||
BeatmapInfo? beatmapInfo = beatmaps.Where(b => b.Ruleset.ShortName.Equals(r, StringComparison.Ordinal)).MinBy(b =>
|
||||
{
|
||||
double difference = b.StarRating - recommendation;
|
||||
return difference >= 0 ? difference * 2 : difference * -1; // prefer easier over harder
|
||||
|
@ -5,7 +5,6 @@ using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Extensions.LocalisationExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
@ -14,6 +13,7 @@ using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Utils;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
@ -156,7 +156,7 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
|
||||
displayedStars.BindValueChanged(s =>
|
||||
{
|
||||
starsText.Text = s.NewValue < 0 ? "-" : s.NewValue.ToLocalisableString("0.00");
|
||||
starsText.Text = s.NewValue < 0 ? "-" : s.NewValue.FormatStarRating();
|
||||
|
||||
background.Colour = colours.ForStarDifficulty(s.NewValue);
|
||||
|
||||
|
@ -13,6 +13,8 @@ namespace osu.Game.Beatmaps.Legacy
|
||||
NewCombo = 1 << 2,
|
||||
Spinner = 1 << 3,
|
||||
ComboOffset = (1 << 4) | (1 << 5) | (1 << 6),
|
||||
Hold = 1 << 7
|
||||
Hold = 1 << 7,
|
||||
|
||||
ObjectTypes = Circle | Slider | Spinner | Hold
|
||||
}
|
||||
}
|
||||
|
@ -57,6 +57,7 @@ namespace osu.Game.Configuration
|
||||
SetDefault(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2f, 1f, 0.01f);
|
||||
|
||||
SetDefault(OsuSetting.BeatmapListingCardSize, BeatmapCardSize.Normal);
|
||||
SetDefault(OsuSetting.BeatmapListingFeaturedArtistFilter, true);
|
||||
|
||||
SetDefault(OsuSetting.ProfileCoverExpanded, true);
|
||||
|
||||
@ -202,6 +203,7 @@ namespace osu.Game.Configuration
|
||||
SetDefault(OsuSetting.HideCountryFlags, false);
|
||||
|
||||
SetDefault(OsuSetting.MultiplayerRoomFilter, RoomPermissionsFilter.All);
|
||||
SetDefault(OsuSetting.MultiplayerShowInProgressFilter, true);
|
||||
|
||||
SetDefault(OsuSetting.LastProcessedMetadataId, -1);
|
||||
|
||||
@ -447,6 +449,8 @@ namespace osu.Game.Configuration
|
||||
EditorRotationOrigin,
|
||||
EditorTimelineShowBreaks,
|
||||
EditorAdjustExistingObjectsOnTimingChanges,
|
||||
AlwaysRequireHoldingForPause
|
||||
AlwaysRequireHoldingForPause,
|
||||
MultiplayerShowInProgressFilter,
|
||||
BeatmapListingFeaturedArtistFilter,
|
||||
}
|
||||
}
|
||||
|
@ -95,8 +95,9 @@ namespace osu.Game.Database
|
||||
/// 42 2024-08-07 Update mania key bindings to reflect changes to ManiaAction
|
||||
/// 43 2024-10-14 Reset keybind for toggling FPS display to avoid conflict with "convert to stream" in the editor, if not already changed by user.
|
||||
/// 44 2024-11-22 Removed several properties from BeatmapInfo which did not need to be persisted to realm.
|
||||
/// 45 2024-12-23 Change beat snap divisor adjust defaults to be Ctrl+Scroll instead of Ctrl+Shift+Scroll, if not already changed by user.
|
||||
/// </summary>
|
||||
private const int schema_version = 44;
|
||||
private const int schema_version = 45;
|
||||
|
||||
/// <summary>
|
||||
/// Lock object which is held during <see cref="BlockAllOperations"/> sections, blocking realm retrieval during blocking periods.
|
||||
@ -1205,6 +1206,22 @@ namespace osu.Game.Database
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 45:
|
||||
{
|
||||
// Cycling beat snap divisors no longer requires holding shift (just control).
|
||||
var keyBindings = migration.NewRealm.All<RealmKeyBinding>();
|
||||
|
||||
var nextBeatSnapBinding = keyBindings.FirstOrDefault(k => k.ActionInt == (int)GlobalAction.EditorCycleNextBeatSnapDivisor);
|
||||
if (nextBeatSnapBinding != null && nextBeatSnapBinding.KeyCombination.Keys.SequenceEqual(new[] { InputKey.Shift, InputKey.Control, InputKey.MouseWheelLeft }))
|
||||
migration.NewRealm.Remove(nextBeatSnapBinding);
|
||||
|
||||
var previousBeatSnapBinding = keyBindings.FirstOrDefault(k => k.ActionInt == (int)GlobalAction.EditorCyclePreviousBeatSnapDivisor);
|
||||
if (previousBeatSnapBinding != null && previousBeatSnapBinding.KeyCombination.Keys.SequenceEqual(new[] { InputKey.Shift, InputKey.Control, InputKey.MouseWheelRight }))
|
||||
migration.NewRealm.Remove(previousBeatSnapBinding);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Logger.Log($"Migration completed in {stopwatch.ElapsedMilliseconds}ms");
|
||||
|
@ -28,7 +28,6 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; }
|
||||
|
||||
private readonly IBindable<APIState> apiState = new Bindable<APIState>();
|
||||
private Bindable<SeasonalBackgroundMode> seasonalBackgroundMode;
|
||||
private Bindable<APISeasonalBackgrounds> seasonalBackgrounds;
|
||||
|
||||
@ -47,13 +46,12 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
SeasonalBackgroundChanged?.Invoke();
|
||||
});
|
||||
|
||||
apiState.BindTo(api.State);
|
||||
apiState.BindValueChanged(fetchSeasonalBackgrounds, true);
|
||||
fetchSeasonalBackgrounds();
|
||||
}
|
||||
|
||||
private void fetchSeasonalBackgrounds(ValueChangedEvent<APIState> stateChanged)
|
||||
private void fetchSeasonalBackgrounds()
|
||||
{
|
||||
if (seasonalBackgrounds.Value != null || stateChanged.NewValue != APIState.Online)
|
||||
if (seasonalBackgrounds.Value != null)
|
||||
return;
|
||||
|
||||
var request = new GetSeasonalBackgroundsRequest();
|
||||
|
@ -195,6 +195,27 @@ namespace osu.Game.Graphics
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the accent colour representing a <see cref="Room"/>'s current status.
|
||||
/// </summary>
|
||||
public Color4 ForRoomStatus(Room room)
|
||||
{
|
||||
if (room.HasEnded)
|
||||
return YellowDarker;
|
||||
|
||||
switch (room.Status)
|
||||
{
|
||||
case RoomStatus.Playing:
|
||||
return Purple;
|
||||
|
||||
default:
|
||||
if (room.HasPassword)
|
||||
return GreenDark;
|
||||
|
||||
return GreenLight;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves colour for a <see cref="RankingTier"/>.
|
||||
/// See https://www.figma.com/file/YHWhp9wZ089YXgB7pe6L1k/Tier-Colours
|
||||
|
@ -62,8 +62,12 @@ namespace osu.Game.IO.Serialization.Converters
|
||||
if (tok["$type"] == null)
|
||||
throw new JsonException("Expected $type token.");
|
||||
|
||||
string typeName = lookupTable[(int)tok["$type"]];
|
||||
var instance = (T)Activator.CreateInstance(Type.GetType(typeName).AsNonNull())!;
|
||||
// Prevent instantiation of types that do not inherit the type targetted by this converter
|
||||
Type type = Type.GetType(lookupTable[(int)tok["$type"]]).AsNonNull();
|
||||
if (!type.IsAssignableTo(typeof(T)))
|
||||
continue;
|
||||
|
||||
var instance = (T)Activator.CreateInstance(type)!;
|
||||
serializer.Populate(itemReader, instance);
|
||||
|
||||
list.Add(instance);
|
||||
|
@ -142,10 +142,8 @@ namespace osu.Game.Input.Bindings
|
||||
new KeyBinding(new[] { InputKey.Control, InputKey.J }, GlobalAction.EditorFlipVertically),
|
||||
new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.MouseWheelDown }, GlobalAction.EditorDecreaseDistanceSpacing),
|
||||
new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.MouseWheelUp }, GlobalAction.EditorIncreaseDistanceSpacing),
|
||||
// Framework automatically converts wheel up/down to left/right when shift is held.
|
||||
// See https://github.com/ppy/osu-framework/blob/master/osu.Framework/Input/StateChanges/MouseScrollRelativeInput.cs#L37-L38.
|
||||
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.MouseWheelRight }, GlobalAction.EditorCyclePreviousBeatSnapDivisor),
|
||||
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.MouseWheelLeft }, GlobalAction.EditorCycleNextBeatSnapDivisor),
|
||||
new KeyBinding(new[] { InputKey.Control, InputKey.MouseWheelUp }, GlobalAction.EditorCyclePreviousBeatSnapDivisor),
|
||||
new KeyBinding(new[] { InputKey.Control, InputKey.MouseWheelDown }, GlobalAction.EditorCycleNextBeatSnapDivisor),
|
||||
new KeyBinding(new[] { InputKey.Control, InputKey.R }, GlobalAction.EditorToggleRotateControl),
|
||||
new KeyBinding(new[] { InputKey.Control, InputKey.E }, GlobalAction.EditorToggleScaleControl),
|
||||
new KeyBinding(new[] { InputKey.Control, InputKey.Left }, GlobalAction.EditorSeekToPreviousHitObject),
|
||||
|
@ -28,6 +28,11 @@ This includes content that may not be correctly licensed for osu! usage. Browse
|
||||
/// </summary>
|
||||
public static LocalisableString UserContentConfirmButtonText => new TranslatableString(getKey(@"understood"), @"I understand");
|
||||
|
||||
/// <summary>
|
||||
/// "Featured Artists are music artists who have collaborated with osu! to make a selection of their tracks available for use in beatmaps. For some osu! releases, we showcase only featured artist beatmaps to better support the surrounding ecosystem."
|
||||
/// </summary>
|
||||
public static LocalisableString FeaturedArtistsTooltip => new TranslatableString(getKey(@"featured_artists_disabled_tooltip"), @"Featured Artists are music artists who have collaborated with osu! to make a selection of their tracks available for use in beatmaps. For some osu! releases, we showcase only featured artist beatmaps to better support the surrounding ecosystem.");
|
||||
|
||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||
}
|
||||
}
|
||||
|
@ -119,6 +119,11 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString AutoplayBeatmapShortcut => new TranslatableString(getKey(@"autoplay_beatmap_shortcut"), @"Ctrl-Enter at song select will start a beatmap in autoplay mode!");
|
||||
|
||||
/// <summary>
|
||||
/// ""Lazer" is not an English word. The correct spelling for the bright light is "laser"."
|
||||
/// </summary>
|
||||
public static LocalisableString LazerIsNotAWord => new TranslatableString(getKey(@"lazer_is_not_a_word"), @"""Lazer"" is not an English word. The correct spelling for the bright light is ""laser"".");
|
||||
|
||||
/// <summary>
|
||||
/// "Multithreading support means that even with low "FPS" your input and judgements will be accurate!"
|
||||
/// </summary>
|
||||
|
34
osu.Game/Localisation/RoomStatusPillStrings.cs
Normal file
34
osu.Game/Localisation/RoomStatusPillStrings.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.Localisation;
|
||||
|
||||
namespace osu.Game.Localisation
|
||||
{
|
||||
public static class RoomStatusPillStrings
|
||||
{
|
||||
private const string prefix = @"osu.Game.Resources.Localisation.RoomStatusPill";
|
||||
|
||||
/// <summary>
|
||||
/// "Ended"
|
||||
/// </summary>
|
||||
public static LocalisableString Ended => new TranslatableString(getKey(@"ended"), @"Ended");
|
||||
|
||||
/// <summary>
|
||||
/// "Playing"
|
||||
/// </summary>
|
||||
public static LocalisableString Playing => new TranslatableString(getKey(@"playing"), @"Playing");
|
||||
|
||||
/// <summary>
|
||||
/// "Open (Private)"
|
||||
/// </summary>
|
||||
public static LocalisableString OpenPrivate => new TranslatableString(getKey(@"open_private"), @"Open (Private)");
|
||||
|
||||
/// <summary>
|
||||
/// "Open"
|
||||
/// </summary>
|
||||
public static LocalisableString Open => new TranslatableString(getKey(@"open"), @"Open");
|
||||
|
||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Localisation;
|
||||
|
||||
namespace osu.Game.Localisation.SkinComponents
|
||||
{
|
||||
public static class SkinnableModDisplayStrings
|
||||
{
|
||||
private const string prefix = @"osu.Game.Resources.Localisation.SkinnableModDisplay";
|
||||
|
||||
/// <summary>
|
||||
/// "Show extended information"
|
||||
/// </summary>
|
||||
public static LocalisableString ShowExtendedInformation => new TranslatableString(getKey(@"show_extended_information"), @"Show extended information");
|
||||
|
||||
/// <summary>
|
||||
/// "Whether to show extended information for each mod."
|
||||
/// </summary>
|
||||
public static LocalisableString ShowExtendedInformationDescription => new TranslatableString(getKey(@"whether_to_show_extended_information"), @"Whether to show extended information for each mod.");
|
||||
|
||||
/// <summary>
|
||||
/// "Expansion mode"
|
||||
/// </summary>
|
||||
public static LocalisableString ExpansionMode => new TranslatableString(getKey(@"expansion_mode"), @"Expansion mode");
|
||||
|
||||
/// <summary>
|
||||
/// "How the mod display expands when interacted with."
|
||||
/// </summary>
|
||||
public static LocalisableString ExpansionModeDescription => new TranslatableString(getKey(@"how_the_mod_display_expands"), @"How the mod display expands when interacted with.");
|
||||
|
||||
/// <summary>
|
||||
/// "Expand on hover"
|
||||
/// </summary>
|
||||
public static LocalisableString ExpandOnHover => new TranslatableString(getKey(@"expand_on_hover"), @"Expand on hover");
|
||||
|
||||
/// <summary>
|
||||
/// "Always contracted"
|
||||
/// </summary>
|
||||
public static LocalisableString AlwaysContracted => new TranslatableString(getKey(@"always_contracted"), @"Always contracted");
|
||||
|
||||
/// <summary>
|
||||
/// "Always expanded"
|
||||
/// </summary>
|
||||
public static LocalisableString AlwaysExpanded => new TranslatableString(getKey(@"always_expanded"), @"Always expanded");
|
||||
|
||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||
}
|
||||
}
|
@ -54,16 +54,6 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString BeatmapHitsounds => new TranslatableString(getKey(@"beatmap_hitsounds"), @"Beatmap hitsounds");
|
||||
|
||||
/// <summary>
|
||||
/// "Export selected skin"
|
||||
/// </summary>
|
||||
public static LocalisableString ExportSkinButton => new TranslatableString(getKey(@"export_skin_button"), @"Export selected skin");
|
||||
|
||||
/// <summary>
|
||||
/// "Delete selected skin"
|
||||
/// </summary>
|
||||
public static LocalisableString DeleteSkinButton => new TranslatableString(getKey(@"delete_skin_button"), @"Delete selected skin");
|
||||
|
||||
private static string getKey(string key) => $"{prefix}:{key}";
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,8 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
@ -21,18 +20,18 @@ using osuTK.Input;
|
||||
namespace osu.Game.Online.Chat
|
||||
{
|
||||
/// <summary>
|
||||
/// Display a chat channel in an insolated region.
|
||||
/// Display a chat channel in an isolated region.
|
||||
/// </summary>
|
||||
public partial class StandAloneChatDisplay : CompositeDrawable
|
||||
{
|
||||
[Cached]
|
||||
public readonly Bindable<Channel> Channel = new Bindable<Channel>();
|
||||
public readonly Bindable<Channel?> Channel = new Bindable<Channel?>();
|
||||
|
||||
protected readonly ChatTextBox TextBox;
|
||||
protected readonly ChatTextBox? TextBox;
|
||||
|
||||
private ChannelManager channelManager;
|
||||
private ChannelManager? channelManager;
|
||||
|
||||
private StandAloneDrawableChannel drawableChannel;
|
||||
private StandAloneDrawableChannel? drawableChannel;
|
||||
|
||||
private readonly bool postingTextBox;
|
||||
|
||||
@ -93,6 +92,8 @@ namespace osu.Game.Online.Chat
|
||||
|
||||
private void postMessage(TextBox sender, bool newText)
|
||||
{
|
||||
Debug.Assert(TextBox != null);
|
||||
|
||||
string text = TextBox.Text.Trim();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
@ -106,9 +107,9 @@ namespace osu.Game.Online.Chat
|
||||
TextBox.Text = string.Empty;
|
||||
}
|
||||
|
||||
protected virtual ChatLine CreateMessage(Message message) => new StandAloneMessage(message);
|
||||
protected virtual ChatLine? CreateMessage(Message message) => new StandAloneMessage(message);
|
||||
|
||||
private void channelChanged(ValueChangedEvent<Channel> e)
|
||||
private void channelChanged(ValueChangedEvent<Channel?> e)
|
||||
{
|
||||
drawableChannel?.Expire();
|
||||
|
||||
@ -128,8 +129,8 @@ namespace osu.Game.Online.Chat
|
||||
|
||||
public partial class ChatTextBox : HistoryTextBox
|
||||
{
|
||||
public Action Focus;
|
||||
public Action FocusLost;
|
||||
public Action? Focus;
|
||||
public Action? FocusLost;
|
||||
|
||||
protected override bool OnKeyDown(KeyDownEvent e)
|
||||
{
|
||||
@ -171,14 +172,14 @@ namespace osu.Game.Online.Chat
|
||||
|
||||
public partial class StandAloneDrawableChannel : DrawableChannel
|
||||
{
|
||||
public Func<Message, ChatLine> CreateChatLineAction;
|
||||
public Func<Message, ChatLine?>? CreateChatLineAction;
|
||||
|
||||
public StandAloneDrawableChannel(Channel channel)
|
||||
: base(channel)
|
||||
{
|
||||
}
|
||||
|
||||
protected override ChatLine CreateChatLine(Message m) => CreateChatLineAction(m);
|
||||
protected override ChatLine? CreateChatLine(Message m) => CreateChatLineAction?.Invoke(m) ?? null;
|
||||
|
||||
protected override DaySeparator CreateDaySeparator(DateTimeOffset time) => new StandAloneDaySeparator(time);
|
||||
}
|
||||
|
@ -18,7 +18,6 @@ using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Multiplayer.Countdown;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Online.Rooms.RoomStatuses;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@ -395,15 +394,17 @@ namespace osu.Game.Online.Multiplayer
|
||||
switch (state)
|
||||
{
|
||||
case MultiplayerRoomState.Open:
|
||||
APIRoom.Status = APIRoom.HasPassword ? new RoomStatusOpenPrivate() : new RoomStatusOpen();
|
||||
APIRoom.Status = RoomStatus.Idle;
|
||||
break;
|
||||
|
||||
case MultiplayerRoomState.WaitingForLoad:
|
||||
case MultiplayerRoomState.Playing:
|
||||
APIRoom.Status = new RoomStatusPlaying();
|
||||
APIRoom.Status = RoomStatus.Playing;
|
||||
break;
|
||||
|
||||
case MultiplayerRoomState.Closed:
|
||||
APIRoom.Status = new RoomStatusEnded();
|
||||
APIRoom.EndDate = DateTimeOffset.Now;
|
||||
APIRoom.Status = RoomStatus.Idle;
|
||||
break;
|
||||
}
|
||||
|
||||
@ -821,7 +822,6 @@ namespace osu.Game.Online.Multiplayer
|
||||
Room.Settings = settings;
|
||||
APIRoom.Name = Room.Settings.Name;
|
||||
APIRoom.Password = Room.Settings.Password;
|
||||
APIRoom.Status = string.IsNullOrEmpty(Room.Settings.Password) ? new RoomStatusOpen() : new RoomStatusOpenPrivate();
|
||||
APIRoom.Type = Room.Settings.MatchType;
|
||||
APIRoom.QueueMode = Room.Settings.QueueMode;
|
||||
APIRoom.AutoStartDuration = Room.Settings.AutoStartDuration;
|
||||
|
@ -11,28 +11,33 @@ namespace osu.Game.Online.Rooms
|
||||
{
|
||||
public class GetRoomsRequest : APIRequest<List<Room>>
|
||||
{
|
||||
private readonly RoomStatusFilter status;
|
||||
private readonly RoomModeFilter mode;
|
||||
private readonly RoomStatusFilter? status;
|
||||
private readonly string category;
|
||||
|
||||
public GetRoomsRequest(RoomStatusFilter status, string category)
|
||||
public GetRoomsRequest(FilterCriteria filterCriteria)
|
||||
{
|
||||
this.status = status;
|
||||
this.category = category;
|
||||
mode = filterCriteria.Mode;
|
||||
category = filterCriteria.Category;
|
||||
status = filterCriteria.Status;
|
||||
}
|
||||
|
||||
protected override WebRequest CreateWebRequest()
|
||||
{
|
||||
var req = base.CreateWebRequest();
|
||||
|
||||
if (status != RoomStatusFilter.Open)
|
||||
req.AddParameter("mode", status.ToString().ToSnakeCase().ToLowerInvariant());
|
||||
if (mode != RoomModeFilter.Open)
|
||||
req.AddParameter(@"mode", mode.ToString().ToSnakeCase().ToLowerInvariant());
|
||||
|
||||
if (status != null)
|
||||
req.AddParameter(@"status", status.Value.ToString().ToSnakeCase().ToLowerInvariant());
|
||||
|
||||
if (!string.IsNullOrEmpty(category))
|
||||
req.AddParameter("category", category);
|
||||
req.AddParameter(@"category", category);
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
protected override string Target => "rooms";
|
||||
protected override string Target => @"rooms";
|
||||
}
|
||||
}
|
||||
|
@ -6,12 +6,10 @@ using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Game.IO.Serialization.Converters;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Rooms.RoomStatuses;
|
||||
|
||||
namespace osu.Game.Online.Rooms
|
||||
{
|
||||
@ -248,7 +246,7 @@ namespace osu.Game.Online.Rooms
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The current room status.
|
||||
/// The current status of the room.
|
||||
/// </summary>
|
||||
public RoomStatus Status
|
||||
{
|
||||
@ -265,18 +263,6 @@ namespace osu.Game.Online.Rooms
|
||||
set => SetField(ref availability, value);
|
||||
}
|
||||
|
||||
[OnDeserialized]
|
||||
private void onDeserialised(StreamingContext context)
|
||||
{
|
||||
// API doesn't populate status so let's do it here.
|
||||
if (EndDate != null && DateTimeOffset.Now >= EndDate)
|
||||
Status = new RoomStatusEnded();
|
||||
else if (HasPassword)
|
||||
Status = new RoomStatusOpenPrivate();
|
||||
else
|
||||
Status = new RoomStatusOpen();
|
||||
}
|
||||
|
||||
[JsonProperty("id")]
|
||||
private long? roomId;
|
||||
|
||||
@ -349,8 +335,9 @@ namespace osu.Game.Online.Rooms
|
||||
[JsonProperty("channel_id")]
|
||||
private int channelId;
|
||||
|
||||
// Not serialised (see: GetRoomsRequest).
|
||||
private RoomStatus status = new RoomStatusOpen();
|
||||
[JsonProperty("status")]
|
||||
[JsonConverter(typeof(SnakeCaseStringEnumConverter))]
|
||||
private RoomStatus status;
|
||||
|
||||
// Not yet serialised (not implemented).
|
||||
private RoomAvailability availability;
|
||||
@ -388,6 +375,15 @@ namespace osu.Game.Online.Rooms
|
||||
RecentParticipants = other.RecentParticipants;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the room is no longer available.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This property does not update in real-time and needs to be queried periodically.
|
||||
/// Subscribe to <see cref="EndDate"/> to be notified of any immediate changes.
|
||||
/// </remarks>
|
||||
public bool HasEnded => DateTimeOffset.Now >= EndDate;
|
||||
|
||||
[JsonObject(MemberSerialization.OptIn)]
|
||||
public class RoomPlaylistItemStats
|
||||
{
|
||||
|
@ -1,19 +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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Game.Graphics;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Online.Rooms
|
||||
{
|
||||
public abstract class RoomStatus
|
||||
public enum RoomStatus
|
||||
{
|
||||
public abstract string Message { get; }
|
||||
public abstract Color4 GetAppropriateColour(OsuColour colours);
|
||||
|
||||
public override int GetHashCode() => GetType().GetHashCode();
|
||||
public override bool Equals(object obj) => GetType() == obj?.GetType();
|
||||
Idle,
|
||||
Playing,
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Graphics;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Online.Rooms.RoomStatuses
|
||||
{
|
||||
public class RoomStatusEnded : RoomStatus
|
||||
{
|
||||
public override string Message => "Ended";
|
||||
public override Color4 GetAppropriateColour(OsuColour colours) => colours.YellowDarker;
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Graphics;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Online.Rooms.RoomStatuses
|
||||
{
|
||||
public class RoomStatusOpen : RoomStatus
|
||||
{
|
||||
public override string Message => "Open";
|
||||
public override Color4 GetAppropriateColour(OsuColour colours) => colours.GreenLight;
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Graphics;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Online.Rooms.RoomStatuses
|
||||
{
|
||||
public class RoomStatusOpenPrivate : RoomStatus
|
||||
{
|
||||
public override string Message => "Open (Private)";
|
||||
public override Color4 GetAppropriateColour(OsuColour colours) => colours.GreenDark;
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Graphics;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Online.Rooms.RoomStatuses
|
||||
{
|
||||
public class RoomStatusPlaying : RoomStatus
|
||||
{
|
||||
public override string Message => "Playing";
|
||||
public override Color4 GetAppropriateColour(OsuColour colours) => colours.Purple;
|
||||
}
|
||||
}
|
@ -57,7 +57,6 @@ using osu.Game.Overlays.Notifications;
|
||||
using osu.Game.Overlays.OSD;
|
||||
using osu.Game.Overlays.SkinEditor;
|
||||
using osu.Game.Overlays.Toolbar;
|
||||
using osu.Game.Overlays.Volume;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens;
|
||||
@ -69,6 +68,7 @@ using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Ranking;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Seasonal;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Updater;
|
||||
using osu.Game.Users;
|
||||
@ -221,6 +221,11 @@ namespace osu.Game
|
||||
|
||||
private readonly List<OverlayContainer> visibleBlockingOverlays = new List<OverlayContainer>();
|
||||
|
||||
/// <summary>
|
||||
/// Whether the game should be limited to only display officially licensed content.
|
||||
/// </summary>
|
||||
public virtual bool HideUnlicensedContent => false;
|
||||
|
||||
public OsuGame(string[] args = null)
|
||||
{
|
||||
this.args = args;
|
||||
@ -320,6 +325,7 @@ namespace osu.Game
|
||||
|
||||
if (host.Window != null)
|
||||
{
|
||||
host.Window.CursorState |= CursorState.Hidden;
|
||||
host.Window.DragDrop += path =>
|
||||
{
|
||||
// on macOS/iOS, URL associations are handled via SDL_DROPFILE events.
|
||||
@ -362,7 +368,10 @@ namespace osu.Game
|
||||
{
|
||||
SentryLogger.AttachUser(API.LocalUser);
|
||||
|
||||
dependencies.Cache(osuLogo = new OsuLogo { Alpha = 0 });
|
||||
if (SeasonalUIConfig.ENABLED)
|
||||
dependencies.CacheAs(osuLogo = new OsuLogoChristmas { Alpha = 0 });
|
||||
else
|
||||
dependencies.CacheAs(osuLogo = new OsuLogo { Alpha = 0 });
|
||||
|
||||
// bind config int to database RulesetInfo
|
||||
configRuleset = LocalConfig.GetBindable<string>(OsuSetting.Ruleset);
|
||||
@ -980,12 +989,6 @@ namespace osu.Game
|
||||
|
||||
AddRange(new Drawable[]
|
||||
{
|
||||
new VolumeControlReceptor
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ActionRequested = action => volume.Adjust(action),
|
||||
ScrollActionRequested = (action, amount, isPrecise) => volume.Adjust(action, amount, isPrecise),
|
||||
},
|
||||
ScreenOffsetContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
@ -1432,6 +1435,19 @@ namespace osu.Game
|
||||
|
||||
switch (e.Action)
|
||||
{
|
||||
case GlobalAction.DecreaseVolume:
|
||||
case GlobalAction.IncreaseVolume:
|
||||
return volume.Adjust(e.Action);
|
||||
|
||||
case GlobalAction.ToggleMute:
|
||||
case GlobalAction.NextVolumeMeter:
|
||||
case GlobalAction.PreviousVolumeMeter:
|
||||
|
||||
if (e.Repeat)
|
||||
return true;
|
||||
|
||||
return volume.Adjust(e.Action);
|
||||
|
||||
case GlobalAction.ToggleFPSDisplay:
|
||||
fpsCounter.ToggleVisibility();
|
||||
return true;
|
||||
|
@ -1,8 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
@ -29,7 +27,7 @@ namespace osu.Game.Overlays.BeatmapListing
|
||||
/// <summary>
|
||||
/// Any time the text box receives key events (even while masked).
|
||||
/// </summary>
|
||||
public Action TypingStarted;
|
||||
public Action? TypingStarted;
|
||||
|
||||
public Bindable<string> Query => textBox.Current;
|
||||
|
||||
@ -51,7 +49,7 @@ namespace osu.Game.Overlays.BeatmapListing
|
||||
|
||||
public Bindable<SearchExplicit> ExplicitContent => explicitContentFilter.Current;
|
||||
|
||||
public APIBeatmapSet BeatmapSet
|
||||
public APIBeatmapSet? BeatmapSet
|
||||
{
|
||||
set
|
||||
{
|
||||
@ -67,7 +65,7 @@ namespace osu.Game.Overlays.BeatmapListing
|
||||
}
|
||||
|
||||
private readonly BeatmapSearchTextBox textBox;
|
||||
private readonly BeatmapSearchMultipleSelectionFilterRow<SearchGeneral> generalFilter;
|
||||
private readonly BeatmapSearchGeneralFilterRow generalFilter;
|
||||
private readonly BeatmapSearchRulesetFilterRow modeFilter;
|
||||
private readonly BeatmapSearchFilterRow<SearchCategory> categoryFilter;
|
||||
private readonly BeatmapSearchFilterRow<SearchGenre> genreFilter;
|
||||
@ -151,7 +149,7 @@ namespace osu.Game.Overlays.BeatmapListing
|
||||
categoryFilter.Current.Value = SearchCategory.Leaderboard;
|
||||
}
|
||||
|
||||
private IBindable<bool> allowExplicitContent;
|
||||
private IBindable<bool> allowExplicitContent = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider, OsuConfigManager config)
|
||||
@ -165,6 +163,13 @@ namespace osu.Game.Overlays.BeatmapListing
|
||||
}, true);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
generalFilter.Ruleset.BindTo(Ruleset);
|
||||
}
|
||||
|
||||
public void TakeFocus() => textBox.TakeFocus();
|
||||
|
||||
private partial class BeatmapSearchTextBox : BasicSearchTextBox
|
||||
@ -172,7 +177,7 @@ namespace osu.Game.Overlays.BeatmapListing
|
||||
/// <summary>
|
||||
/// Any time the text box receives key events (even while masked).
|
||||
/// </summary>
|
||||
public Action TextChanged;
|
||||
public Action? TextChanged;
|
||||
|
||||
protected override Color4 SelectionColour => Color4.Gray;
|
||||
|
||||
|
@ -1,18 +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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Overlays.Dialog;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Utils;
|
||||
using osuTK.Graphics;
|
||||
using CommonStrings = osu.Game.Resources.Localisation.Web.CommonStrings;
|
||||
|
||||
@ -20,27 +26,97 @@ namespace osu.Game.Overlays.BeatmapListing
|
||||
{
|
||||
public partial class BeatmapSearchGeneralFilterRow : BeatmapSearchMultipleSelectionFilterRow<SearchGeneral>
|
||||
{
|
||||
public readonly IBindable<RulesetInfo> Ruleset = new Bindable<RulesetInfo>();
|
||||
|
||||
public BeatmapSearchGeneralFilterRow()
|
||||
: base(BeatmapsStrings.ListingSearchFiltersGeneral)
|
||||
{
|
||||
}
|
||||
|
||||
protected override MultipleSelectionFilter CreateMultipleSelectionFilter() => new GeneralFilter();
|
||||
protected override MultipleSelectionFilter CreateMultipleSelectionFilter() => new GeneralFilter
|
||||
{
|
||||
Ruleset = { BindTarget = Ruleset }
|
||||
};
|
||||
|
||||
private partial class GeneralFilter : MultipleSelectionFilter
|
||||
{
|
||||
public readonly IBindable<RulesetInfo> Ruleset = new Bindable<RulesetInfo>();
|
||||
|
||||
protected override MultipleSelectionFilterTabItem CreateTabItem(SearchGeneral value)
|
||||
{
|
||||
if (value == SearchGeneral.FeaturedArtists)
|
||||
return new FeaturedArtistsTabItem();
|
||||
switch (value)
|
||||
{
|
||||
case SearchGeneral.Recommended:
|
||||
return new RecommendedDifficultyTabItem
|
||||
{
|
||||
Ruleset = { BindTarget = Ruleset }
|
||||
};
|
||||
|
||||
return new MultipleSelectionFilterTabItem(value);
|
||||
case SearchGeneral.FeaturedArtists:
|
||||
return new FeaturedArtistsTabItem();
|
||||
|
||||
default:
|
||||
return new MultipleSelectionFilterTabItem(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private partial class FeaturedArtistsTabItem : MultipleSelectionFilterTabItem
|
||||
private partial class RecommendedDifficultyTabItem : MultipleSelectionFilterTabItem
|
||||
{
|
||||
private Bindable<bool> disclaimerShown;
|
||||
public readonly IBindable<RulesetInfo> Ruleset = new Bindable<RulesetInfo>();
|
||||
|
||||
[Resolved]
|
||||
private DifficultyRecommender? recommender { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private RulesetStore rulesets { get; set; } = null!;
|
||||
|
||||
public RecommendedDifficultyTabItem()
|
||||
: base(SearchGeneral.Recommended)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
if (recommender != null)
|
||||
recommender.StarRatingUpdated += updateText;
|
||||
|
||||
Ruleset.BindValueChanged(_ => updateText(), true);
|
||||
}
|
||||
|
||||
private void updateText()
|
||||
{
|
||||
// fallback to profile default game mode if beatmap listing mode filter is set to Any
|
||||
// TODO: find a way to update `PlayMode` when the profile default game mode has changed
|
||||
RulesetInfo? ruleset = Ruleset.Value.IsLegacyRuleset() ? Ruleset.Value : rulesets.GetRuleset(api.LocalUser.Value.PlayMode);
|
||||
|
||||
if (ruleset == null) return;
|
||||
|
||||
double? starRating = recommender?.GetRecommendedStarRatingFor(ruleset);
|
||||
|
||||
if (starRating != null)
|
||||
Text.Text = LocalisableString.Interpolate($"{Value.GetLocalisableDescription()} ({starRating.Value.FormatStarRating()})");
|
||||
else
|
||||
Text.Text = Value.GetLocalisableDescription();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (recommender != null)
|
||||
recommender.StarRatingUpdated -= updateText;
|
||||
}
|
||||
}
|
||||
|
||||
private partial class FeaturedArtistsTabItem : MultipleSelectionFilterTabItem, IHasTooltip
|
||||
{
|
||||
private Bindable<bool> disclaimerShown = null!;
|
||||
|
||||
public FeaturedArtistsTabItem()
|
||||
: base(SearchGeneral.FeaturedArtists)
|
||||
@ -48,19 +124,38 @@ namespace osu.Game.Overlays.BeatmapListing
|
||||
}
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; }
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private SessionStatics sessionStatics { get; set; }
|
||||
private OsuConfigManager config { get; set; } = null!;
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
private IDialogOverlay dialogOverlay { get; set; }
|
||||
[Resolved]
|
||||
private SessionStatics sessionStatics { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private IDialogOverlay? dialogOverlay { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private OsuGame? game { get; set; }
|
||||
|
||||
public LocalisableString TooltipText => BeatmapOverlayStrings.FeaturedArtistsTooltip;
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
config.BindWith(OsuSetting.BeatmapListingFeaturedArtistFilter, Active);
|
||||
disclaimerShown = sessionStatics.GetBindable<bool>(Static.FeaturedArtistDisclaimerShownOnce);
|
||||
|
||||
// no need to show the disclaimer if the user already had it toggled off in config.
|
||||
if (!Active.Value)
|
||||
disclaimerShown.Value = true;
|
||||
|
||||
if (game?.HideUnlicensedContent == true)
|
||||
{
|
||||
Enabled.Value = false;
|
||||
Active.Disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected override Color4 ColourNormal => colours.Orange1;
|
||||
@ -68,6 +163,9 @@ namespace osu.Game.Overlays.BeatmapListing
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
if (!Enabled.Value)
|
||||
return true;
|
||||
|
||||
if (!disclaimerShown.Value && dialogOverlay != null)
|
||||
{
|
||||
dialogOverlay.Push(new FeaturedArtistConfirmDialog(() =>
|
||||
|
@ -73,7 +73,10 @@ namespace osu.Game.Overlays.BeatmapListing
|
||||
private void currentChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
foreach (var c in Children)
|
||||
c.Active.Value = Current.Contains(c.Value);
|
||||
{
|
||||
if (!c.Active.Disabled)
|
||||
c.Active.Value = Current.Contains(c.Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -100,7 +103,7 @@ namespace osu.Game.Overlays.BeatmapListing
|
||||
|
||||
protected partial class MultipleSelectionFilterTabItem : FilterTabItem<T>
|
||||
{
|
||||
private Drawable activeContent = null!;
|
||||
private Container activeContent = null!;
|
||||
private Circle background = null!;
|
||||
|
||||
public MultipleSelectionFilterTabItem(T value)
|
||||
@ -160,7 +163,9 @@ namespace osu.Game.Overlays.BeatmapListing
|
||||
{
|
||||
Color4 colour = Active.Value ? ColourActive : ColourNormal;
|
||||
|
||||
if (IsHovered)
|
||||
if (!Enabled.Value)
|
||||
colour = colour.Darken(1f);
|
||||
else if (IsHovered)
|
||||
colour = Active.Value ? colour.Darken(0.2f) : colour.Lighten(0.2f);
|
||||
|
||||
if (Active.Value)
|
||||
|
@ -57,7 +57,9 @@ namespace osu.Game.Overlays.BeatmapListing
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Enabled.BindValueChanged(_ => UpdateState());
|
||||
UpdateState();
|
||||
|
||||
FinishTransforms(true);
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,7 @@ using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Utils;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Overlays.BeatmapSet
|
||||
@ -185,7 +186,7 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
OnHovered = beatmap =>
|
||||
{
|
||||
showBeatmap(beatmap);
|
||||
starRating.Text = beatmap.StarRating.ToLocalisableString(@"0.00");
|
||||
starRating.Text = beatmap.StarRating.FormatStarRating();
|
||||
starRatingContainer.FadeIn(100);
|
||||
},
|
||||
OnClicked = beatmap => { Beatmap.Value = beatmap; },
|
||||
|
@ -7,6 +7,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
@ -132,6 +133,7 @@ namespace osu.Game.Overlays.Chat
|
||||
Channel.PendingMessageResolved -= pendingMessageResolved;
|
||||
}
|
||||
|
||||
[CanBeNull]
|
||||
protected virtual ChatLine CreateChatLine(Message m) => new ChatLine(m);
|
||||
|
||||
protected virtual DaySeparator CreateDaySeparator(DateTimeOffset time) => new DaySeparator(time);
|
||||
@ -155,8 +157,13 @@ namespace osu.Game.Overlays.Chat
|
||||
{
|
||||
addDaySeparatorIfRequired(lastMessage, message);
|
||||
|
||||
ChatLineFlow.Add(CreateChatLine(message));
|
||||
lastMessage = message;
|
||||
var chatLine = CreateChatLine(message);
|
||||
|
||||
if (chatLine != null)
|
||||
{
|
||||
ChatLineFlow.Add(chatLine);
|
||||
lastMessage = message;
|
||||
}
|
||||
}
|
||||
|
||||
var staleMessages = chatLines.Where(c => c.LifetimeEnd == double.MaxValue).ToArray();
|
||||
|
@ -4,12 +4,14 @@
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.LocalisationExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Leaderboards;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using osu.Game.Scoring;
|
||||
@ -162,18 +164,77 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
scoreRankInfo.Value.RankCount = user?.Statistics?.GradesCount[scoreRankInfo.Key] ?? 0;
|
||||
|
||||
detailGlobalRank.Content = user?.Statistics?.GlobalRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-";
|
||||
|
||||
var rankHighest = user?.RankHighest;
|
||||
|
||||
detailGlobalRank.ContentTooltipText = rankHighest != null
|
||||
? UsersStrings.ShowRankHighest(rankHighest.Rank.ToLocalisableString("\\##,##0"), rankHighest.UpdatedAt.ToLocalisableString(@"d MMM yyyy"))
|
||||
: string.Empty;
|
||||
detailGlobalRank.ContentTooltipText = getGlobalRankTooltipText(user);
|
||||
|
||||
detailCountryRank.Content = user?.Statistics?.CountryRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-";
|
||||
detailCountryRank.ContentTooltipText = getCountryRankTooltipText(user);
|
||||
|
||||
rankGraph.Statistics.Value = user?.Statistics;
|
||||
}
|
||||
|
||||
private static LocalisableString getGlobalRankTooltipText(APIUser? user)
|
||||
{
|
||||
var rankHighest = user?.RankHighest;
|
||||
var variants = user?.Statistics?.Variants;
|
||||
|
||||
LocalisableString? result = null;
|
||||
|
||||
if (variants?.Count > 0)
|
||||
{
|
||||
foreach (var variant in variants)
|
||||
{
|
||||
if (variant.GlobalRank != null)
|
||||
{
|
||||
var variantText = LocalisableString.Interpolate($"{variant.VariantType.GetLocalisableDescription()}: {variant.GlobalRank.ToLocalisableString("\\##,##0")}");
|
||||
|
||||
if (result == null)
|
||||
result = variantText;
|
||||
else
|
||||
result = LocalisableString.Interpolate($"{result}\n{variantText}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (rankHighest != null)
|
||||
{
|
||||
var rankHighestText = UsersStrings.ShowRankHighest(
|
||||
rankHighest.Rank.ToLocalisableString("\\##,##0"),
|
||||
rankHighest.UpdatedAt.ToLocalisableString(@"d MMM yyyy"));
|
||||
|
||||
if (result == null)
|
||||
result = rankHighestText;
|
||||
else
|
||||
result = LocalisableString.Interpolate($"{result}\n{rankHighestText}");
|
||||
}
|
||||
|
||||
return result ?? default;
|
||||
}
|
||||
|
||||
private static LocalisableString getCountryRankTooltipText(APIUser? user)
|
||||
{
|
||||
var variants = user?.Statistics?.Variants;
|
||||
|
||||
LocalisableString? result = null;
|
||||
|
||||
if (variants?.Count > 0)
|
||||
{
|
||||
foreach (var variant in variants)
|
||||
{
|
||||
if (variant.CountryRank != null)
|
||||
{
|
||||
var variantText = LocalisableString.Interpolate($"{variant.VariantType.GetLocalisableDescription()}: {variant.CountryRank.ToLocalisableString("\\##,##0")}");
|
||||
|
||||
if (result == null)
|
||||
result = variantText;
|
||||
else
|
||||
result = LocalisableString.Interpolate($"{result}\n{variantText}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result ?? default;
|
||||
}
|
||||
|
||||
private partial class ScoreRankInfo : CompositeDrawable
|
||||
{
|
||||
private readonly OsuSpriteText rankCount;
|
||||
|
@ -57,10 +57,11 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
||||
LabelText = MouseSettingsStrings.HighPrecisionMouse,
|
||||
TooltipText = MouseSettingsStrings.HighPrecisionMouseTooltip,
|
||||
Current = relativeMode,
|
||||
Keywords = new[] { @"raw", @"input", @"relative", @"cursor" }
|
||||
Keywords = new[] { @"raw", @"input", @"relative", @"cursor", "sensitivity", "speed", "velocity" },
|
||||
},
|
||||
new SensitivitySetting
|
||||
{
|
||||
Keywords = new[] { "speed", "velocity" },
|
||||
LabelText = MouseSettingsStrings.CursorSensitivity,
|
||||
Current = localSensitivity
|
||||
},
|
||||
|
@ -9,17 +9,23 @@ using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Overlays.SkinEditor;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
using Realms;
|
||||
|
||||
namespace osu.Game.Overlays.Settings.Sections
|
||||
@ -64,13 +70,26 @@ namespace osu.Game.Overlays.Settings.Sections
|
||||
Current = skins.CurrentSkinInfo,
|
||||
Keywords = new[] { @"skins" },
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(5, 0),
|
||||
Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS, Right = SettingsPanel.CONTENT_MARGINS },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
// This is all super-temporary until we move skin settings to their own panel / overlay.
|
||||
new RenameSkinButton { Padding = new MarginPadding(), RelativeSizeAxes = Axes.None, Width = 120 },
|
||||
new ExportSkinButton { Padding = new MarginPadding(), RelativeSizeAxes = Axes.None, Width = 120 },
|
||||
new DeleteSkinButton { Padding = new MarginPadding(), RelativeSizeAxes = Axes.None, Width = 110 },
|
||||
}
|
||||
},
|
||||
new SettingsButton
|
||||
{
|
||||
Text = SkinSettingsStrings.SkinLayoutEditor,
|
||||
Action = () => skinEditor?.ToggleVisibility(),
|
||||
},
|
||||
new ExportSkinButton(),
|
||||
new DeleteSkinButton(),
|
||||
};
|
||||
}
|
||||
|
||||
@ -136,6 +155,34 @@ namespace osu.Game.Overlays.Settings.Sections
|
||||
}
|
||||
}
|
||||
|
||||
public partial class RenameSkinButton : SettingsButton, IHasPopover
|
||||
{
|
||||
[Resolved]
|
||||
private SkinManager skins { get; set; }
|
||||
|
||||
private Bindable<Skin> currentSkin;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Text = "Rename";
|
||||
Action = this.ShowPopover;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
currentSkin = skins.CurrentSkin.GetBoundCopy();
|
||||
currentSkin.BindValueChanged(skin => Enabled.Value = skin.NewValue.SkinInfo.PerformRead(s => !s.Protected), true);
|
||||
}
|
||||
|
||||
public Popover GetPopover()
|
||||
{
|
||||
return new RenameSkinPopover();
|
||||
}
|
||||
}
|
||||
|
||||
public partial class ExportSkinButton : SettingsButton
|
||||
{
|
||||
[Resolved]
|
||||
@ -146,7 +193,7 @@ namespace osu.Game.Overlays.Settings.Sections
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Text = SkinSettingsStrings.ExportSkinButton;
|
||||
Text = "Export";
|
||||
Action = export;
|
||||
}
|
||||
|
||||
@ -184,7 +231,7 @@ namespace osu.Game.Overlays.Settings.Sections
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Text = SkinSettingsStrings.DeleteSkinButton;
|
||||
Text = "Delete";
|
||||
Action = delete;
|
||||
}
|
||||
|
||||
@ -201,5 +248,63 @@ namespace osu.Game.Overlays.Settings.Sections
|
||||
dialogOverlay?.Push(new SkinDeleteDialog(currentSkin.Value));
|
||||
}
|
||||
}
|
||||
|
||||
public partial class RenameSkinPopover : OsuPopover
|
||||
{
|
||||
[Resolved]
|
||||
private SkinManager skins { get; set; }
|
||||
|
||||
private readonly FocusedTextBox textBox;
|
||||
|
||||
public RenameSkinPopover()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
Origin = Anchor.TopCentre;
|
||||
|
||||
RoundedButton renameButton;
|
||||
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
Direction = FillDirection.Vertical,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Width = 250,
|
||||
Spacing = new Vector2(10f),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
textBox = new FocusedTextBox
|
||||
{
|
||||
PlaceholderText = @"Skin name",
|
||||
FontSize = OsuFont.DEFAULT_FONT_SIZE,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
SelectAllOnFocus = true,
|
||||
},
|
||||
renameButton = new RoundedButton
|
||||
{
|
||||
Height = 40,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
MatchingFilter = true,
|
||||
Text = "Save",
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
renameButton.Action += rename;
|
||||
textBox.OnCommit += (_, _) => rename();
|
||||
}
|
||||
|
||||
protected override void PopIn()
|
||||
{
|
||||
textBox.Text = skins.CurrentSkinInfo.Value.Value.Name;
|
||||
textBox.TakeFocus();
|
||||
|
||||
base.PopIn();
|
||||
}
|
||||
|
||||
private void rename() => skins.CurrentSkinInfo.Value.PerformWrite(skin =>
|
||||
{
|
||||
skin.Name = textBox.Text;
|
||||
PopOut();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,11 +36,13 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
Keywords = new[] { "intro", "welcome" },
|
||||
LabelText = UserInterfaceStrings.InterfaceVoices,
|
||||
Current = config.GetBindable<bool>(OsuSetting.MenuVoice)
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
Keywords = new[] { "intro", "welcome" },
|
||||
LabelText = UserInterfaceStrings.OsuMusicTheme,
|
||||
Current = config.GetBindable<bool>(OsuSetting.MenuMusic)
|
||||
},
|
||||
|
37
osu.Game/Overlays/Volume/GlobalScrollAdjustsVolume.cs
Normal file
37
osu.Game/Overlays/Volume/GlobalScrollAdjustsVolume.cs
Normal file
@ -0,0 +1,37 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Input.Bindings;
|
||||
|
||||
namespace osu.Game.Overlays.Volume
|
||||
{
|
||||
/// <summary>
|
||||
/// Add to a container or screen to make scrolling anywhere in the container cause the global game volume to be adjusted.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is generally expected behaviour in many locations in osu!stable.
|
||||
/// </remarks>
|
||||
public partial class GlobalScrollAdjustsVolume : Container
|
||||
{
|
||||
[Resolved]
|
||||
private VolumeOverlay? volumeOverlay { get; set; }
|
||||
|
||||
public GlobalScrollAdjustsVolume()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
protected override bool OnScroll(ScrollEvent e)
|
||||
{
|
||||
if (e.ScrollDelta.Y == 0)
|
||||
return false;
|
||||
|
||||
// forward any unhandled mouse scroll events to the volume control.
|
||||
return volumeOverlay?.Adjust(GlobalAction.IncreaseVolume, e.ScrollDelta.Y, e.IsPrecise) ?? false;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Input.Bindings;
|
||||
|
||||
namespace osu.Game.Overlays.Volume
|
||||
{
|
||||
public partial class VolumeControlReceptor : Container, IScrollBindingHandler<GlobalAction>, IHandleGlobalKeyboardInput
|
||||
{
|
||||
public Func<GlobalAction, bool> ActionRequested;
|
||||
public Func<GlobalAction, float, bool, bool> ScrollActionRequested;
|
||||
|
||||
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||
{
|
||||
switch (e.Action)
|
||||
{
|
||||
case GlobalAction.DecreaseVolume:
|
||||
case GlobalAction.IncreaseVolume:
|
||||
return ActionRequested?.Invoke(e.Action) == true;
|
||||
|
||||
case GlobalAction.ToggleMute:
|
||||
case GlobalAction.NextVolumeMeter:
|
||||
case GlobalAction.PreviousVolumeMeter:
|
||||
if (!e.Repeat)
|
||||
return ActionRequested?.Invoke(e.Action) == true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnReleased(KeyBindingReleaseEvent<GlobalAction> e)
|
||||
{
|
||||
}
|
||||
|
||||
protected override bool OnScroll(ScrollEvent e)
|
||||
{
|
||||
if (e.ScrollDelta.Y == 0)
|
||||
return false;
|
||||
|
||||
// forward any unhandled mouse scroll events to the volume control.
|
||||
ScrollActionRequested?.Invoke(GlobalAction.IncreaseVolume, e.ScrollDelta.Y, e.IsPrecise);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool OnScroll(KeyBindingScrollEvent<GlobalAction> e) =>
|
||||
ScrollActionRequested?.Invoke(e.Action, e.ScrollAmount, e.IsPrecise) ?? false;
|
||||
}
|
||||
}
|
@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
|
||||
public Vector2 Position { get; set; }
|
||||
|
||||
public LegacyHitObjectType LegacyType { get; set; }
|
||||
public LegacyHitObjectType LegacyType { get; set; } = LegacyHitObjectType.Circle;
|
||||
|
||||
public override Judgement CreateJudgement() => new IgnoreJudgement();
|
||||
|
||||
|
@ -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 osu.Game.Beatmaps.Legacy;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Legacy
|
||||
@ -16,5 +17,10 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
public double Duration { get; set; }
|
||||
|
||||
public double EndTime => StartTime + Duration;
|
||||
|
||||
public ConvertHold()
|
||||
{
|
||||
LegacyType = LegacyHitObjectType.Hold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ using osu.Framework.Bindables;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Beatmaps.Legacy;
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Legacy
|
||||
{
|
||||
@ -56,6 +57,11 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
|
||||
public bool GenerateTicks { get; set; } = true;
|
||||
|
||||
public ConvertSlider()
|
||||
{
|
||||
LegacyType = LegacyHitObjectType.Slider;
|
||||
}
|
||||
|
||||
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty)
|
||||
{
|
||||
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
||||
|
@ -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 osu.Game.Beatmaps.Legacy;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Legacy
|
||||
@ -16,5 +17,10 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
public double Duration { get; set; }
|
||||
|
||||
public double EndTime => StartTime + Duration;
|
||||
|
||||
public ConvertSpinner()
|
||||
{
|
||||
LegacyType = LegacyHitObjectType.Spinner;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -39,7 +39,18 @@ namespace osu.Game.Rulesets.UI
|
||||
private IMod mod;
|
||||
|
||||
private readonly bool showTooltip;
|
||||
private readonly bool showExtendedInformation;
|
||||
|
||||
private bool showExtendedInformation;
|
||||
|
||||
public bool ShowExtendedInformation
|
||||
{
|
||||
get => showExtendedInformation;
|
||||
set
|
||||
{
|
||||
showExtendedInformation = value;
|
||||
updateExtendedInformation();
|
||||
}
|
||||
}
|
||||
|
||||
public IMod Mod
|
||||
{
|
||||
|
@ -34,6 +34,9 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
[Resolved]
|
||||
private Bindable<ControlPointGroup?> selectedGroup { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private IEditorChangeHandler? editorChangeHandler { get; set; }
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours, OverlayColourProvider colourProvider)
|
||||
{
|
||||
@ -110,6 +113,9 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
if (editorChangeHandler != null)
|
||||
editorChangeHandler.OnStateChange += onUndoRedo;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@ -185,5 +191,21 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
|
||||
selectedGroup.Value = group;
|
||||
}
|
||||
|
||||
private void onUndoRedo()
|
||||
{
|
||||
// Best effort. We have no tracking of control points through undo/redo changes.
|
||||
// If we don't deselect, things like offset changes could spawn groups to be added from previous states (see https://github.com/ppy/osu/issues/31098).
|
||||
if (selectedGroup.Value != null && !Beatmap.ControlPointInfo.Groups.Contains(selectedGroup.Value))
|
||||
selectedGroup.Value = null;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (editorChangeHandler != null)
|
||||
editorChangeHandler.OnStateChange -= onUndoRedo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@ -21,6 +22,7 @@ using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens.Edit.Timing.RowAttributes;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Timing
|
||||
{
|
||||
@ -177,7 +179,7 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
private readonly BindableWithCurrent<ControlPointGroup> current = new BindableWithCurrent<ControlPointGroup>();
|
||||
|
||||
private Box background = null!;
|
||||
private Box currentIndicator = null!;
|
||||
private Drawable currentIndicator = null!;
|
||||
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||
@ -202,7 +204,7 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
InternalChildren = new[]
|
||||
{
|
||||
background = new Box
|
||||
{
|
||||
@ -210,11 +212,26 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
Colour = colourProvider.Background1,
|
||||
Alpha = 0,
|
||||
},
|
||||
currentIndicator = new Box
|
||||
currentIndicator = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Width = 5,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Width = 5,
|
||||
},
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Blending = BlendingParameters.Additive,
|
||||
X = 5,
|
||||
Width = 150,
|
||||
Colour = ColourInfo.GradientHorizontal(Color4.White.Opacity(0.1f), Color4.White.Opacity(0))
|
||||
},
|
||||
}
|
||||
},
|
||||
new Container
|
||||
{
|
||||
@ -281,14 +298,8 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
bool hasCurrentTimingPoint = activeTimingPoint.Value != null && current.Value.ControlPoints.Contains(activeTimingPoint.Value);
|
||||
bool hasCurrentEffectPoint = activeEffectPoint.Value != null && current.Value.ControlPoints.Contains(activeEffectPoint.Value);
|
||||
|
||||
if (IsHovered || isSelected)
|
||||
background.FadeIn(100, Easing.OutQuint);
|
||||
else if (hasCurrentTimingPoint || hasCurrentEffectPoint)
|
||||
background.FadeTo(0.2f, 100, Easing.OutQuint);
|
||||
else
|
||||
background.FadeOut(100, Easing.OutQuint);
|
||||
|
||||
background.Colour = isSelected ? colourProvider.Colour3 : colourProvider.Background1;
|
||||
background.FadeTo(IsHovered || isSelected ? 1 : 0, 100, Easing.OutQuint);
|
||||
background.FadeColour(isSelected ? colourProvider.Colour3 : colourProvider.Background1, 100, Easing.OutQuint);
|
||||
|
||||
if (hasCurrentTimingPoint || hasCurrentEffectPoint)
|
||||
{
|
||||
|
@ -6,6 +6,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Development;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shaders;
|
||||
@ -15,6 +16,7 @@ using osu.Framework.Screens;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Seasonal;
|
||||
using IntroSequence = osu.Game.Configuration.IntroSequence;
|
||||
|
||||
namespace osu.Game.Screens
|
||||
@ -37,6 +39,11 @@ namespace osu.Game.Screens
|
||||
|
||||
private IntroScreen getIntroSequence()
|
||||
{
|
||||
// Headless tests run too fast to load non-circles intros correctly.
|
||||
// They will hit the "audio can't play" notification and cause random test failures.
|
||||
if (SeasonalUIConfig.ENABLED && !DebugUtils.IsNUnitRunning)
|
||||
return new IntroChristmas(createMainMenu);
|
||||
|
||||
if (introSequence == IntroSequence.Random)
|
||||
introSequence = (IntroSequence)RNG.Next(0, (int)IntroSequence.Random);
|
||||
|
||||
|
@ -24,6 +24,7 @@ using osu.Game.Localisation;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using osu.Game.Overlays.Volume;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Screens.Backgrounds;
|
||||
using osu.Game.Skinning;
|
||||
@ -174,6 +175,8 @@ namespace osu.Game.Screens.Menu
|
||||
|
||||
return UsingThemedIntro = initialBeatmap != null;
|
||||
}
|
||||
|
||||
AddInternal(new GlobalScrollAdjustsVolume());
|
||||
}
|
||||
|
||||
public override void OnEntering(ScreenTransitionEvent e)
|
||||
@ -207,7 +210,7 @@ namespace osu.Game.Screens.Menu
|
||||
Text = NotificationsStrings.AudioPlaybackIssue
|
||||
});
|
||||
}
|
||||
}, 5000);
|
||||
}, 8000);
|
||||
}
|
||||
|
||||
public override void OnResuming(ScreenTransitionEvent e)
|
||||
|
@ -28,6 +28,7 @@ using osu.Game.Online.API;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Dialog;
|
||||
using osu.Game.Overlays.SkinEditor;
|
||||
using osu.Game.Overlays.Volume;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Screens.Backgrounds;
|
||||
using osu.Game.Screens.Edit;
|
||||
@ -35,6 +36,7 @@ using osu.Game.Screens.OnlinePlay.DailyChallenge;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||
using osu.Game.Screens.OnlinePlay.Playlists;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Seasonal;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
@ -124,6 +126,8 @@ namespace osu.Game.Screens.Menu
|
||||
|
||||
AddRangeInternal(new[]
|
||||
{
|
||||
SeasonalUIConfig.ENABLED ? new MainMenuSeasonalLighting() : Empty(),
|
||||
new GlobalScrollAdjustsVolume(),
|
||||
buttonsContainer = new ParallaxContainer
|
||||
{
|
||||
ParallaxAmount = 0.01f,
|
||||
@ -159,14 +163,15 @@ namespace osu.Game.Screens.Menu
|
||||
}
|
||||
},
|
||||
logoTarget = new Container { RelativeSizeAxes = Axes.Both, },
|
||||
sideFlashes = new MenuSideFlashes(),
|
||||
sideFlashes = SeasonalUIConfig.ENABLED ? new SeasonalMenuSideFlashes() : new MenuSideFlashes(),
|
||||
songTicker = new SongTicker
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
Margin = new MarginPadding { Right = 15, Top = 5 }
|
||||
},
|
||||
new KiaiMenuFountains(),
|
||||
// For now, this is too much alongside the seasonal lighting.
|
||||
SeasonalUIConfig.ENABLED ? Empty() : new KiaiMenuFountains(),
|
||||
bottomElementsFlow = new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
@ -197,18 +202,20 @@ namespace osu.Game.Screens.Menu
|
||||
holdToExitGameOverlay?.CreateProxy() ?? Empty()
|
||||
});
|
||||
|
||||
float baseDim = SeasonalUIConfig.ENABLED ? 0.84f : 1;
|
||||
|
||||
Buttons.StateChanged += state =>
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case ButtonSystemState.Initial:
|
||||
case ButtonSystemState.Exit:
|
||||
ApplyToBackground(b => b.FadeColour(Color4.White, 500, Easing.OutSine));
|
||||
ApplyToBackground(b => b.FadeColour(OsuColour.Gray(baseDim), 500, Easing.OutSine));
|
||||
onlineMenuBanner.State.Value = Visibility.Hidden;
|
||||
break;
|
||||
|
||||
default:
|
||||
ApplyToBackground(b => b.FadeColour(OsuColour.Gray(0.8f), 500, Easing.OutSine));
|
||||
ApplyToBackground(b => b.FadeColour(OsuColour.Gray(baseDim * 0.8f), 500, Easing.OutSine));
|
||||
onlineMenuBanner.State.Value = Visibility.Visible;
|
||||
break;
|
||||
}
|
||||
|
@ -1,21 +1,19 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osuTK.Graphics;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.Menu
|
||||
{
|
||||
internal partial class MenuLogoVisualisation : LogoVisualisation
|
||||
public partial class MenuLogoVisualisation : LogoVisualisation
|
||||
{
|
||||
private IBindable<APIUser> user;
|
||||
private Bindable<Skin> skin;
|
||||
private IBindable<APIUser> user = null!;
|
||||
private Bindable<Skin> skin = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(IAPIProvider api, SkinManager skinManager)
|
||||
@ -23,11 +21,11 @@ namespace osu.Game.Screens.Menu
|
||||
user = api.LocalUser.GetBoundCopy();
|
||||
skin = skinManager.CurrentSkin.GetBoundCopy();
|
||||
|
||||
user.ValueChanged += _ => updateColour();
|
||||
skin.BindValueChanged(_ => updateColour(), true);
|
||||
user.ValueChanged += _ => UpdateColour();
|
||||
skin.BindValueChanged(_ => UpdateColour(), true);
|
||||
}
|
||||
|
||||
private void updateColour()
|
||||
protected virtual void UpdateColour()
|
||||
{
|
||||
if (user.Value?.IsSupporter ?? false)
|
||||
Colour = skin.Value.GetConfig<GlobalSkinColours, Color4>(GlobalSkinColours.MenuGlow)?.Value ?? Color4.White;
|
||||
|
@ -3,8 +3,10 @@
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osuTK.Graphics;
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
@ -13,17 +15,19 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Online.API;
|
||||
using System;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.Menu
|
||||
{
|
||||
public partial class MenuSideFlashes : BeatSyncedContainer
|
||||
{
|
||||
protected virtual bool RefreshColoursEveryFlash => false;
|
||||
|
||||
protected virtual float Intensity => 2;
|
||||
|
||||
private readonly IBindable<WorkingBeatmap> beatmap = new Bindable<WorkingBeatmap>();
|
||||
|
||||
private Box leftBox;
|
||||
@ -67,7 +71,7 @@ namespace osu.Game.Screens.Menu
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Width = box_width * 2,
|
||||
Width = box_width * Intensity,
|
||||
Height = 1.5f,
|
||||
// align off-screen to make sure our edges don't become visible during parallax.
|
||||
X = -box_width,
|
||||
@ -79,7 +83,7 @@ namespace osu.Game.Screens.Menu
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Width = box_width * 2,
|
||||
Width = box_width * Intensity,
|
||||
Height = 1.5f,
|
||||
X = box_width,
|
||||
Alpha = 0,
|
||||
@ -87,8 +91,11 @@ namespace osu.Game.Screens.Menu
|
||||
}
|
||||
};
|
||||
|
||||
user.ValueChanged += _ => updateColour();
|
||||
skin.BindValueChanged(_ => updateColour(), true);
|
||||
if (!RefreshColoursEveryFlash)
|
||||
{
|
||||
user.ValueChanged += _ => updateColour();
|
||||
skin.BindValueChanged(_ => updateColour(), true);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes)
|
||||
@ -104,18 +111,28 @@ namespace osu.Game.Screens.Menu
|
||||
|
||||
private void flash(Drawable d, double beatLength, bool kiai, ChannelAmplitudes amplitudes)
|
||||
{
|
||||
d.FadeTo(Math.Max(0, ((ReferenceEquals(d, leftBox) ? amplitudes.LeftChannel : amplitudes.RightChannel) - amplitude_dead_zone) / (kiai ? kiai_multiplier : alpha_multiplier)), box_fade_in_time)
|
||||
if (RefreshColoursEveryFlash)
|
||||
updateColour();
|
||||
|
||||
d.FadeTo(Math.Clamp(0.1f + ((ReferenceEquals(d, leftBox) ? amplitudes.LeftChannel : amplitudes.RightChannel) - amplitude_dead_zone) / (kiai ? kiai_multiplier : alpha_multiplier), 0.1f, 1),
|
||||
box_fade_in_time)
|
||||
.Then()
|
||||
.FadeOut(beatLength, Easing.In);
|
||||
}
|
||||
|
||||
private void updateColour()
|
||||
protected virtual Color4 GetBaseColour()
|
||||
{
|
||||
Color4 baseColour = colours.Blue;
|
||||
|
||||
if (user.Value?.IsSupporter ?? false)
|
||||
baseColour = skin.Value.GetConfig<GlobalSkinColours, Color4>(GlobalSkinColours.MenuGlow)?.Value ?? baseColour;
|
||||
|
||||
return baseColour;
|
||||
}
|
||||
|
||||
private void updateColour()
|
||||
{
|
||||
var baseColour = GetBaseColour();
|
||||
// linear colour looks better in this case, so let's use it for now.
|
||||
Color4 gradientDark = baseColour.Opacity(0).ToLinear();
|
||||
Color4 gradientLight = baseColour.Opacity(0.6f).ToLinear();
|
||||
|
@ -122,7 +122,8 @@ namespace osu.Game.Screens.Menu
|
||||
MenuTipStrings.RandomSkinShortcut,
|
||||
MenuTipStrings.ToggleReplaySettingsShortcut,
|
||||
MenuTipStrings.CopyModsFromScore,
|
||||
MenuTipStrings.AutoplayBeatmapShortcut
|
||||
MenuTipStrings.AutoplayBeatmapShortcut,
|
||||
MenuTipStrings.LazerIsNotAWord
|
||||
};
|
||||
|
||||
return tips[RNG.Next(0, tips.Length)];
|
||||
|
@ -53,8 +53,12 @@ namespace osu.Game.Screens.Menu
|
||||
private Sample sampleClick;
|
||||
private SampleChannel sampleClickChannel;
|
||||
|
||||
private Sample sampleBeat;
|
||||
private Sample sampleDownbeat;
|
||||
protected virtual MenuLogoVisualisation CreateMenuLogoVisualisation() => new MenuLogoVisualisation();
|
||||
|
||||
protected virtual double BeatSampleVariance => 0.1;
|
||||
|
||||
protected Sample SampleBeat;
|
||||
protected Sample SampleDownbeat;
|
||||
|
||||
private readonly Container colourAndTriangles;
|
||||
private readonly TrianglesV2 triangles;
|
||||
@ -151,15 +155,15 @@ namespace osu.Game.Screens.Menu
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
visualizer = new MenuLogoVisualisation
|
||||
visualizer = CreateMenuLogoVisualisation().With(v =>
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
Alpha = visualizer_default_alpha,
|
||||
Size = SCALE_ADJUST
|
||||
},
|
||||
new Container
|
||||
v.RelativeSizeAxes = Axes.Both;
|
||||
v.Origin = Anchor.Centre;
|
||||
v.Anchor = Anchor.Centre;
|
||||
v.Alpha = visualizer_default_alpha;
|
||||
v.Size = SCALE_ADJUST;
|
||||
}),
|
||||
LogoElements = new Container
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
@ -243,6 +247,8 @@ namespace osu.Game.Screens.Menu
|
||||
};
|
||||
}
|
||||
|
||||
public Container LogoElements { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Schedule a new external animation. Handled queueing and finishing previous animations in a sane way.
|
||||
/// </summary>
|
||||
@ -271,8 +277,9 @@ namespace osu.Game.Screens.Menu
|
||||
private void load(TextureStore textures, AudioManager audio)
|
||||
{
|
||||
sampleClick = audio.Samples.Get(@"Menu/osu-logo-select");
|
||||
sampleBeat = audio.Samples.Get(@"Menu/osu-logo-heartbeat");
|
||||
sampleDownbeat = audio.Samples.Get(@"Menu/osu-logo-downbeat");
|
||||
|
||||
SampleBeat = audio.Samples.Get(@"Menu/osu-logo-heartbeat");
|
||||
SampleDownbeat = audio.Samples.Get(@"Menu/osu-logo-downbeat");
|
||||
|
||||
logo.Texture = textures.Get(@"Menu/logo");
|
||||
ripple.Texture = textures.Get(@"Menu/logo");
|
||||
@ -298,12 +305,13 @@ namespace osu.Game.Screens.Menu
|
||||
{
|
||||
if (beatIndex % timingPoint.TimeSignature.Numerator == 0)
|
||||
{
|
||||
sampleDownbeat?.Play();
|
||||
SampleDownbeat?.Play();
|
||||
}
|
||||
else
|
||||
{
|
||||
var channel = sampleBeat.GetChannel();
|
||||
channel.Frequency.Value = 0.95 + RNG.NextDouble(0.1);
|
||||
var channel = SampleBeat.GetChannel();
|
||||
|
||||
channel.Frequency.Value = 1 - BeatSampleVariance / 2 + RNG.NextDouble(BeatSampleVariance);
|
||||
channel.Play();
|
||||
}
|
||||
});
|
||||
|
@ -47,7 +47,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
|
||||
|
||||
lastPollRequest?.Cancel();
|
||||
|
||||
var req = new GetRoomsRequest(Filter.Value.Status, Filter.Value.Category);
|
||||
var req = new GetRoomsRequest(Filter.Value);
|
||||
|
||||
req.Success += result =>
|
||||
{
|
||||
|
@ -29,18 +29,28 @@ namespace osu.Game.Screens.OnlinePlay.Components
|
||||
base.LoadComplete();
|
||||
|
||||
room.PropertyChanged += onRoomPropertyChanged;
|
||||
|
||||
// Timed update required to track rooms which have hit the end time, see `HasEnded`.
|
||||
Scheduler.AddDelayed(updateRoomStatus, 1000, true);
|
||||
updateRoomStatus();
|
||||
}
|
||||
|
||||
private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(Room.Status))
|
||||
updateRoomStatus();
|
||||
switch (e.PropertyName)
|
||||
{
|
||||
case nameof(Room.Category):
|
||||
case nameof(Room.Status):
|
||||
case nameof(Room.EndDate):
|
||||
case nameof(Room.HasPassword):
|
||||
updateRoomStatus();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateRoomStatus()
|
||||
{
|
||||
this.FadeColour(colours.ForRoomCategory(room.Category) ?? room.Status.GetAppropriateColour(colours), transitionDuration);
|
||||
this.FadeColour(colours.ForRoomCategory(room.Category) ?? colours.ForRoomStatus(room), transitionDuration);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user