1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-22 00:43:25 +08:00

Merge branch 'master' into Private_Messages

This commit is contained in:
miterosan 2018-07-08 19:29:33 +02:00 committed by GitHub
commit c56a4ea893
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 809 additions and 479 deletions

30
.vscode/launch.json vendored
View File

@ -11,7 +11,11 @@
], ],
"cwd": "${workspaceRoot}", "cwd": "${workspaceRoot}",
"preLaunchTask": "Build tests (Debug)", "preLaunchTask": "Build tests (Debug)",
"env": {}, "linux": {
"env": {
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp2.1:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole" "console": "internalConsole"
}, },
{ {
@ -24,7 +28,11 @@
], ],
"cwd": "${workspaceRoot}", "cwd": "${workspaceRoot}",
"preLaunchTask": "Build tests (Release)", "preLaunchTask": "Build tests (Release)",
"env": {}, "linux": {
"env": {
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp2.1:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole" "console": "internalConsole"
}, },
{ {
@ -33,11 +41,15 @@
"request": "launch", "request": "launch",
"program": "dotnet", "program": "dotnet",
"args": [ "args": [
"${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.1/osu!.dll", "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.1/osu!.dll"
], ],
"cwd": "${workspaceRoot}", "cwd": "${workspaceRoot}",
"preLaunchTask": "Build osu! (Debug)", "preLaunchTask": "Build osu! (Debug)",
"env": {}, "linux": {
"env": {
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.1:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole" "console": "internalConsole"
}, },
{ {
@ -46,12 +58,16 @@
"request": "launch", "request": "launch",
"program": "dotnet", "program": "dotnet",
"args": [ "args": [
"${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.1/osu!.dll", "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.1/osu!.dll"
], ],
"cwd": "${workspaceRoot}", "cwd": "${workspaceRoot}",
"preLaunchTask": "Build osu! (Release)", "preLaunchTask": "Build osu! (Release)",
"env": {}, "linux": {
"env": {
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.1:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole" "console": "internalConsole"
} }
] ]
} }

View File

