mirror of
https://github.com/ppy/osu
synced 2025-01-02 20:32:10 +00:00
Merge branch 'master' into ios-app-delegates
This commit is contained in:
commit
f698284171
@ -10,7 +10,7 @@
|
||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.1219.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.1220.0" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||
|
@ -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>();
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
@ -980,12 +979,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 +1425,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;
|
||||
|
@ -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();
|
||||
|
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
@ -124,6 +125,7 @@ namespace osu.Game.Screens.Menu
|
||||
|
||||
AddRangeInternal(new[]
|
||||
{
|
||||
new GlobalScrollAdjustsVolume(),
|
||||
buttonsContainer = new ParallaxContainer
|
||||
{
|
||||
ParallaxAmount = 0.01f,
|
||||
|
@ -19,6 +19,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
[Resolved(CanBeNull = true)]
|
||||
private ILocalUserPlayInfo? localUserInfo { get; set; }
|
||||
|
||||
protected new ChatTextBox TextBox => base.TextBox!;
|
||||
|
||||
private readonly IBindable<LocalUserPlayingState> localUserPlaying = new Bindable<LocalUserPlayingState>();
|
||||
|
||||
public override bool PropagatePositionalInputSubTree => localUserPlaying.Value != LocalUserPlayingState.Playing;
|
||||
@ -58,7 +60,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
|
||||
localUserPlaying.BindValueChanged(playing =>
|
||||
{
|
||||
// for now let's never hold focus. this avoid misdirected gameplay keys entering chat.
|
||||
// for now let's never hold focus. this avoids misdirected gameplay keys entering chat.
|
||||
// note that this is done within this callback as it triggers an un-focus as well.
|
||||
TextBox.HoldFocus = false;
|
||||
|
||||
|
44
osu.Game/Screens/Play/GameplayScrollWheelHandling.cs
Normal file
44
osu.Game/Screens/Play/GameplayScrollWheelHandling.cs
Normal file
@ -0,0 +1,44 @@
|
||||
// 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.Bindables;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Overlays.Volume;
|
||||
|
||||
namespace osu.Game.Screens.Play
|
||||
{
|
||||
/// <summary>
|
||||
/// Primarily handles volume adjustment in gameplay.
|
||||
///
|
||||
/// - If the user has mouse wheel disabled, only allow during break time or when holding alt. Also block scroll from parent handling.
|
||||
/// - Otherwise always allow, as per <see cref="GlobalScrollAdjustsVolume"/> implementation.
|
||||
/// </summary>
|
||||
internal partial class GameplayScrollWheelHandling : GlobalScrollAdjustsVolume
|
||||
{
|
||||
private Bindable<bool> mouseWheelDisabled = null!;
|
||||
|
||||
[Resolved]
|
||||
private IGameplayClock gameplayClock { get; set; } = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuConfigManager config)
|
||||
{
|
||||
mouseWheelDisabled = config.GetBindable<bool>(OsuSetting.MouseDisableWheel);
|
||||
}
|
||||
|
||||
protected override bool OnScroll(ScrollEvent e)
|
||||
{
|
||||
// During pause, allow global volume adjust regardless of settings.
|
||||
if (gameplayClock.IsPaused.Value)
|
||||
return base.OnScroll(e);
|
||||
|
||||
// Block any parent handling of scroll if the user has asked for it (special case when holding "Alt").
|
||||
if (mouseWheelDisabled.Value && !e.AltPressed)
|
||||
return true;
|
||||
|
||||
return base.OnScroll(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -15,7 +15,6 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Threading;
|
||||
@ -88,8 +87,6 @@ namespace osu.Game.Screens.Play
|
||||
private bool isRestarting;
|
||||
private bool skipExitTransition;
|
||||
|
||||
private Bindable<bool> mouseWheelDisabled;
|
||||
|
||||
private readonly Bindable<bool> storyboardReplacesBackground = new Bindable<bool>();
|
||||
|
||||
public IBindable<bool> LocalUserPlaying => localUserPlaying;
|
||||
@ -228,8 +225,6 @@ namespace osu.Game.Screens.Play
|
||||
return;
|
||||
}
|
||||
|
||||
mouseWheelDisabled = config.GetBindable<bool>(OsuSetting.MouseDisableWheel);
|
||||
|
||||
if (game != null)
|
||||
gameActive.BindTo(game.IsActive);
|
||||
|
||||
@ -251,7 +246,10 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
dependencies.CacheAs(HealthProcessor);
|
||||
|
||||
InternalChild = GameplayClockContainer = CreateGameplayClockContainer(Beatmap.Value, DrawableRuleset.GameplayStartTime);
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
GameplayClockContainer = CreateGameplayClockContainer(Beatmap.Value, DrawableRuleset.GameplayStartTime),
|
||||
};
|
||||
|
||||
AddInternal(screenSuspension = new ScreenSuspensionHandler(GameplayClockContainer));
|
||||
|
||||
@ -266,6 +264,7 @@ namespace osu.Game.Screens.Play
|
||||
dependencies.CacheAs(GameplayState = new GameplayState(playableBeatmap, ruleset, gameplayMods, Score, ScoreProcessor, HealthProcessor, Beatmap.Value.Storyboard));
|
||||
|
||||
var rulesetSkinProvider = new RulesetSkinProvidingContainer(ruleset, playableBeatmap, Beatmap.Value.Skin);
|
||||
GameplayClockContainer.Add(new GameplayScrollWheelHandling());
|
||||
|
||||
// load the skinning hierarchy first.
|
||||
// this is intentionally done in two stages to ensure things are in a loaded state before exposing the ruleset to skin sources.
|
||||
@ -894,16 +893,6 @@ namespace osu.Game.Screens.Play
|
||||
});
|
||||
}
|
||||
|
||||
protected override bool OnScroll(ScrollEvent e)
|
||||
{
|
||||
// During pause, allow global volume adjust regardless of settings.
|
||||
if (GameplayClockContainer.IsPaused.Value)
|
||||
return false;
|
||||
|
||||
// Block global volume adjust if the user has asked for it (special case when holding "Alt").
|
||||
return mouseWheelDisabled.Value && !e.AltPressed;
|
||||
}
|
||||
|
||||
#region Gameplay leaderboard
|
||||
|
||||
protected readonly Bindable<bool> LeaderboardExpandedState = new BindableBool();
|
||||
|
@ -27,6 +27,7 @@ using osu.Game.Input;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using osu.Game.Overlays.Volume;
|
||||
using osu.Game.Performance;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Menu;
|
||||
@ -190,6 +191,7 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new GlobalScrollAdjustsVolume(),
|
||||
(content = new LogoTrackingContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
|
@ -31,6 +31,7 @@ using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Mods;
|
||||
using osu.Game.Overlays.Volume;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Screens.Backgrounds;
|
||||
@ -169,10 +170,12 @@ namespace osu.Game.Screens.Select
|
||||
|
||||
AddRangeInternal(new Drawable[]
|
||||
{
|
||||
new GlobalScrollAdjustsVolume(),
|
||||
new VerticalMaskingContainer
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new GlobalScrollAdjustsVolume(),
|
||||
new GridContainer // used for max width implementation
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
|
@ -35,8 +35,8 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Realm" Version="11.5.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2024.1219.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2024.1202.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2024.1220.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2024.1219.1" />
|
||||
<PackageReference Include="Sentry" Version="4.13.0" />
|
||||
<!-- Held back due to 0.34.0 failing AOT compilation on ZstdSharp.dll dependency. -->
|
||||
<PackageReference Include="SharpCompress" Version="0.38.0" />
|
||||
|
@ -17,6 +17,6 @@
|
||||
<MtouchInterpreter>-all</MtouchInterpreter>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2024.1219.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2024.1220.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -34,9 +34,11 @@
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>We don't really use the camera.</string>
|
||||
<string>We don't use the camera.</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>We don't really use the microphone.</string>
|
||||
<string>We don't use the microphone.</string>
|
||||
<key>NSBluetoothAlwaysUsageDescription</key>
|
||||
<string>We don't use Bluetooth.</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
@ -153,5 +155,7 @@
|
||||
</array>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.music-games</string>
|
||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
Loading…
Reference in New Issue
Block a user