@ -1,18 +1,32 @@
# osu! [![Build status](https://ci.appveyor.com/api/projects/status/u2p01nx7l6og8buh?svg=true)](https://ci.appveyor.com/project/peppy/osu) [![CodeFactor](https://www.codefactor.io/repository/github/ppy/osu/badge)](https://www.codefactor.io/repository/github/ppy/osu) [![dev chat](https://discordapp.com/api/guilds/188630481301012481/widget.png?style=shield)](https://discord.gg/ppy) # osu! [![Build status](https://ci.appveyor.com/api/projects/status/u2p01nx7l6og8buh?svg=true)](https://ci.appveyor.com/project/peppy/osu) [![CodeFactor](https://www.codefactor.io/repository/github/ppy/osu/badge)](https://www.codefactor.io/repository/github/ppy/osu) [![dev chat](https://discordapp.com/api/guilds/188630481301012481/widget.png?style=shield)](https://discord.gg/ppy)
Rhythm is just a *click* away. The future of [osu!](https://osu.ppy.sh) and the beginning of an open era! Rhythm is just a *click* away. The future of [osu!](https://osu.ppy.sh) and the beginning of an open era! Commonly known by the codename "osu!lazer". Pew pew.
# Status # Status
This is still heavily under development and is not intended for end-user use. This repository is intended for developer collaboration. You're welcome to try and use it but please do not submit bug reports without a patch. Please do not ask for help building or using this software. This project is still heavily under development, but is in a state where users are encouraged to try it out and keep it installed alongside the stable osu! client. It will continue to evolve over the coming months and hopefully bring some new unique features to the table.
We are accepting bug reports (please report with as much detail as possible). Feature requests are welcome as long as you read and understand the contribution guidelines listed below.
# Requirements # Requirements
- A desktop platform that can compile .NET 4.7.1. We recommend using [Visual Studio Community Edition](https://www.visualstudio.com/) (Windows), [Visual Studio for Mac](https://www.visualstudio.com/vs/visual-studio-mac/) (macOS) or [MonoDevelop](http://www.monodevelop.com/download/) (Linux), all of which are free. [Visual Studio Code](https://code.visualstudio.com/) may also be used but requires further setup steps which are not covered here. - A desktop platform with the [.NET Core SDK 2.1](https://www.microsoft.com/net/learn/get-started) or higher installed.
- When working with the codebase, we recommend using an IDE with intellisense and syntax highlighting, such as [Visual Studio Community Edition](https://www.visualstudio.com/) (Windows), [Visual Studio Code](https://code.visualstudio.com/) (with the C# plugin installed) or [Jetbrains Rider](https://www.jetbrains.com/rider/) (commercial).
# Getting Started # Building and running
- Clone the repository including submodules (`git clone --recurse-submodules https://github.com/ppy/osu`)
- Build in your IDE of choice (recommended IDEs automatically restore nuget packages; if you are using an alternative make sure to `nuget restore`) If you are not interested in developing the game, please head over to the [releases](https://github.com/ppy/osu/releases) to download a precompiled build with automatic updating enabled (download and run the install executable for your platform).
Clone the repository including submodules
`git clone --recurse-submodules https://github.com/ppy/osu`
Build and run
- Using Visual Studio 2017, Rider or Visual Studio Code (configurations are included)
- From command line using `dotnet run --project osu.Desktop --framework netcoreapp2.1`
The above methods should automatically do so, but if you run into issues building you may need to restore nuget packages (commonly via `dotnet restore`).
# Contributing # Contributing

View File

@ -5,6 +5,8 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.MathUtils; using osu.Framework.MathUtils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
@ -13,11 +15,10 @@ using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Mania.Tests namespace osu.Game.Rulesets.Mania.Tests
{ {
[TestFixture] [TestFixture]
public class ManiaBeatmapConversionTest : BeatmapConversionTest<ConvertValue> public class ManiaBeatmapConversionTest : BeatmapConversionTest<ManiaConvertMapping, ConvertValue>
{ {
protected override string ResourceAssembly => "osu.Game.Rulesets.Mania"; protected override string ResourceAssembly => "osu.Game.Rulesets.Mania";
[NonParallelizable]
[TestCase("basic")] [TestCase("basic")]
public new void Test(string name) public new void Test(string name)
{ {
@ -34,9 +35,35 @@ namespace osu.Game.Rulesets.Mania.Tests
}; };
} }
protected override ManiaConvertMapping CreateConvertMapping() => new ManiaConvertMapping(Converter);
protected override Ruleset CreateRuleset() => new ManiaRuleset(); protected override Ruleset CreateRuleset() => new ManiaRuleset();
} }
public class ManiaConvertMapping : ConvertMapping<ConvertValue>, IEquatable<ManiaConvertMapping>
{
public uint RandomW;
public uint RandomX;
public uint RandomY;
public uint RandomZ;
public ManiaConvertMapping()
{
}
public ManiaConvertMapping(IBeatmapConverter converter)
{
var maniaConverter = (ManiaBeatmapConverter)converter;
RandomW = maniaConverter.Random.W;
RandomX = maniaConverter.Random.X;
RandomY = maniaConverter.Random.Y;
RandomZ = maniaConverter.Random.Z;
}
public bool Equals(ManiaConvertMapping other) => other != null && RandomW == other.RandomW && RandomX == other.RandomX && RandomY == other.RandomY && RandomZ == other.RandomZ;
public override bool Equals(ConvertMapping<ConvertValue> other) => base.Equals(other) && Equals(other as ManiaConvertMapping);
}
public struct ConvertValue : IEquatable<ConvertValue> public struct ConvertValue : IEquatable<ConvertValue>
{ {
/// <summary> /// <summary>

View File

@ -153,9 +153,6 @@ namespace osu.Game.Rulesets.Mania.Tests
if (!(obj.HitObject is IHasEndTime endTime)) if (!(obj.HitObject is IHasEndTime endTime))
continue; continue;
if (!obj.HasNestedHitObjects)
continue;
foreach (var nested in obj.NestedHitObjects) foreach (var nested in obj.NestedHitObjects)
{ {
double finalPosition = (nested.HitObject.StartTime - obj.HitObject.StartTime) / endTime.Duration; double finalPosition = (nested.HitObject.StartTime - obj.HitObject.StartTime) / endTime.Duration;

View File

@ -5,6 +5,7 @@ using osu.Game.Rulesets.Mania.Objects;
using System; using System;
using System.Linq; using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.MathUtils;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
@ -28,8 +29,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
public int TargetColumns; public int TargetColumns;
public readonly bool IsForCurrentRuleset; public readonly bool IsForCurrentRuleset;
// Internal for testing purposes
internal FastRandom Random { get; private set; }
private Pattern lastPattern = new Pattern(); private Pattern lastPattern = new Pattern();
private FastRandom random;
private ManiaBeatmap beatmap; private ManiaBeatmap beatmap;
@ -62,7 +65,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
BeatmapDifficulty difficulty = original.BeatmapInfo.BaseDifficulty; BeatmapDifficulty difficulty = original.BeatmapInfo.BaseDifficulty;
int seed = (int)Math.Round(difficulty.DrainRate + difficulty.CircleSize) * 20 + (int)(difficulty.OverallDifficulty * 41.2) + (int)Math.Round(difficulty.ApproachRate); int seed = (int)Math.Round(difficulty.DrainRate + difficulty.CircleSize) * 20 + (int)(difficulty.OverallDifficulty * 41.2) + (int)Math.Round(difficulty.ApproachRate);
random = new FastRandom(seed); Random = new FastRandom(seed);
return base.ConvertBeatmap(original); return base.ConvertBeatmap(original);
} }
@ -100,7 +103,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
private double lastTime; private double lastTime;
private Vector2 lastPosition; private Vector2 lastPosition;
private PatternType lastStair; private PatternType lastStair = PatternType.Stair;
private void recordNote(double time, Vector2 position) private void recordNote(double time, Vector2 position)
{ {
lastTime = time; lastTime = time;
@ -115,12 +118,15 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
/// <returns>The hit objects generated.</returns> /// <returns>The hit objects generated.</returns>
private IEnumerable<ManiaHitObject> generateSpecific(HitObject original, IBeatmap originalBeatmap) private IEnumerable<ManiaHitObject> generateSpecific(HitObject original, IBeatmap originalBeatmap)
{ {
var generator = new SpecificBeatmapPatternGenerator(random, original, beatmap, lastPattern, originalBeatmap); var generator = new SpecificBeatmapPatternGenerator(Random, original, beatmap, lastPattern, originalBeatmap);
Pattern newPattern = generator.Generate(); foreach (var newPattern in generator.Generate())
lastPattern = newPattern; {
lastPattern = newPattern;
return newPattern.HitObjects; foreach (var obj in newPattern.HitObjects)
yield return obj;
}
} }
/// <summary> /// <summary>
@ -138,27 +144,44 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
Patterns.PatternGenerator conversion = null; Patterns.PatternGenerator conversion = null;
if (distanceData != null) if (distanceData != null)
conversion = new DistanceObjectPatternGenerator(random, original, beatmap, lastPattern, originalBeatmap); {
var generator = new DistanceObjectPatternGenerator(Random, original, beatmap, lastPattern, originalBeatmap);
conversion = generator;
for (double time = original.StartTime; !Precision.DefinitelyBigger(time, generator.EndTime); time += generator.SegmentDuration)
{
recordNote(time, positionData?.Position ?? Vector2.Zero);
computeDensity(time);
}
}
else if (endTimeData != null) else if (endTimeData != null)
conversion = new EndTimeObjectPatternGenerator(random, original, beatmap, originalBeatmap); {
conversion = new EndTimeObjectPatternGenerator(Random, original, beatmap, originalBeatmap);
recordNote(endTimeData.EndTime, new Vector2(256, 192));
computeDensity(endTimeData.EndTime);
}
else if (positionData != null) else if (positionData != null)
{ {
computeDensity(original.StartTime); computeDensity(original.StartTime);
conversion = new HitObjectPatternGenerator(random, original, beatmap, lastPattern, lastTime, lastPosition, density, lastStair, originalBeatmap); conversion = new HitObjectPatternGenerator(Random, original, beatmap, lastPattern, lastTime, lastPosition, density, lastStair, originalBeatmap);
recordNote(original.StartTime, positionData.Position); recordNote(original.StartTime, positionData.Position);
} }
if (conversion == null) if (conversion == null)
return null; yield break;
Pattern newPattern = conversion.Generate(); foreach (var newPattern in conversion.Generate())
{
lastPattern = conversion is EndTimeObjectPatternGenerator ? lastPattern : newPattern;
lastStair = (conversion as HitObjectPatternGenerator)?.StairType ?? lastStair;
lastPattern = conversion is EndTimeObjectPatternGenerator ? lastPattern : newPattern; foreach (var obj in newPattern.HitObjects)
lastStair = (conversion as HitObjectPatternGenerator)?.StairType ?? lastStair; yield return obj;
return newPattern.HitObjects; }
} }
/// <summary> /// <summary>
@ -171,7 +194,12 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
{ {
} }
public override Pattern Generate() public override IEnumerable<Pattern> Generate()
{
yield return generate();
}
private Pattern generate()
{ {
var endTimeData = HitObject as IHasEndTime; var endTimeData = HitObject as IHasEndTime;
var positionData = HitObject as IHasXPosition; var positionData = HitObject as IHasXPosition;

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.MathUtils;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.MathUtils; using osu.Game.Rulesets.Mania.MathUtils;
@ -24,8 +25,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
/// </summary> /// </summary>
private const float osu_base_scoring_distance = 100; private const float osu_base_scoring_distance = 100;
private readonly double endTime; public readonly double EndTime;
private readonly double segmentDuration; public readonly double SegmentDuration;
private readonly int spanCount; private readonly int spanCount;
private PatternType convertType; private PatternType convertType;
@ -52,53 +54,81 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
// The duration of the osu! hit object // The duration of the osu! hit object
double osuDuration = distance / osuVelocity; double osuDuration = distance / osuVelocity;
endTime = hitObject.StartTime + osuDuration; EndTime = hitObject.StartTime + osuDuration;
segmentDuration = (endTime - HitObject.StartTime) / spanCount; SegmentDuration = (EndTime - HitObject.StartTime) / spanCount;
} }
public override Pattern Generate() public override IEnumerable<Pattern> Generate()
{
var originalPattern = generate();
if (originalPattern.HitObjects.Count() == 1)
{
yield return originalPattern;
yield break;
}
// We need to split the intermediate pattern into two new patterns:
// 1. A pattern containing all objects that do not end at our EndTime.
// 2. A pattern containing all objects that end at our EndTime. This will be used for further pattern generation.
var intermediatePattern = new Pattern();
var endTimePattern = new Pattern();
foreach (var obj in originalPattern.HitObjects)
{
if (!Precision.AlmostEquals(EndTime, (obj as IHasEndTime)?.EndTime ?? obj.StartTime))
intermediatePattern.Add(obj);
else
endTimePattern.Add(obj);
}
yield return intermediatePattern;
yield return endTimePattern;
}
private Pattern generate()
{ {
if (TotalColumns == 1) if (TotalColumns == 1)
{ {
var pattern = new Pattern(); var pattern = new Pattern();
addToPattern(pattern, 0, HitObject.StartTime, endTime); addToPattern(pattern, 0, HitObject.StartTime, EndTime);
return pattern; return pattern;
} }
if (spanCount > 1) if (spanCount > 1)
{ {
if (segmentDuration <= 90) if (SegmentDuration <= 90)
return generateRandomHoldNotes(HitObject.StartTime, 1); return generateRandomHoldNotes(HitObject.StartTime, 1);
if (segmentDuration <= 120) if (SegmentDuration <= 120)
{ {
convertType |= PatternType.ForceNotStack; convertType |= PatternType.ForceNotStack;
return generateRandomNotes(HitObject.StartTime, spanCount + 1); return generateRandomNotes(HitObject.StartTime, spanCount + 1);
} }
if (segmentDuration <= 160) if (SegmentDuration <= 160)
return generateStair(HitObject.StartTime); return generateStair(HitObject.StartTime);
if (segmentDuration <= 200 && ConversionDifficulty > 3) if (SegmentDuration <= 200 && ConversionDifficulty > 3)
return generateRandomMultipleNotes(HitObject.StartTime); return generateRandomMultipleNotes(HitObject.StartTime);
double duration = endTime - HitObject.StartTime; double duration = EndTime - HitObject.StartTime;
if (duration >= 4000) if (duration >= 4000)
return generateNRandomNotes(HitObject.StartTime, 0.23, 0, 0); return generateNRandomNotes(HitObject.StartTime, 0.23, 0, 0);
if (segmentDuration > 400 && spanCount < TotalColumns - 1 - RandomStart) if (SegmentDuration > 400 && spanCount < TotalColumns - 1 - RandomStart)
return generateTiledHoldNotes(HitObject.StartTime); return generateTiledHoldNotes(HitObject.StartTime);
return generateHoldAndNormalNotes(HitObject.StartTime); return generateHoldAndNormalNotes(HitObject.StartTime);
} }
if (segmentDuration <= 110) if (SegmentDuration <= 110)
{ {
if (PreviousPattern.ColumnWithObjects < TotalColumns) if (PreviousPattern.ColumnWithObjects < TotalColumns)
convertType |= PatternType.ForceNotStack; convertType |= PatternType.ForceNotStack;
else else
convertType &= ~PatternType.ForceNotStack; convertType &= ~PatternType.ForceNotStack;
return generateRandomNotes(HitObject.StartTime, segmentDuration < 80 ? 1 : 2); return generateRandomNotes(HitObject.StartTime, SegmentDuration < 80 ? 1 : 2);
} }
if (ConversionDifficulty > 6.5) if (ConversionDifficulty > 6.5)
@ -148,7 +178,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{ {
while (pattern.ColumnHasObject(nextColumn) || PreviousPattern.ColumnHasObject(nextColumn)) //find available column while (pattern.ColumnHasObject(nextColumn) || PreviousPattern.ColumnHasObject(nextColumn)) //find available column
nextColumn = Random.Next(RandomStart, TotalColumns); nextColumn = Random.Next(RandomStart, TotalColumns);
addToPattern(pattern, nextColumn, startTime, endTime); addToPattern(pattern, nextColumn, startTime, EndTime);
} }
// This is can't be combined with the above loop due to RNG // This is can't be combined with the above loop due to RNG
@ -156,7 +186,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{ {
while (pattern.ColumnHasObject(nextColumn)) while (pattern.ColumnHasObject(nextColumn))
nextColumn = Random.Next(RandomStart, TotalColumns); nextColumn = Random.Next(RandomStart, TotalColumns);
addToPattern(pattern, nextColumn, startTime, endTime); addToPattern(pattern, nextColumn, startTime, EndTime);
} }
return pattern; return pattern;
@ -193,7 +223,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
nextColumn = Random.Next(RandomStart, TotalColumns); nextColumn = Random.Next(RandomStart, TotalColumns);
lastColumn = nextColumn; lastColumn = nextColumn;
startTime += segmentDuration; startTime += SegmentDuration;
} }
return pattern; return pattern;
@ -223,7 +253,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
for (int i = 0; i <= spanCount; i++) for (int i = 0; i <= spanCount; i++)
{ {
addToPattern(pattern, column, startTime, startTime); addToPattern(pattern, column, startTime, startTime);
startTime += segmentDuration; startTime += SegmentDuration;
// Check if we're at the borders of the stage, and invert the pattern if so // Check if we're at the borders of the stage, and invert the pattern if so
if (increasing) if (increasing)
@ -284,7 +314,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
addToPattern(pattern, nextColumn, startTime, startTime); addToPattern(pattern, nextColumn, startTime, startTime);
nextColumn = Random.Next(RandomStart, TotalColumns); nextColumn = Random.Next(RandomStart, TotalColumns);
startTime += segmentDuration; startTime += SegmentDuration;
} }
return pattern; return pattern;
@ -372,8 +402,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
while (pattern.ColumnHasObject(nextColumn)) while (pattern.ColumnHasObject(nextColumn))
nextColumn = Random.Next(RandomStart, TotalColumns); nextColumn = Random.Next(RandomStart, TotalColumns);
addToPattern(pattern, nextColumn, startTime, endTime); addToPattern(pattern, nextColumn, startTime, EndTime);
startTime += segmentDuration; startTime += SegmentDuration;
} }
return pattern; return pattern;
@ -402,7 +432,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
} }
// Create the hold note // Create the hold note
addToPattern(pattern, holdColumn, startTime, endTime); addToPattern(pattern, holdColumn, startTime, EndTime);
int nextColumn = Random.Next(RandomStart, TotalColumns); int nextColumn = Random.Next(RandomStart, TotalColumns);
int noteCount; int noteCount;
@ -434,7 +464,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
pattern.Add(rowPattern); pattern.Add(rowPattern);
rowPattern.Clear(); rowPattern.Clear();
startTime += segmentDuration; startTime += SegmentDuration;
} }
return pattern; return pattern;
@ -452,7 +482,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (curveData == null) if (curveData == null)
return HitObject.Samples; return HitObject.Samples;
double segmentTime = (endTime - HitObject.StartTime) / spanCount; double segmentTime = (EndTime - HitObject.StartTime) / spanCount;
int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime); int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime);
return curveData.RepeatSamples[index]; return curveData.RepeatSamples[index];

View File

@ -24,7 +24,12 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
endTime = endtimeData?.EndTime ?? 0; endTime = endtimeData?.EndTime ?? 0;
} }
public override Pattern Generate() public override IEnumerable<Pattern> Generate()
{
yield return generate();
}
private Pattern generate()
{ {
var pattern = new Pattern(); var pattern = new Pattern();

View File

@ -2,6 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using OpenTK; using OpenTK;
using osu.Game.Audio; using osu.Game.Audio;
@ -82,127 +83,133 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{ {
if (HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_FINISH) && TotalColumns != 8) if (HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_FINISH) && TotalColumns != 8)
convertType |= PatternType.Mirror; convertType |= PatternType.Mirror;
else else if (HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_CLAP))
convertType |= PatternType.Gathered; convertType |= PatternType.Gathered;
} }
} }
public override Pattern Generate() public override IEnumerable<Pattern> Generate()
{ {
if (TotalColumns == 1) yield return generate();
}
private Pattern generate()
{
var pattern = new Pattern();
try
{ {
var pattern = new Pattern(); if (TotalColumns == 1)
addToPattern(pattern, 0);
return pattern;
}
int lastColumn = PreviousPattern.HitObjects.FirstOrDefault()?.Column ?? 0;
if ((convertType & PatternType.Reverse) > 0 && PreviousPattern.HitObjects.Any())
{
// Generate a new pattern by copying the last hit objects in reverse-column order
var pattern = new Pattern();
for (int i = RandomStart; i < TotalColumns; i++)
if (PreviousPattern.ColumnHasObject(i))
addToPattern(pattern, RandomStart + TotalColumns - i - 1);
return pattern;
}
if ((convertType & PatternType.Cycle) > 0 && 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))
{
// Generate a new pattern by cycling backwards (similar to Reverse but for only one hit object)
var pattern = new Pattern();
int column = RandomStart + TotalColumns - lastColumn - 1;
addToPattern(pattern, column);
return pattern;
}
if ((convertType & PatternType.ForceStack) > 0 && PreviousPattern.HitObjects.Any())
{
// Generate a new pattern by placing on the already filled columns
var pattern = new Pattern();
for (int i = RandomStart; i < TotalColumns; i++)
if (PreviousPattern.ColumnHasObject(i))
addToPattern(pattern, i);
return pattern;
}
if ((convertType & PatternType.Stair) > 0 && PreviousPattern.HitObjects.Count() == 1)
{
// Generate a new pattern by placing on the next column, cycling back to the start if there is no "next"
var pattern = new Pattern();
int targetColumn = lastColumn + 1;
if (targetColumn == TotalColumns)
{ {
targetColumn = RandomStart; addToPattern(pattern, 0);
StairType = PatternType.ReverseStair; return pattern;
} }
addToPattern(pattern, targetColumn); int lastColumn = PreviousPattern.HitObjects.FirstOrDefault()?.Column ?? 0;
return pattern;
}
if ((convertType & PatternType.ReverseStair) > 0 && PreviousPattern.HitObjects.Count() == 1) if ((convertType & PatternType.Reverse) > 0 && PreviousPattern.HitObjects.Any())
{
// Generate a new pattern by placing on the previous column, cycling back to the end if there is no "previous"
var pattern = new Pattern();
int targetColumn = lastColumn - 1;
if (targetColumn == RandomStart - 1)
{ {
targetColumn = TotalColumns - 1; // Generate a new pattern by copying the last hit objects in reverse-column order
StairType = PatternType.Stair; for (int i = RandomStart; i < TotalColumns; i++)
if (PreviousPattern.ColumnHasObject(i))
addToPattern(pattern, RandomStart + TotalColumns - i - 1);
return pattern;
} }
addToPattern(pattern, targetColumn); if ((convertType & PatternType.Cycle) > 0 && PreviousPattern.HitObjects.Count() == 1
return pattern; // 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;
addToPattern(pattern, column);
if ((convertType & PatternType.KeepSingle) > 0) return pattern;
return generateRandomNotes(1); }
if ((convertType & PatternType.ForceStack) > 0 && PreviousPattern.HitObjects.Any())
{
// Generate a new pattern by placing on the already filled columns
for (int i = RandomStart; i < TotalColumns; i++)
if (PreviousPattern.ColumnHasObject(i))
addToPattern(pattern, i);
return pattern;
}
if (PreviousPattern.HitObjects.Count() == 1)
{
if ((convertType & PatternType.Stair) > 0)
{
// Generate a new pattern by placing on the next column, cycling back to the start if there is no "next"
int targetColumn = lastColumn + 1;
if (targetColumn == TotalColumns)
targetColumn = RandomStart;
addToPattern(pattern, targetColumn);
return pattern;
}
if ((convertType & PatternType.ReverseStair) > 0)
{
// Generate a new pattern by placing on the previous column, cycling back to the end if there is no "previous"
int targetColumn = lastColumn - 1;
if (targetColumn == RandomStart - 1)
targetColumn = TotalColumns - 1;
addToPattern(pattern, targetColumn);
return pattern;
}
}
if ((convertType & PatternType.KeepSingle) > 0)
return pattern = generateRandomNotes(1);
if ((convertType & PatternType.Mirror) > 0)
{
if (ConversionDifficulty > 6.5)
return pattern = generateRandomPatternWithMirrored(0.12, 0.38, 0.12);
if (ConversionDifficulty > 4)
return pattern = generateRandomPatternWithMirrored(0.12, 0.17, 0);
return pattern = generateRandomPatternWithMirrored(0.12, 0, 0);
}
if ((convertType & PatternType.Mirror) > 0)
{
if (ConversionDifficulty > 6.5) if (ConversionDifficulty > 6.5)
return generateRandomPatternWithMirrored(0.12, 0.38, 0.12); {
if ((convertType & PatternType.LowProbability) > 0)
return pattern = generateRandomPattern(0.78, 0.42, 0, 0);
return pattern = generateRandomPattern(1, 0.62, 0, 0);
}
if (ConversionDifficulty > 4) if (ConversionDifficulty > 4)
return generateRandomPatternWithMirrored(0.12, 0.17, 0); {
return generateRandomPatternWithMirrored(0.12, 0, 0); if ((convertType & PatternType.LowProbability) > 0)
} return pattern = generateRandomPattern(0.35, 0.08, 0, 0);
return pattern = generateRandomPattern(0.52, 0.15, 0, 0);
}
if (ConversionDifficulty > 6.5) if (ConversionDifficulty > 2)
{
if ((convertType & PatternType.LowProbability) > 0)
return pattern = generateRandomPattern(0.18, 0, 0, 0);
return pattern = generateRandomPattern(0.45, 0, 0, 0);
}
return pattern = generateRandomPattern(0, 0, 0, 0);
}
finally
{ {
if ((convertType & PatternType.LowProbability) > 0) foreach (var obj in pattern.HitObjects)
return generateRandomPattern(0.78, 0.42, 0, 0); {
return generateRandomPattern(1, 0.62, 0, 0); if ((convertType & PatternType.Stair) > 0 && obj.Column == TotalColumns - 1)
StairType = PatternType.ReverseStair;
if ((convertType & PatternType.ReverseStair) > 0 && obj.Column == RandomStart)
StairType = PatternType.Stair;
}
} }
if (ConversionDifficulty > 4)
{
if ((convertType & PatternType.LowProbability) > 0)
return generateRandomPattern(0.35, 0.08, 0, 0);
return generateRandomPattern(0.52, 0.15, 0, 0);
}
if (ConversionDifficulty > 2)
{
if ((convertType & PatternType.LowProbability) > 0)
return generateRandomPattern(0.18, 0, 0, 0);
return generateRandomPattern(0.45, 0, 0, 0);
}
return generateRandomPattern(0, 0, 0, 0);
} }
/// <summary> /// <summary>

View File

@ -103,17 +103,14 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
HitObject lastObject = OriginalBeatmap.HitObjects.LastOrDefault(); HitObject lastObject = OriginalBeatmap.HitObjects.LastOrDefault();
HitObject firstObject = OriginalBeatmap.HitObjects.FirstOrDefault(); HitObject firstObject = OriginalBeatmap.HitObjects.FirstOrDefault();
double drainTime = (lastObject?.StartTime ?? 0) - (firstObject?.StartTime ?? 0); // Drain time in seconds
drainTime -= OriginalBeatmap.TotalBreakTime; int drainTime = (int)(((lastObject?.StartTime ?? 0) - (firstObject?.StartTime ?? 0) - OriginalBeatmap.TotalBreakTime) / 1000);
if (drainTime == 0) if (drainTime == 0)
drainTime = 10000000; drainTime = 10000;
// We need this in seconds
drainTime /= 1000;
BeatmapDifficulty difficulty = OriginalBeatmap.BeatmapInfo.BaseDifficulty; BeatmapDifficulty difficulty = OriginalBeatmap.BeatmapInfo.BaseDifficulty;
conversionDifficulty = ((difficulty.DrainRate + MathHelper.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + OriginalBeatmap.HitObjects.Count() / drainTime * 9f) / 38f * 5f / 1.15; conversionDifficulty = ((difficulty.DrainRate + MathHelper.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + (double)OriginalBeatmap.HitObjects.Count() / drainTime * 9f) / 38f * 5f / 1.15;
conversionDifficulty = Math.Min(conversionDifficulty.Value, 12); conversionDifficulty = Math.Min(conversionDifficulty.Value, 12);
return conversionDifficulty.Value; return conversionDifficulty.Value;

View File

@ -2,6 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using System.Collections.Generic;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns
@ -42,9 +43,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns
} }
/// <summary> /// <summary>
/// Generates the pattern for <see cref="HitObject"/>, filled with hit objects. /// Generates the patterns for <see cref="HitObject"/>, each filled with hit objects.
/// </summary> /// </summary>
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns> /// <returns>The <see cref="Pattern"/>s containing the hit objects.</returns>
public abstract Pattern Generate(); public abstract IEnumerable<Pattern> Generate();
} }
} }

View File

@ -15,11 +15,15 @@ namespace osu.Game.Rulesets.Mania.MathUtils
private const uint y = 842502087; private const uint y = 842502087;
private const uint z = 3579807591; private const uint z = 3579807591;
private const uint w = 273326509; private const uint w = 273326509;
private uint _x, _y = y, _z = z, _w = w;
internal uint X { get; private set; }
internal uint Y { get; private set; } = y;
internal uint Z { get; private set; } = z;
internal uint W { get; private set; } = w;
public FastRandom(int seed) public FastRandom(int seed)
{ {
_x = (uint)seed; X = (uint)seed;
} }
public FastRandom() public FastRandom()
@ -33,11 +37,11 @@ namespace osu.Game.Rulesets.Mania.MathUtils
/// <returns>The random value.</returns> /// <returns>The random value.</returns>
public uint NextUInt() public uint NextUInt()
{ {
uint t = _x ^ _x << 11; uint t = X ^ X << 11;
_x = _y; X = Y;
_y = _z; Y = Z;
_z = _w; Z = W;
return _w = _w ^ _w >> 19 ^ t ^ t >> 8; return W = W ^ W >> 19 ^ t ^ t >> 8;
} }
/// <summary> /// <summary>

View File

@ -1,103 +1,132 @@
{ {
"Mappings": [{ "Mappings": [{
"StartTime": 500, "RandomW": 2659373485,
"Objects": [{ "RandomX": 3579807591,
"StartTime": 500, "RandomY": 273326509,
"EndTime": 2500, "RandomZ": 272969173,
"Column": 0 "StartTime": 500.0,
}, "Objects": [{
{ "StartTime": 500.0,
"StartTime": 1500, "EndTime": 2500.0,
"EndTime": 2500, "Column": 0
"Column": 1 }, {
} "StartTime": 1500.0,
] "EndTime": 2500.0,
}, "Column": 1
{ }]
"StartTime": 3000, }, {
"Objects": [{ "RandomW": 3083803045,
"StartTime": 3000, "RandomX": 273326509,
"EndTime": 4000, "RandomY": 272969173,
"Column": 2 "RandomZ": 2659373485,
}] "StartTime": 3000.0,
}, "Objects": [{
{ "StartTime": 3000.0,
"StartTime": 4500, "EndTime": 4000.0,
"Objects": [{ "Column": 2
"StartTime": 4500, }]
"EndTime": 5500, }, {
"Column": 4 "RandomW": 4073554232,
}] "RandomX": 272969173,
}, "RandomY": 2659373485,
{ "RandomZ": 3083803045,
"StartTime": 6000, "StartTime": 4500.0,
"Objects": [{ "Objects": [{
"StartTime": 6000, "StartTime": 4500.0,
"EndTime": 6500, "EndTime": 5500.0,
"Column": 2 "Column": 4
}] }]
}, }, {
{ "RandomW": 3420401969,
"StartTime": 7000, "RandomX": 2659373485,
"Objects": [{ "RandomY": 3083803045,
"StartTime": 7000, "RandomZ": 4073554232,
"EndTime": 8000, "StartTime": 6000.0,
"Column": 2 "Objects": [{
}] "StartTime": 6000.0,
}, "EndTime": 6500.0,
{ "Column": 2
"StartTime": 8500, }]
"Objects": [{ }, {
"StartTime": 8500, "RandomW": 1129881182,
"EndTime": 11000, "RandomX": 3083803045,
"Column": 0 "RandomY": 4073554232,
}] "RandomZ": 3420401969,
}, "StartTime": 7000.0,
{ "Objects": [{
"StartTime": 11500, "StartTime": 7000.0,
"Objects": [{ "EndTime": 8000.0,
"StartTime": 11500, "Column": 2
"EndTime": 12000, }]
"Column": 1 }, {
}] "RandomW": 315568458,
}, "RandomX": 3420401969,
{ "RandomY": 1129881182,
"StartTime": 12500, "RandomZ": 2358617505,
"Objects": [{ "StartTime": 8500.0,
"StartTime": 12500, "Objects": [{
"EndTime": 16500, "StartTime": 8500.0,
"Column": 4 "EndTime": 11000.0,
}] "Column": 0
}, }]
{ }, {
"StartTime": 17000, "RandomW": 548134043,
"Objects": [{ "RandomX": 1129881182,
"StartTime": 17000, "RandomY": 2358617505,
"EndTime": 18000, "RandomZ": 315568458,
"Column": 2 "StartTime": 11500.0,
}] "Objects": [{
}, "StartTime": 11500.0,
{ "EndTime": 12000.0,
"StartTime": 18500, "Column": 1
"Objects": [{ }]
"StartTime": 18500, }, {
"EndTime": 19450, "RandomW": 3979422122,
"Column": 0 "RandomX": 548134043,
}] "RandomY": 2810584254,
}, "RandomZ": 2250186050,
{ "StartTime": 12500.0,
"StartTime": 19875, "Objects": [{
"Objects": [{ "StartTime": 12500.0,
"StartTime": 19875, "EndTime": 16500.0,
"EndTime": 23875, "Column": 4
"Column": 1 }]
}, }, {
{ "RandomW": 2466283411,
"StartTime": 19875, "RandomX": 2810584254,
"EndTime": 23875, "RandomY": 2250186050,
"Column": 0 "RandomZ": 3979422122,
} "StartTime": 17000.0,
] "Objects": [{
} "StartTime": 17000.0,
] "EndTime": 18000.0,
"Column": 2
}]
}, {
"RandomW": 83157665,
"RandomX": 2250186050,
"RandomY": 3979422122,
"RandomZ": 2466283411,
"StartTime": 18500.0,
"Objects": [{
"StartTime": 18500.0,
"EndTime": 19450.0,
"Column": 0
}]
}, {
"RandomW": 2383087700,
"RandomX": 83157665,
"RandomY": 2055150192,
"RandomZ": 510071020,
"StartTime": 19875.0,
"Objects": [{
"StartTime": 19875.0,
"EndTime": 23875.0,
"Column": 1
}, {
"StartTime": 19875.0,
"EndTime": 23875.0,
"Column": 0
}]
}]
} }

View File

@ -93,9 +93,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
base.AccentColour = value; base.AccentColour = value;
Body.AccentColour = AccentColour; Body.AccentColour = AccentColour;
Ball.AccentColour = AccentColour; Ball.AccentColour = AccentColour;
if (HasNestedHitObjects)
foreach (var drawableHitObject in NestedHitObjects) foreach (var drawableHitObject in NestedHitObjects)
drawableHitObject.AccentColour = AccentColour; drawableHitObject.AccentColour = AccentColour;
} }
} }
@ -136,7 +136,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
if (!userTriggered && Time.Current >= slider.EndTime) if (!userTriggered && Time.Current >= slider.EndTime)
{ {
var judgementsCount = NestedHitObjects.Count; var judgementsCount = NestedHitObjects.Count();
var judgementsHit = NestedHitObjects.Count(h => h.IsHit); var judgementsHit = NestedHitObjects.Count(h => h.IsHit);
var hitFraction = (double)judgementsHit / judgementsCount; var hitFraction = (double)judgementsHit / judgementsCount;

View File

@ -11,6 +11,7 @@ using osu.Game.Audio;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Formats;
using osu.Game.Beatmaps.Timing; using osu.Game.Beatmaps.Timing;
using osu.Game.Rulesets.Objects;
using osu.Game.Skinning; using osu.Game.Skinning;
namespace osu.Game.Tests.Beatmaps.Formats namespace osu.Game.Tests.Beatmaps.Formats
@ -211,5 +212,41 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.IsTrue(hitObjects[1].Samples.Any(s => s.Name == SampleInfo.HIT_CLAP)); Assert.IsTrue(hitObjects[1].Samples.Any(s => s.Name == SampleInfo.HIT_CLAP));
} }
} }
[Test]
public void TestDecodeCustomSamples()
{
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
using (var resStream = Resource.OpenResource("custom-samples.osu"))
using (var stream = new StreamReader(resStream))
{
var hitObjects = decoder.Decode(stream).HitObjects;
Assert.AreEqual("hitnormal", getTestableSampleInfo(hitObjects[0]).Name);
Assert.AreEqual("hitnormal", getTestableSampleInfo(hitObjects[1]).Name);
Assert.AreEqual("hitnormal2", getTestableSampleInfo(hitObjects[2]).Name);
Assert.AreEqual("hitnormal", getTestableSampleInfo(hitObjects[3]).Name);
}
SampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(new SampleInfo { Name = "hitnormal" });
}
[Test]
public void TestDecodeCustomHitObjectSamples()
{
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
using (var resStream = Resource.OpenResource("custom-hitobject-samples.osu"))
using (var stream = new StreamReader(resStream))
{
var hitObjects = decoder.Decode(stream).HitObjects;
Assert.AreEqual("hit_1.wav", hitObjects[0].Samples[0].LookupNames.First());
Assert.AreEqual("hit_2.wav", hitObjects[1].Samples[0].LookupNames.First());
Assert.AreEqual("hitnormal2", getTestableSampleInfo(hitObjects[2]).Name);
Assert.AreEqual("hit_1.wav", hitObjects[3].Samples[0].LookupNames.First());
}
SampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(new SampleInfo { Name = "hitnormal" });
}
} }
} }

View File

@ -118,7 +118,11 @@ namespace osu.Game.Tests.Beatmaps.Formats
public void TestParity(string beatmap) public void TestParity(string beatmap)
{ {
var legacy = decode(beatmap, out Beatmap json); var legacy = decode(beatmap, out Beatmap json);
json.WithDeepEqual(legacy).IgnoreProperty(r => r.DeclaringType == typeof(HitWindows)).Assert(); json.WithDeepEqual(legacy)
.IgnoreProperty(r => r.DeclaringType == typeof(HitWindows)
// Todo: CustomSampleBank shouldn't exist going forward, we need a conversion mechanism
|| r.Name == nameof(LegacyDecoder<Beatmap>.LegacySampleControlPoint.CustomSampleBank))
.Assert();
} }
/// <summary> /// <summary>

View File

@ -0,0 +1,16 @@
osu file format v14
[General]
SampleSet: Normal
[TimingPoints]
2170,468.75,4,1,0,40,1,0
2638,-100,4,1,1,40,0,0
3107,-100,4,1,2,40,0,0
3576,-100,4,1,0,40,0,0
[HitObjects]
255,193,2170,1,0,0:0:0:0:hit_1.wav
256,191,2638,5,0,0:0:0:0:hit_2.wav
255,193,3107,1,0,0:0:0:0:
256,191,3576,1,0,0:0:0:0:hit_1.wav

View File

@ -0,0 +1,16 @@
osu file format v14
[General]
SampleSet: Normal
[TimingPoints]
2170,468.75,4,1,0,40,1,0
2638,-100,4,1,1,40,0,0
3107,-100,4,1,2,40,0,0
3576,-100,4,1,0,40,0,0
[HitObjects]
255,193,2170,1,0,0:0:0:0:
256,191,2638,5,0,0:0:0:0:
255,193,3107,1,0,0:0:0:0:
256,191,3576,1,0,0:0:0:0:

View File

@ -2,6 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using System.Collections.Generic;
namespace osu.Game.Audio namespace osu.Game.Audio
{ {
@ -32,5 +33,21 @@ namespace osu.Game.Audio
/// The sample volume. /// The sample volume.
/// </summary> /// </summary>
public int Volume; public int Volume;
/// <summary>
/// Retrieve all possible filenames that can be used as a source, returned in order of preference (highest first).
/// </summary>
public virtual IEnumerable<string> LookupNames
{
get
{
if (!string.IsNullOrEmpty(Namespace))
yield return $"{Namespace}/{Bank}-{Name}";
yield return $"{Bank}-{Name}"; // Without namespace as a fallback even when we have a namespace
}
}
public SampleInfo Clone() => (SampleInfo)MemberwiseClone();
} }
} }

View File

@ -14,6 +14,15 @@ namespace osu.Game.Beatmaps.ControlPoints
public int CompareTo(ControlPoint other) => Time.CompareTo(other.Time); public int CompareTo(ControlPoint other) => Time.CompareTo(other.Time);
public bool Equals(ControlPoint other) => Time.Equals(other?.Time); /// <summary>
/// Whether this <see cref="ControlPoint"/> provides the same parametric changes as another <see cref="ControlPoint"/>.
/// Basically an equality check without considering the <see cref="Time"/>.
/// </summary>
/// <param name="other">The <see cref="ControlPoint"/> to compare to.</param>
/// <returns>Whether this <see cref="ControlPoint"/> is equivalent to <paramref name="other"/>.</returns>
public virtual bool EquivalentTo(ControlPoint other) => true;
public bool Equals(ControlPoint other)
=> EquivalentTo(other) && Time.Equals(other?.Time);
} }
} }

View File

@ -17,5 +17,10 @@ namespace osu.Game.Beatmaps.ControlPoints
} }
private double speedMultiplier = 1; private double speedMultiplier = 1;
public override bool EquivalentTo(ControlPoint other)
=> base.EquivalentTo(other)
&& other is DifficultyControlPoint difficulty
&& SpeedMultiplier.Equals(difficulty.SpeedMultiplier);
} }
} }

View File

@ -14,5 +14,11 @@ namespace osu.Game.Beatmaps.ControlPoints
/// Whether the first bar line of this control point is ignored. /// Whether the first bar line of this control point is ignored.
/// </summary> /// </summary>
public bool OmitFirstBarLine; public bool OmitFirstBarLine;
public override bool EquivalentTo(ControlPoint other)
=> base.EquivalentTo(other)
&& other is EffectControlPoint effect
&& KiaiMode.Equals(effect.KiaiMode)
&& OmitFirstBarLine.Equals(effect.OmitFirstBarLine);
} }
} }

View File

@ -30,5 +30,25 @@ namespace osu.Game.Beatmaps.ControlPoints
Name = sampleName, Name = sampleName,
Volume = SampleVolume, Volume = SampleVolume,
}; };
/// <summary>
/// Applies <see cref="SampleBank"/> and <see cref="SampleVolume"/> to a <see cref="SampleInfo"/> if necessary, returning the modified <see cref="SampleInfo"/>.
/// </summary>
/// <param name="sampleInfo">The <see cref="SampleInfo"/>. This will not be modified.</param>
/// <returns>The modified <see cref="SampleInfo"/>. This does not share a reference with <paramref name="sampleInfo"/>.</returns>
public virtual SampleInfo ApplyTo(SampleInfo sampleInfo)
{
var newSampleInfo = sampleInfo.Clone();
newSampleInfo.Bank = sampleInfo.Bank ?? SampleBank;
newSampleInfo.Name = sampleInfo.Name;
newSampleInfo.Volume = sampleInfo.Volume > 0 ? sampleInfo.Volume : SampleVolume;
return newSampleInfo;
}
public override bool EquivalentTo(ControlPoint other)
=> base.EquivalentTo(other)
&& other is SampleControlPoint sample
&& SampleBank.Equals(sample.SampleBank)
&& SampleVolume.Equals(sample.SampleVolume);
} }
} }

View File

@ -23,5 +23,11 @@ namespace osu.Game.Beatmaps.ControlPoints
} }
private double beatLength = 1000; private double beatLength = 1000;
public override bool EquivalentTo(ControlPoint other)
=> base.EquivalentTo(other)
&& other is TimingControlPoint timing
&& TimeSignature.Equals(timing.TimeSignature)
&& BeatLength.Equals(timing.BeatLength);
} }
} }

View File

@ -289,9 +289,9 @@ namespace osu.Game.Beatmaps.Formats
if (split.Length >= 4) if (split.Length >= 4)
sampleSet = (LegacySampleBank)int.Parse(split[3]); sampleSet = (LegacySampleBank)int.Parse(split[3]);
//SampleBank sampleBank = SampleBank.Default; int customSampleBank = 0;
//if (split.Length >= 5) if (split.Length >= 5)
// sampleBank = (SampleBank)int.Parse(split[4]); customSampleBank = int.Parse(split[4]);
int sampleVolume = defaultSampleVolume; int sampleVolume = defaultSampleVolume;
if (split.Length >= 6) if (split.Length >= 6)
@ -314,13 +314,9 @@ namespace osu.Game.Beatmaps.Formats
if (stringSampleSet == @"none") if (stringSampleSet == @"none")
stringSampleSet = @"normal"; stringSampleSet = @"normal";
DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(time);
SampleControlPoint samplePoint = beatmap.ControlPointInfo.SamplePointAt(time);
EffectControlPoint effectPoint = beatmap.ControlPointInfo.EffectPointAt(time);
if (timingChange) if (timingChange)
{ {
beatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint handleTimingControlPoint(new TimingControlPoint
{ {
Time = time, Time = time,
BeatLength = beatLength, BeatLength = beatLength,
@ -328,41 +324,68 @@ namespace osu.Game.Beatmaps.Formats
}); });
} }
if (speedMultiplier != difficultyPoint.SpeedMultiplier) handleDifficultyControlPoint(new DifficultyControlPoint
{ {
beatmap.ControlPointInfo.DifficultyPoints.RemoveAll(x => x.Time == time); Time = time,
beatmap.ControlPointInfo.DifficultyPoints.Add(new DifficultyControlPoint SpeedMultiplier = speedMultiplier
{ });
Time = time,
SpeedMultiplier = speedMultiplier
});
}
if (stringSampleSet != samplePoint.SampleBank || sampleVolume != samplePoint.SampleVolume) handleEffectControlPoint(new EffectControlPoint
{ {
beatmap.ControlPointInfo.SamplePoints.Add(new SampleControlPoint Time = time,
{ KiaiMode = kiaiMode,
Time = time, OmitFirstBarLine = omitFirstBarSignature
SampleBank = stringSampleSet, });
SampleVolume = sampleVolume
});
}
if (kiaiMode != effectPoint.KiaiMode || omitFirstBarSignature != effectPoint.OmitFirstBarLine) handleSampleControlPoint(new LegacySampleControlPoint
{ {
beatmap.ControlPointInfo.EffectPoints.Add(new EffectControlPoint Time = time,
{ SampleBank = stringSampleSet,
Time = time, SampleVolume = sampleVolume,
KiaiMode = kiaiMode, CustomSampleBank = customSampleBank
OmitFirstBarLine = omitFirstBarSignature });
});
}
} }
catch (FormatException e) catch (FormatException e)
{ {
} }
} }
private void handleTimingControlPoint(TimingControlPoint newPoint)
{
beatmap.ControlPointInfo.TimingPoints.Add(newPoint);
}
private void handleDifficultyControlPoint(DifficultyControlPoint newPoint)
{
var existing = beatmap.ControlPointInfo.DifficultyPointAt(newPoint.Time);
if (newPoint.EquivalentTo(existing))
return;
beatmap.ControlPointInfo.DifficultyPoints.RemoveAll(x => x.Time == newPoint.Time);
beatmap.ControlPointInfo.DifficultyPoints.Add(newPoint);
}
private void handleEffectControlPoint(EffectControlPoint newPoint)
{
var existing = beatmap.ControlPointInfo.EffectPointAt(newPoint.Time);
if (newPoint.EquivalentTo(existing))
return;
beatmap.ControlPointInfo.EffectPoints.Add(newPoint);
}
private void handleSampleControlPoint(SampleControlPoint newPoint)
{
var existing = beatmap.ControlPointInfo.SamplePointAt(newPoint.Time);
if (newPoint.EquivalentTo(existing))
return;
beatmap.ControlPointInfo.SamplePoints.Add(newPoint);
}
private void handleHitObject(string line) private void handleHitObject(string line)
{ {
// If the ruleset wasn't specified, assume the osu!standard ruleset. // If the ruleset wasn't specified, assume the osu!standard ruleset.

View File

@ -5,6 +5,8 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Game.Audio;
using osu.Game.Beatmaps.ControlPoints;
using OpenTK.Graphics; using OpenTK.Graphics;
namespace osu.Game.Beatmaps.Formats namespace osu.Game.Beatmaps.Formats
@ -167,5 +169,25 @@ namespace osu.Game.Beatmaps.Formats
Pass = 2, Pass = 2,
Foreground = 3 Foreground = 3
} }
internal class LegacySampleControlPoint : SampleControlPoint
{
public int CustomSampleBank;
public override SampleInfo ApplyTo(SampleInfo sampleInfo)
{
var baseInfo = base.ApplyTo(sampleInfo);
if (CustomSampleBank > 1)
baseInfo.Name += CustomSampleBank;
return baseInfo;
}
public override bool EquivalentTo(ControlPoint other)
=> base.EquivalentTo(other)
&& other is LegacySampleControlPoint legacy
&& CustomSampleBank == legacy.CustomSampleBank;
}
} }
} }

View File

@ -67,7 +67,7 @@ namespace osu.Game.Graphics.Containers
return base.OnClick(state); return base.OnClick(state);
} }
public bool OnPressed(GlobalAction action) public virtual bool OnPressed(GlobalAction action)
{ {
if (action == GlobalAction.Back) if (action == GlobalAction.Back)
{ {

View File

@ -22,7 +22,9 @@ namespace osu.Game.Graphics.Cursor
private readonly IBindable<bool> screenshotCursorVisibility = new Bindable<bool>(true); private readonly IBindable<bool> screenshotCursorVisibility = new Bindable<bool>(true);
public override bool IsPresent => screenshotCursorVisibility.Value && base.IsPresent; public override bool IsPresent => screenshotCursorVisibility.Value && base.IsPresent;
protected override Drawable CreateCursor() => new Cursor(); protected override Drawable CreateCursor() => activeCursor = new Cursor();
private Cursor activeCursor;
private Bindable<bool> cursorRotate; private Bindable<bool> cursorRotate;
private DragRotationState dragRotationState; private DragRotationState dragRotationState;
@ -54,12 +56,12 @@ namespace osu.Game.Graphics.Cursor
float degrees = (float)MathHelper.RadiansToDegrees(Math.Atan2(-offset.X, offset.Y)) + 24.3f; float degrees = (float)MathHelper.RadiansToDegrees(Math.Atan2(-offset.X, offset.Y)) + 24.3f;
// Always rotate in the direction of least distance // Always rotate in the direction of least distance
float diff = (degrees - ActiveCursor.Rotation) % 360; float diff = (degrees - activeCursor.Rotation) % 360;
if (diff < -180) diff += 360; if (diff < -180) diff += 360;
if (diff > 180) diff -= 360; if (diff > 180) diff -= 360;
degrees = ActiveCursor.Rotation + diff; degrees = activeCursor.Rotation + diff;
ActiveCursor.RotateTo(degrees, 600, Easing.OutQuint); activeCursor.RotateTo(degrees, 600, Easing.OutQuint);
} }
} }
@ -68,11 +70,15 @@ namespace osu.Game.Graphics.Cursor
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
{ {
ActiveCursor.Scale = new Vector2(1); // only trigger animation for main mouse buttons
ActiveCursor.ScaleTo(0.90f, 800, Easing.OutQuint); if (args.Button <= MouseButton.Right)
{
activeCursor.Scale = new Vector2(1);
activeCursor.ScaleTo(0.90f, 800, Easing.OutQuint);
((Cursor)ActiveCursor).AdditiveLayer.Alpha = 0; activeCursor.AdditiveLayer.Alpha = 0;
((Cursor)ActiveCursor).AdditiveLayer.FadeInFromZero(800, Easing.OutQuint); activeCursor.AdditiveLayer.FadeInFromZero(800, Easing.OutQuint);
}
if (args.Button == MouseButton.Left && cursorRotate) if (args.Button == MouseButton.Left && cursorRotate)
{ {
@ -86,36 +92,29 @@ namespace osu.Game.Graphics.Cursor
{ {
if (!state.Mouse.HasMainButtonPressed) if (!state.Mouse.HasMainButtonPressed)
{ {
((Cursor)ActiveCursor).AdditiveLayer.FadeOut(500, Easing.OutQuint); activeCursor.AdditiveLayer.FadeOutFromOne(500, Easing.OutQuint);
ActiveCursor.ScaleTo(1, 500, Easing.OutElastic); activeCursor.ScaleTo(1, 500, Easing.OutElastic);
} }
if (args.Button == MouseButton.Left) if (args.Button == MouseButton.Left)
{ {
if (dragRotationState == DragRotationState.Rotating) if (dragRotationState == DragRotationState.Rotating)
ActiveCursor.RotateTo(0, 600 * (1 + Math.Abs(ActiveCursor.Rotation / 720)), Easing.OutElasticHalf); activeCursor.RotateTo(0, 600 * (1 + Math.Abs(activeCursor.Rotation / 720)), Easing.OutElasticHalf);
dragRotationState = DragRotationState.NotDragging; dragRotationState = DragRotationState.NotDragging;
} }
return base.OnMouseUp(state, args); return base.OnMouseUp(state, args);
} }
protected override bool OnClick(InputState state)
{
((Cursor)ActiveCursor).AdditiveLayer.FadeOutFromOne(500, Easing.OutQuint);
return base.OnClick(state);
}
protected override void PopIn() protected override void PopIn()
{ {
ActiveCursor.FadeTo(1, 250, Easing.OutQuint); activeCursor.FadeTo(1, 250, Easing.OutQuint);
ActiveCursor.ScaleTo(1, 400, Easing.OutQuint); activeCursor.ScaleTo(1, 400, Easing.OutQuint);
} }
protected override void PopOut() protected override void PopOut()
{ {
ActiveCursor.FadeTo(0, 250, Easing.OutQuint); activeCursor.FadeTo(0, 250, Easing.OutQuint);
ActiveCursor.ScaleTo(0.6f, 250, Easing.In); activeCursor.ScaleTo(0.6f, 250, Easing.In);
} }
public class Cursor : Container public class Cursor : Container

View File

@ -39,7 +39,10 @@ namespace osu.Game.Input.Bindings
new KeyBinding(InputKey.F4, GlobalAction.ToggleMute), new KeyBinding(InputKey.F4, GlobalAction.ToggleMute),
new KeyBinding(InputKey.Escape, GlobalAction.Back), new KeyBinding(InputKey.Escape, GlobalAction.Back),
new KeyBinding(InputKey.MouseButton1, GlobalAction.Back) new KeyBinding(InputKey.MouseButton1, GlobalAction.Back),
new KeyBinding(InputKey.Space, GlobalAction.Select),
new KeyBinding(InputKey.Enter, GlobalAction.Select),
}; };
public IEnumerable<KeyBinding> InGameKeyBindings => new[] public IEnumerable<KeyBinding> InGameKeyBindings => new[]
@ -86,7 +89,7 @@ namespace osu.Game.Input.Bindings
[Description("Toggle gameplay mouse buttons")] [Description("Toggle gameplay mouse buttons")]
ToggleGameplayMouseButtons, ToggleGameplayMouseButtons,
[Description("Go back")] [Description("Back")]
Back, Back,
[Description("Increase scroll speed")] [Description("Increase scroll speed")]
@ -94,5 +97,8 @@ namespace osu.Game.Input.Bindings
[Description("Decrease scroll speed")] [Description("Decrease scroll speed")]
DecreaseScrollSpeed, DecreaseScrollSpeed,
[Description("Select")]
Select,
} }
} }

View File

@ -49,7 +49,7 @@ namespace osu.Game.Online.API.Requests.Responses
private DateTimeOffset submitted { get; set; } private DateTimeOffset submitted { get; set; }
[JsonProperty(@"ranked_date")] [JsonProperty(@"ranked_date")]
private DateTimeOffset ranked { get; set; } private DateTimeOffset? ranked { get; set; }
[JsonProperty(@"last_updated")] [JsonProperty(@"last_updated")]
private DateTimeOffset lastUpdated { get; set; } private DateTimeOffset lastUpdated { get; set; }

View File

@ -244,7 +244,8 @@ namespace osu.Game
new VolumeControlReceptor new VolumeControlReceptor
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
ActionRequested = action => volume.Adjust(action) ActionRequested = action => volume.Adjust(action),
ScrollActionRequested = (action, amount, isPrecise) => volume.Adjust(action, amount, isPrecise),
}, },
mainContent = new Container { RelativeSizeAxes = Axes.Both }, mainContent = new Container { RelativeSizeAxes = Axes.Both },
overlayContent = new Container { RelativeSizeAxes = Axes.Both, Depth = float.MinValue }, overlayContent = new Container { RelativeSizeAxes = Axes.Both, Depth = float.MinValue },

View File

@ -13,6 +13,7 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Backgrounds;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Input.Bindings;
using OpenTK; using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
using OpenTK.Input; using OpenTK.Input;
@ -69,10 +70,10 @@ namespace osu.Game.Overlays.Dialog
{ {
if (actionInvoked) return; if (actionInvoked) return;
Hide();
actionInvoked = true; actionInvoked = true;
action?.Invoke(); action?.Invoke();
Hide();
}; };
} }
} }
@ -192,16 +193,22 @@ namespace osu.Game.Overlays.Dialog
}; };
} }
public override bool OnPressed(GlobalAction action)
{
switch (action)
{
case GlobalAction.Select:
Buttons.OfType<PopupDialogOkButton>().FirstOrDefault()?.TriggerOnClick();
return true;
}
return base.OnPressed(action);
}
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
{ {
if (args.Repeat) return false; if (args.Repeat) return false;
if (args.Key == Key.Enter || args.Key == Key.KeypadEnter)
{
Buttons.OfType<PopupDialogOkButton>().FirstOrDefault()?.TriggerOnClick();
return true;
}
// press button at number if 1-9 on number row or keypad are pressed // press button at number if 1-9 on number row or keypad are pressed
var k = args.Key; var k = args.Key;
if (k >= Key.Number1 && k <= Key.Number9) if (k >= Key.Number1 && k <= Key.Number9)

View File

@ -186,7 +186,7 @@ namespace osu.Game.Overlays.KeyBinding
{ {
if (bindTarget.IsHovered) if (bindTarget.IsHovered)
{ {
bindTarget.UpdateKeyCombination(new KeyCombination(KeyCombination.FromInputState(state).Keys.Append(state.Mouse.ScrollDelta.Y > 0 ? InputKey.MouseWheelUp : InputKey.MouseWheelDown))); bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(state, state.Mouse.ScrollDelta));
finalise(); finalise();
return true; return true;
} }

View File

@ -9,11 +9,13 @@ using osu.Game.Input.Bindings;
namespace osu.Game.Overlays.Volume namespace osu.Game.Overlays.Volume
{ {
public class VolumeControlReceptor : Container, IKeyBindingHandler<GlobalAction>, IHandleGlobalInput public class VolumeControlReceptor : Container, IScrollBindingHandler<GlobalAction>, IHandleGlobalInput
{ {
public Func<GlobalAction, bool> ActionRequested; public Func<GlobalAction, bool> ActionRequested;
public Func<GlobalAction, float, bool, bool> ScrollActionRequested;
public bool OnPressed(GlobalAction action) => ActionRequested?.Invoke(action) ?? false; public bool OnPressed(GlobalAction action) => ActionRequested?.Invoke(action) ?? false;
public bool OnScroll(GlobalAction action, float amount, bool isPrecise) => ScrollActionRequested?.Invoke(action, amount, isPrecise) ?? false;
public bool OnReleased(GlobalAction action) => false; public bool OnReleased(GlobalAction action) => false;
} }
} }

View File

@ -12,17 +12,15 @@ using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Framework.Input.Bindings;
using osu.Framework.MathUtils; using osu.Framework.MathUtils;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Input.Bindings;
using OpenTK; using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
namespace osu.Game.Overlays.Volume namespace osu.Game.Overlays.Volume
{ {
public class VolumeMeter : Container, IKeyBindingHandler<GlobalAction> public class VolumeMeter : Container
{ {
private CircularProgress volumeCircle; private CircularProgress volumeCircle;
private CircularProgress volumeCircleGlow; private CircularProgress volumeCircleGlow;
@ -226,59 +224,27 @@ namespace osu.Game.Overlays.Volume
private const float adjust_step = 0.05f; private const float adjust_step = 0.05f;
public void Increase() => adjust(1); public void Increase(double amount = 1, bool isPrecise = false) => adjust(amount, isPrecise);
public void Decrease() => adjust(-1); public void Decrease(double amount = 1, bool isPrecise = false) => adjust(-amount, isPrecise);
private void adjust(int direction)
{
float amount = adjust_step * direction;
// handle the case where the OnPressed action was actually a mouse wheel.
// this allows for precise wheel handling.
var state = GetContainingInputManager().CurrentState;
if (state.Mouse?.ScrollDelta.Y != 0)
{
OnScroll(state);
return;
}
Volume += amount;
}
public bool OnPressed(GlobalAction action)
{
if (!IsHovered) return false;
switch (action)
{
case GlobalAction.DecreaseVolume:
Decrease();
return true;
case GlobalAction.IncreaseVolume:
Increase();
return true;
}
return false;
}
// because volume precision is set to 0.01, this local is required to keep track of more precise adjustments and only apply when possible. // because volume precision is set to 0.01, this local is required to keep track of more precise adjustments and only apply when possible.
private double scrollAmount; private double adjustAccumulator;
private void adjust(double delta, bool isPrecise)
{
adjustAccumulator += delta * adjust_step * (isPrecise ? 0.1 : 1);
if (Math.Abs(adjustAccumulator) < Bindable.Precision)
return;
Volume += adjustAccumulator;
adjustAccumulator = 0;
}
protected override bool OnScroll(InputState state) protected override bool OnScroll(InputState state)
{ {
scrollAmount += adjust_step * state.Mouse.ScrollDelta.Y * (state.Mouse.HasPreciseScroll ? 0.1f : 1); adjust(state.Mouse.ScrollDelta.Y, state.Mouse.HasPreciseScroll);
if (Math.Abs(scrollAmount) < Bindable.Precision)
return true;
Volume += scrollAmount;
scrollAmount = 0;
return true; return true;
} }
public bool OnReleased(GlobalAction action) => false;
private const float transition_length = 500; private const float transition_length = 500;
protected override bool OnHover(InputState state) protected override bool OnHover(InputState state)

View File

@ -93,7 +93,7 @@ namespace osu.Game.Overlays
muteButton.Current.ValueChanged += _ => Show(); muteButton.Current.ValueChanged += _ => Show();
} }
public bool Adjust(GlobalAction action) public bool Adjust(GlobalAction action, float amount = 1, bool isPrecise = false)
{ {
if (!IsLoaded) return false; if (!IsLoaded) return false;
@ -103,13 +103,13 @@ namespace osu.Game.Overlays
if (State == Visibility.Hidden) if (State == Visibility.Hidden)
Show(); Show();
else else
volumeMeterMaster.Decrease(); volumeMeterMaster.Decrease(amount, isPrecise);
return true; return true;
case GlobalAction.IncreaseVolume: case GlobalAction.IncreaseVolume:
if (State == Visibility.Hidden) if (State == Visibility.Hidden)
Show(); Show();
else else
volumeMeterMaster.Increase(); volumeMeterMaster.Increase(amount, isPrecise);
return true; return true;
case GlobalAction.ToggleMute: case GlobalAction.ToggleMute:
Show(); Show();

View File

@ -33,8 +33,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
protected virtual IEnumerable<SampleInfo> GetSamples() => HitObject.Samples; protected virtual IEnumerable<SampleInfo> GetSamples() => HitObject.Samples;
private readonly Lazy<List<DrawableHitObject>> nestedHitObjects = new Lazy<List<DrawableHitObject>>(); private readonly Lazy<List<DrawableHitObject>> nestedHitObjects = new Lazy<List<DrawableHitObject>>();
public bool HasNestedHitObjects => nestedHitObjects.IsValueCreated; public IEnumerable<DrawableHitObject> NestedHitObjects => nestedHitObjects.IsValueCreated ? nestedHitObjects.Value : Enumerable.Empty<DrawableHitObject>();
public IReadOnlyList<DrawableHitObject> NestedHitObjects => nestedHitObjects.Value;
public event Action<DrawableHitObject, Judgement> OnJudgement; public event Action<DrawableHitObject, Judgement> OnJudgement;
public event Action<DrawableHitObject, Judgement> OnJudgementRemoved; public event Action<DrawableHitObject, Judgement> OnJudgementRemoved;
@ -50,12 +49,12 @@ namespace osu.Game.Rulesets.Objects.Drawables
/// <summary> /// <summary>
/// Whether this <see cref="DrawableHitObject"/> and all of its nested <see cref="DrawableHitObject"/>s have been hit. /// Whether this <see cref="DrawableHitObject"/> and all of its nested <see cref="DrawableHitObject"/>s have been hit.
/// </summary> /// </summary>
public bool IsHit => Judgements.Any(j => j.Final && j.IsHit) && (!HasNestedHitObjects || NestedHitObjects.All(n => n.IsHit)); public bool IsHit => Judgements.Any(j => j.Final && j.IsHit) && NestedHitObjects.All(n => n.IsHit);
/// <summary> /// <summary>
/// Whether this <see cref="DrawableHitObject"/> and all of its nested <see cref="DrawableHitObject"/>s have been judged. /// Whether this <see cref="DrawableHitObject"/> and all of its nested <see cref="DrawableHitObject"/>s have been judged.
/// </summary> /// </summary>
public bool AllJudged => (!ProvidesJudgement || judgementFinalized) && (!HasNestedHitObjects || NestedHitObjects.All(h => h.AllJudged)); public bool AllJudged => (!ProvidesJudgement || judgementFinalized) && NestedHitObjects.All(h => h.AllJudged);
/// <summary> /// <summary>
/// Whether this <see cref="DrawableHitObject"/> can be judged. /// Whether this <see cref="DrawableHitObject"/> can be judged.
@ -90,13 +89,12 @@ namespace osu.Game.Rulesets.Objects.Drawables
if (HitObject.SampleControlPoint == null) if (HitObject.SampleControlPoint == null)
throw new ArgumentNullException(nameof(HitObject.SampleControlPoint), $"{nameof(HitObject)}s must always have an attached {nameof(HitObject.SampleControlPoint)}." throw new ArgumentNullException(nameof(HitObject.SampleControlPoint), $"{nameof(HitObject)}s must always have an attached {nameof(HitObject.SampleControlPoint)}."
+ $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}."); + $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}.");
AddInternal(Samples = new SkinnableSound(samples.Select(s => new SampleInfo
{ samples = samples.Select(s => HitObject.SampleControlPoint.ApplyTo(s)).ToArray();
Bank = s.Bank ?? HitObject.SampleControlPoint.SampleBank, foreach (var s in samples)
Name = s.Name, s.Namespace = SampleNamespace;
Volume = s.Volume > 0 ? s.Volume : HitObject.SampleControlPoint.SampleVolume,
Namespace = SampleNamespace AddInternal(Samples = new SkinnableSound(samples));
}).ToArray()));
} }
} }
@ -206,9 +204,8 @@ namespace osu.Game.Rulesets.Objects.Drawables
if (AllJudged) if (AllJudged)
return false; return false;
if (HasNestedHitObjects) foreach (var d in NestedHitObjects)
foreach (var d in NestedHitObjects) judgementOccurred |= d.UpdateJudgement(userTriggered);
judgementOccurred |= d.UpdateJudgement(userTriggered);
if (!ProvidesJudgement || judgementFinalized || judgementOccurred) if (!ProvidesJudgement || judgementFinalized || judgementOccurred)
return judgementOccurred; return judgementOccurred;

View File

@ -6,6 +6,7 @@ using osu.Game.Rulesets.Objects.Types;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO;
using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Formats;
using osu.Game.Audio; using osu.Game.Audio;
using System.Linq; using System.Linq;
@ -196,9 +197,6 @@ namespace osu.Game.Rulesets.Objects.Legacy
var bank = (LegacyBeatmapDecoder.LegacySampleBank)Convert.ToInt32(split[0]); var bank = (LegacyBeatmapDecoder.LegacySampleBank)Convert.ToInt32(split[0]);
var addbank = (LegacyBeatmapDecoder.LegacySampleBank)Convert.ToInt32(split[1]); var addbank = (LegacyBeatmapDecoder.LegacySampleBank)Convert.ToInt32(split[1]);
// Let's not implement this for now, because this doesn't fit nicely into the bank structure
//string sampleFile = split2.Length > 4 ? split2[4] : string.Empty;
string stringBank = bank.ToString().ToLower(); string stringBank = bank.ToString().ToLower();
if (stringBank == @"none") if (stringBank == @"none")
stringBank = null; stringBank = null;
@ -211,6 +209,8 @@ namespace osu.Game.Rulesets.Objects.Legacy
if (split.Length > 3) if (split.Length > 3)
bankInfo.Volume = int.Parse(split[3]); bankInfo.Volume = int.Parse(split[3]);
bankInfo.Filename = split.Length > 4 ? split[4] : null;
} }
/// <summary> /// <summary>
@ -252,6 +252,10 @@ namespace osu.Game.Rulesets.Objects.Legacy
private List<SampleInfo> convertSoundType(LegacySoundType type, SampleBankInfo bankInfo) private List<SampleInfo> convertSoundType(LegacySoundType type, SampleBankInfo bankInfo)
{ {
// Todo: This should return the normal SampleInfos if the specified sample file isn't found, but that's a pretty edge-case scenario
if (!string.IsNullOrEmpty(bankInfo.Filename))
return new List<SampleInfo> { new FileSampleInfo { Filename = bankInfo.Filename } };
var soundTypes = new List<SampleInfo> var soundTypes = new List<SampleInfo>
{ {
new SampleInfo new SampleInfo
@ -297,14 +301,24 @@ namespace osu.Game.Rulesets.Objects.Legacy
private class SampleBankInfo private class SampleBankInfo
{ {
public string Filename;
public string Normal; public string Normal;
public string Add; public string Add;
public int Volume; public int Volume;
public SampleBankInfo Clone() public SampleBankInfo Clone() => (SampleBankInfo)MemberwiseClone();
}
private class FileSampleInfo : SampleInfo
{
public string Filename;
public override IEnumerable<string> LookupNames => new[]
{ {
return (SampleBankInfo)MemberwiseClone(); Filename,
} Path.ChangeExtension(Filename, null)
};
} }
[Flags] [Flags]

View File

@ -47,13 +47,10 @@ namespace osu.Game.Rulesets.UI.Scrolling.Visualisers
} }
} }
if (obj.HasNestedHitObjects) ComputeInitialStates(obj.NestedHitObjects, direction, timeRange, length);
{
ComputeInitialStates(obj.NestedHitObjects, direction, timeRange, length);
// Nested hitobjects don't need to scroll, but they do need accurate positions // Nested hitobjects don't need to scroll, but they do need accurate positions
UpdatePositions(obj.NestedHitObjects, direction, obj.HitObject.StartTime, timeRange, length); UpdatePositions(obj.NestedHitObjects, direction, obj.HitObject.StartTime, timeRange, length);
}
} }
} }

View File

@ -48,13 +48,10 @@ namespace osu.Game.Rulesets.UI.Scrolling.Visualisers
} }
} }
if (obj.HasNestedHitObjects) ComputeInitialStates(obj.NestedHitObjects, direction, timeRange, length);
{
ComputeInitialStates(obj.NestedHitObjects, direction, timeRange, length);
// Nested hitobjects don't need to scroll, but they do need accurate positions // Nested hitobjects don't need to scroll, but they do need accurate positions
UpdatePositions(obj.NestedHitObjects, direction, obj.HitObject.StartTime, timeRange, length); UpdatePositions(obj.NestedHitObjects, direction, obj.HitObject.StartTime, timeRange, length);
}
} }
} }

View File

@ -11,7 +11,6 @@ using osu.Framework.Audio.Sample;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Input;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Framework.Threading; using osu.Framework.Threading;
using osu.Game.Graphics; using osu.Game.Graphics;
@ -139,26 +138,15 @@ namespace osu.Game.Screens.Menu
sampleBack = audio.Sample.Get(@"Menu/button-back-select"); sampleBack = audio.Sample.Get(@"Menu/button-back-select");
} }
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
{
if (args.Repeat) return false;
switch (args.Key)
{
case Key.Space:
logo?.TriggerOnClick(state);
return true;
}
return false;
}
public bool OnPressed(GlobalAction action) public bool OnPressed(GlobalAction action)
{ {
switch (action) switch (action)
{ {
case GlobalAction.Back: case GlobalAction.Back:
return goBack(); return goBack();
case GlobalAction.Select:
logo?.TriggerOnClick();
return true;
default: default:
return false; return false;
} }

View File

@ -101,7 +101,7 @@ namespace osu.Game.Screens
sampleExit = audio.Sample.Get(@"UI/screen-back"); sampleExit = audio.Sample.Get(@"UI/screen-back");
} }
public bool OnPressed(GlobalAction action) public virtual bool OnPressed(GlobalAction action)
{ {
if (!IsCurrentScreen) return false; if (!IsCurrentScreen) return false;

View File

@ -55,6 +55,8 @@ namespace osu.Game.Screens.Select
private readonly Box box; private readonly Box box;
private readonly Box light; private readonly Box light;
public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => box.ReceiveMouseInputAt(screenSpacePos);
public FooterButton() public FooterButton()
{ {
Children = new Drawable[] Children = new Drawable[]

View File

@ -17,6 +17,7 @@ using osu.Framework.Threading;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Input.Bindings;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Backgrounds;
@ -67,6 +68,7 @@ namespace osu.Game.Screens.Select
protected new readonly Bindable<RulesetInfo> Ruleset = new Bindable<RulesetInfo>(); protected new readonly Bindable<RulesetInfo> Ruleset = new Bindable<RulesetInfo>();
private DependencyContainer dependencies; private DependencyContainer dependencies;
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
=> dependencies = new DependencyContainer(base.CreateLocalDependencies(parent)); => dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
@ -464,7 +466,8 @@ namespace osu.Game.Screens.Select
private void carouselBeatmapsLoaded() private void carouselBeatmapsLoaded()
{ {
if (!Beatmap.IsDefault && Beatmap.Value.BeatmapSetInfo?.DeletePending == false && Beatmap.Value.BeatmapSetInfo?.Protected == false && Carousel.SelectBeatmap(Beatmap.Value.BeatmapInfo, false)) if (!Beatmap.IsDefault && Beatmap.Value.BeatmapSetInfo?.DeletePending == false && Beatmap.Value.BeatmapSetInfo?.Protected == false
&& Carousel.SelectBeatmap(Beatmap.Value.BeatmapInfo, false))
return; return;
if (Carousel.SelectedBeatmapSet == null && !Carousel.SelectNextRandom()) if (Carousel.SelectedBeatmapSet == null && !Carousel.SelectNextRandom())
@ -481,16 +484,26 @@ namespace osu.Game.Screens.Select
dialogOverlay?.Push(new BeatmapDeleteDialog(beatmap)); dialogOverlay?.Push(new BeatmapDeleteDialog(beatmap));
} }
public override bool OnPressed(GlobalAction action)
{
if (!IsCurrentScreen) return false;
switch (action)
{
case GlobalAction.Select:
FinaliseSelection();
return true;
}
return base.OnPressed(action);
}
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
{ {
if (args.Repeat) return false; if (args.Repeat) return false;
switch (args.Key) switch (args.Key)
{ {
case Key.KeypadEnter:
case Key.Enter:
FinaliseSelection();
return true;
case Key.Delete: case Key.Delete:
if (state.Keyboard.ShiftPressed) if (state.Keyboard.ShiftPressed)
{ {

View File

@ -44,19 +44,17 @@ namespace osu.Game.Skinning
private SampleChannel loadChannel(SampleInfo info, Func<string, SampleChannel> getSampleFunction) private SampleChannel loadChannel(SampleInfo info, Func<string, SampleChannel> getSampleFunction)
{ {
SampleChannel ch = null; foreach (var lookup in info.LookupNames)
{
var ch = getSampleFunction($"Gameplay/{lookup}");
if (ch == null)
continue;
if (info.Namespace != null)
ch = getSampleFunction($"Gameplay/{info.Namespace}/{info.Bank}-{info.Name}");
// try without namespace as a fallback.
if (ch == null)
ch = getSampleFunction($"Gameplay/{info.Bank}-{info.Name}");
if (ch != null)
ch.Volume.Value = info.Volume / 100.0; ch.Volume.Value = info.Volume / 100.0;
return ch;
}
return ch; return null;
} }
} }
} }

View File

@ -25,6 +25,8 @@ namespace osu.Game.Tests.Beatmaps
protected abstract string ResourceAssembly { get; } protected abstract string ResourceAssembly { get; }
protected IBeatmapConverter Converter { get; private set; }
protected void Test(string name) protected void Test(string name)
{ {
var ourResult = convert(name); var ourResult = convert(name);
@ -41,14 +43,22 @@ namespace osu.Game.Tests.Beatmaps
Assert.Fail($"A conversion did not generate any hitobjects, but should have, for hitobject at time: {expectedResult.Mappings[mappingCounter].StartTime}\n"); Assert.Fail($"A conversion did not generate any hitobjects, but should have, for hitobject at time: {expectedResult.Mappings[mappingCounter].StartTime}\n");
else if (mappingCounter >= expectedResult.Mappings.Count) else if (mappingCounter >= expectedResult.Mappings.Count)
Assert.Fail($"A conversion generated hitobjects, but should not have, for hitobject at time: {ourResult.Mappings[mappingCounter].StartTime}\n"); Assert.Fail($"A conversion generated hitobjects, but should not have, for hitobject at time: {ourResult.Mappings[mappingCounter].StartTime}\n");
else if (!expectedResult.Mappings[mappingCounter].Equals(ourResult.Mappings[mappingCounter]))
{
var expectedMapping = expectedResult.Mappings[mappingCounter];
var ourMapping = ourResult.Mappings[mappingCounter];
Assert.Fail($"The conversion mapping differed for object at time {expectedMapping.StartTime}:\n"
+ $"Expected {JsonConvert.SerializeObject(expectedMapping)}\n"
+ $"Received: {JsonConvert.SerializeObject(ourMapping)}\n");
}
else else
{ {
var counter = mappingCounter; var ourMapping = ourResult.Mappings[mappingCounter];
var expectedMapping = expectedResult.Mappings[mappingCounter];
Assert.Multiple(() => Assert.Multiple(() =>
{ {
var ourMapping = ourResult.Mappings[counter];
var expectedMapping = expectedResult.Mappings[counter];
int objectCounter = 0; int objectCounter = 0;
while (true) while (true)
{ {
@ -60,10 +70,6 @@ namespace osu.Game.Tests.Beatmaps
else if (objectCounter >= expectedMapping.Objects.Count) else if (objectCounter >= expectedMapping.Objects.Count)
Assert.Fail($"The conversion generated a hitobject, but should not have, for hitobject at time: {ourMapping.StartTime}:\n" Assert.Fail($"The conversion generated a hitobject, but should not have, for hitobject at time: {ourMapping.StartTime}:\n"
+ $"Received: {JsonConvert.SerializeObject(ourMapping.Objects[objectCounter])}\n"); + $"Received: {JsonConvert.SerializeObject(ourMapping.Objects[objectCounter])}\n");
else if (!expectedMapping.Equals(ourMapping))
Assert.Fail($"The conversion mapping differed for object at time {expectedMapping.StartTime}:\n"
+ $"Expected {JsonConvert.SerializeObject(expectedMapping)}\n"
+ $"Received: {JsonConvert.SerializeObject(ourMapping)}\n");
else if (!expectedMapping.Objects[objectCounter].Equals(ourMapping.Objects[objectCounter])) else if (!expectedMapping.Objects[objectCounter].Equals(ourMapping.Objects[objectCounter]))
{ {
Assert.Fail($"The conversion generated differing hitobjects for object at time: {expectedMapping.StartTime}:\n" Assert.Fail($"The conversion generated differing hitobjects for object at time: {expectedMapping.StartTime}:\n"
@ -88,10 +94,11 @@ namespace osu.Game.Tests.Beatmaps
var rulesetInstance = CreateRuleset(); var rulesetInstance = CreateRuleset();
beatmap.BeatmapInfo.Ruleset = beatmap.BeatmapInfo.RulesetID == rulesetInstance.RulesetInfo.ID ? rulesetInstance.RulesetInfo : new RulesetInfo(); beatmap.BeatmapInfo.Ruleset = beatmap.BeatmapInfo.RulesetID == rulesetInstance.RulesetInfo.ID ? rulesetInstance.RulesetInfo : new RulesetInfo();
var result = new ConvertResult(); Converter = rulesetInstance.CreateBeatmapConverter(beatmap);
var converter = rulesetInstance.CreateBeatmapConverter(beatmap);
converter.ObjectConverted += (orig, converted) => var result = new ConvertResult();
Converter.ObjectConverted += (orig, converted) =>
{ {
converted.ForEach(h => h.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty)); converted.ForEach(h => h.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty));
@ -103,7 +110,7 @@ namespace osu.Game.Tests.Beatmaps
result.Mappings.Add(mapping); result.Mappings.Add(mapping);
}; };
IBeatmap convertedBeatmap = converter.Convert(); IBeatmap convertedBeatmap = Converter.Convert();
rulesetInstance.CreateBeatmapProcessor(convertedBeatmap)?.PostProcess(); rulesetInstance.CreateBeatmapProcessor(convertedBeatmap)?.PostProcess();
return result; return result;

View File

@ -18,7 +18,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.1.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.1.1" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" /> <PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="ppy.osu.Framework" Version="2018.629.0" /> <PackageReference Include="ppy.osu.Framework" Version="2018.705.0" />
<PackageReference Include="SharpCompress" Version="0.17.1" /> <PackageReference Include="SharpCompress" Version="0.17.1" />
<PackageReference Include="NUnit" Version="3.10.1" /> <PackageReference Include="NUnit" Version="3.10.1" />
<PackageReference Include="System.ComponentModel.Annotations" Version="4.5.0" /> <PackageReference Include="System.ComponentModel.Annotations" Version="4.5.0" />