1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-13 18:32:55 +08:00

Merge branch 'master' into fix-music-controller-regressed

This commit is contained in:
Dean Herbert 2020-09-08 18:26:15 +09:00
commit 072aab90ab
63 changed files with 560 additions and 142 deletions

View File

@ -52,6 +52,6 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.904.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2020.904.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.904.0" /> <PackageReference Include="ppy.osu.Framework.Android" Version="2020.907.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -14,6 +14,7 @@ using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Catch.Tests namespace osu.Game.Rulesets.Catch.Tests
{ {
[TestFixture] [TestFixture]
[Timeout(10000)]
public class CatchBeatmapConversionTest : BeatmapConversionTest<ConvertValue> public class CatchBeatmapConversionTest : BeatmapConversionTest<ConvertValue>
{ {
protected override string ResourceAssembly => "osu.Game.Rulesets.Catch"; protected override string ResourceAssembly => "osu.Game.Rulesets.Catch";

View File

@ -21,13 +21,11 @@ using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring; using osu.Game.Scoring;
using System; using System;
using osu.Framework.Testing;
using osu.Game.Rulesets.Catch.Skinning; using osu.Game.Rulesets.Catch.Skinning;
using osu.Game.Skinning; using osu.Game.Skinning;
namespace osu.Game.Rulesets.Catch namespace osu.Game.Rulesets.Catch
{ {
[ExcludeFromDynamicCompile]
public class CatchRuleset : Ruleset, ILegacyRuleset public class CatchRuleset : Ruleset, ILegacyRuleset
{ {
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => new DrawableCatchRuleset(this, beatmap, mods); public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => new DrawableCatchRuleset(this, beatmap, mods);

View File

@ -14,11 +14,13 @@ using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Mania.Tests namespace osu.Game.Rulesets.Mania.Tests
{ {
[TestFixture] [TestFixture]
[Timeout(10000)]
public class ManiaBeatmapConversionTest : BeatmapConversionTest<ManiaConvertMapping, ConvertValue> public class ManiaBeatmapConversionTest : BeatmapConversionTest<ManiaConvertMapping, ConvertValue>
{ {
protected override string ResourceAssembly => "osu.Game.Rulesets.Mania"; protected override string ResourceAssembly => "osu.Game.Rulesets.Mania";
[TestCase("basic")] [TestCase("basic")]
[TestCase("zero-length-slider")]
public void Test(string name) => base.Test(name); public void Test(string name) => base.Test(name);
protected override IEnumerable<ConvertValue> CreateConvertValue(HitObject hitObject) protected override IEnumerable<ConvertValue> CreateConvertValue(HitObject hitObject)

View File

@ -5,7 +5,6 @@ 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.Utils;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
@ -167,8 +166,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
var positionData = original as IHasPosition; var positionData = original as IHasPosition;
for (double time = original.StartTime; !Precision.DefinitelyBigger(time, generator.EndTime); time += generator.SegmentDuration) for (int i = 0; i <= generator.SpanCount; i++)
{ {
double time = original.StartTime + generator.SegmentDuration * i;
recordNote(time, positionData?.Position ?? Vector2.Zero); recordNote(time, positionData?.Position ?? Vector2.Zero);
computeDensity(time); computeDensity(time);
} }

View File

@ -27,8 +27,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
public readonly double EndTime; public readonly double EndTime;
public readonly double SegmentDuration; public readonly double SegmentDuration;
public readonly int SpanCount;
private readonly int spanCount;
private PatternType convertType; private PatternType convertType;
@ -42,20 +41,20 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
var distanceData = hitObject as IHasDistance; var distanceData = hitObject as IHasDistance;
var repeatsData = hitObject as IHasRepeats; var repeatsData = hitObject as IHasRepeats;
spanCount = repeatsData?.SpanCount() ?? 1; SpanCount = repeatsData?.SpanCount() ?? 1;
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime); TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime);
DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(hitObject.StartTime); DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(hitObject.StartTime);
// The true distance, accounting for any repeats // The true distance, accounting for any repeats
double distance = (distanceData?.Distance ?? 0) * spanCount; double distance = (distanceData?.Distance ?? 0) * SpanCount;
// The velocity of the osu! hit object - calculated as the velocity of a slider // The velocity of the osu! hit object - calculated as the velocity of a slider
double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / timingPoint.BeatLength; double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / timingPoint.BeatLength;
// 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 IEnumerable<Pattern> Generate() public override IEnumerable<Pattern> Generate()
@ -96,7 +95,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
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);
@ -104,7 +103,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
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)
@ -117,7 +116,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
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);
@ -251,7 +250,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
int column = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); int column = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
bool increasing = Random.NextDouble() > 0.5; bool increasing = Random.NextDouble() > 0.5;
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;
@ -302,7 +301,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
for (int i = 0; i <= spanCount; i++) for (int i = 0; i <= SpanCount; i++)
{ {
addToPattern(pattern, nextColumn, startTime, startTime); addToPattern(pattern, nextColumn, startTime, startTime);
@ -393,7 +392,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
var pattern = new Pattern(); var pattern = new Pattern();
int columnRepeat = Math.Min(spanCount, TotalColumns); int columnRepeat = Math.Min(SpanCount, TotalColumns);
int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns) if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
@ -447,7 +446,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
var rowPattern = new Pattern(); var rowPattern = new Pattern();
for (int i = 0; i <= spanCount; i++) for (int i = 0; i <= SpanCount; i++)
{ {
if (!(ignoreHead && startTime == HitObject.StartTime)) if (!(ignoreHead && startTime == HitObject.StartTime))
{ {

View File

@ -12,7 +12,6 @@ using System.Linq;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Framework.Testing;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Replays; using osu.Game.Rulesets.Mania.Replays;
using osu.Game.Rulesets.Replays.Types; using osu.Game.Rulesets.Replays.Types;
@ -35,7 +34,6 @@ using osu.Game.Screens.Ranking.Statistics;
namespace osu.Game.Rulesets.Mania namespace osu.Game.Rulesets.Mania
{ {
[ExcludeFromDynamicCompile]
public class ManiaRuleset : Ruleset, ILegacyRuleset public class ManiaRuleset : Ruleset, ILegacyRuleset
{ {
/// <summary> /// <summary>

View File

@ -0,0 +1,14 @@
{
"Mappings": [{
"RandomW": 3083084786,
"RandomX": 273326509,
"RandomY": 273553282,
"RandomZ": 2659838971,
"StartTime": 4836,
"Objects": [{
"StartTime": 4836,
"EndTime": 4836,
"Column": 0
}]
}]
}

View File

@ -0,0 +1,20 @@
osu file format v14
[General]
StackLeniency: 0.7
Mode: 0
[Difficulty]
HPDrainRate:1
CircleSize:4
OverallDifficulty:1
ApproachRate:9
SliderMultiplier:2.5
SliderTickRate:0.5
[TimingPoints]
34,431.654676258993,4,1,0,50,1,0
4782,-66.6666666666667,4,1,0,20,0,0
[HitObjects]
15,199,4836,22,0,L,1,46.8750017881394

View File

@ -12,6 +12,7 @@ using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Osu.Tests namespace osu.Game.Rulesets.Osu.Tests
{ {
[TestFixture] [TestFixture]
[Timeout(10000)]
public class OsuBeatmapConversionTest : BeatmapConversionTest<ConvertValue> public class OsuBeatmapConversionTest : BeatmapConversionTest<ConvertValue>
{ {
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu"; protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";

View File

@ -30,14 +30,12 @@ using osu.Game.Scoring;
using osu.Game.Skinning; using osu.Game.Skinning;
using System; using System;
using System.Linq; using System.Linq;
using osu.Framework.Testing;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Statistics; using osu.Game.Rulesets.Osu.Statistics;
using osu.Game.Screens.Ranking.Statistics; using osu.Game.Screens.Ranking.Statistics;
namespace osu.Game.Rulesets.Osu namespace osu.Game.Rulesets.Osu
{ {
[ExcludeFromDynamicCompile]
public class OsuRuleset : Ruleset, ILegacyRuleset public class OsuRuleset : Ruleset, ILegacyRuleset
{ {
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => new DrawableOsuRuleset(this, beatmap, mods); public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => new DrawableOsuRuleset(this, beatmap, mods);

View File

@ -18,6 +18,10 @@ namespace osu.Game.Rulesets.Osu.Skinning
{ {
private const float shadow_portion = 1 - (OsuLegacySkinTransformer.LEGACY_CIRCLE_RADIUS / OsuHitObject.OBJECT_RADIUS); private const float shadow_portion = 1 - (OsuLegacySkinTransformer.LEGACY_CIRCLE_RADIUS / OsuHitObject.OBJECT_RADIUS);
protected new float CalculatedBorderPortion
// Roughly matches osu!stable's slider border portions.
=> base.CalculatedBorderPortion * 0.77f;
public new Color4 AccentColour => new Color4(base.AccentColour.R, base.AccentColour.G, base.AccentColour.B, base.AccentColour.A * 0.70f); public new Color4 AccentColour => new Color4(base.AccentColour.R, base.AccentColour.G, base.AccentColour.B, base.AccentColour.A * 0.70f);
protected override Color4 ColourAt(float position) protected override Color4 ColourAt(float position)

View File

@ -12,6 +12,7 @@ using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Taiko.Tests namespace osu.Game.Rulesets.Taiko.Tests
{ {
[TestFixture] [TestFixture]
[Timeout(10000)]
public class TaikoBeatmapConversionTest : BeatmapConversionTest<ConvertValue> public class TaikoBeatmapConversionTest : BeatmapConversionTest<ConvertValue>
{ {
protected override string ResourceAssembly => "osu.Game.Rulesets.Taiko"; protected override string ResourceAssembly => "osu.Game.Rulesets.Taiko";

View File

@ -22,7 +22,6 @@ using osu.Game.Rulesets.Taiko.Scoring;
using osu.Game.Scoring; using osu.Game.Scoring;
using System; using System;
using System.Linq; using System.Linq;
using osu.Framework.Testing;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Taiko.Edit; using osu.Game.Rulesets.Taiko.Edit;
using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects;
@ -32,7 +31,6 @@ using osu.Game.Skinning;
namespace osu.Game.Rulesets.Taiko namespace osu.Game.Rulesets.Taiko
{ {
[ExcludeFromDynamicCompile]
public class TaikoRuleset : Ruleset, ILegacyRuleset public class TaikoRuleset : Ruleset, ILegacyRuleset
{ {
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => new DrawableTaikoRuleset(this, beatmap, mods); public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => new DrawableTaikoRuleset(this, beatmap, mods);

View File

@ -10,6 +10,7 @@ using System.Text;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Audio.Track; using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Formats;
@ -19,6 +20,7 @@ using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Mania;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Taiko; using osu.Game.Rulesets.Taiko;
using osu.Game.Skinning;
using osu.Game.Tests.Resources; using osu.Game.Tests.Resources;
namespace osu.Game.Tests.Beatmaps.Formats namespace osu.Game.Tests.Beatmaps.Formats
@ -26,18 +28,33 @@ namespace osu.Game.Tests.Beatmaps.Formats
[TestFixture] [TestFixture]
public class LegacyBeatmapEncoderTest public class LegacyBeatmapEncoderTest
{ {
private static IEnumerable<string> allBeatmaps => TestResources.GetStore().GetAvailableResources().Where(res => res.EndsWith(".osu")); private static readonly DllResourceStore beatmaps_resource_store = TestResources.GetStore();
private static IEnumerable<string> allBeatmaps = beatmaps_resource_store.GetAvailableResources().Where(res => res.EndsWith(".osu"));
[TestCaseSource(nameof(allBeatmaps))] [TestCaseSource(nameof(allBeatmaps))]
public void TestEncodeDecodeStability(string name) public void TestEncodeDecodeStability(string name)
{ {
var decoded = decodeFromLegacy(TestResources.GetStore().GetStream(name)); var decoded = decodeFromLegacy(beatmaps_resource_store.GetStream(name), name);
var decodedAfterEncode = decodeFromLegacy(encodeToLegacy(decoded)); var decodedAfterEncode = decodeFromLegacy(encodeToLegacy(decoded), name);
sort(decoded); sort(decoded.beatmap);
sort(decodedAfterEncode); sort(decodedAfterEncode.beatmap);
Assert.That(decodedAfterEncode.Serialize(), Is.EqualTo(decoded.Serialize())); Assert.That(decodedAfterEncode.beatmap.Serialize(), Is.EqualTo(decoded.beatmap.Serialize()));
Assert.IsTrue(areComboColoursEqual(decodedAfterEncode.beatmapSkin.Configuration, decoded.beatmapSkin.Configuration));
}
private bool areComboColoursEqual(IHasComboColours a, IHasComboColours b)
{
// equal to null, no need to SequenceEqual
if (a.ComboColours == null && b.ComboColours == null)
return true;
if (a.ComboColours == null || b.ComboColours == null)
return false;
return a.ComboColours.SequenceEqual(b.ComboColours);
} }
private void sort(IBeatmap beatmap) private void sort(IBeatmap beatmap)
@ -50,18 +67,31 @@ namespace osu.Game.Tests.Beatmaps.Formats
} }
} }
private IBeatmap decodeFromLegacy(Stream stream) private (IBeatmap beatmap, TestLegacySkin beatmapSkin) decodeFromLegacy(Stream stream, string name)
{ {
using (var reader = new LineBufferedReader(stream)) using (var reader = new LineBufferedReader(stream))
return convert(new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(reader)); {
var beatmap = new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(reader);
var beatmapSkin = new TestLegacySkin(beatmaps_resource_store, name);
return (convert(beatmap), beatmapSkin);
}
} }
private Stream encodeToLegacy(IBeatmap beatmap) private class TestLegacySkin : LegacySkin
{ {
public TestLegacySkin(IResourceStore<byte[]> storage, string fileName)
: base(new SkinInfo { Name = "Test Skin", Creator = "Craftplacer" }, storage, null, fileName)
{
}
}
private Stream encodeToLegacy((IBeatmap beatmap, ISkin beatmapSkin) fullBeatmap)
{
var (beatmap, beatmapSkin) = fullBeatmap;
var stream = new MemoryStream(); var stream = new MemoryStream();
using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true)) using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true))
new LegacyBeatmapEncoder(beatmap).Encode(writer); new LegacyBeatmapEncoder(beatmap, beatmapSkin).Encode(writer);
stream.Position = 0; stream.Position = 0;

View File

@ -15,8 +15,10 @@ using osu.Framework.Extensions;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.IO; using osu.Game.IO;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Tests.Resources; using osu.Game.Tests.Resources;
using osu.Game.Users;
using SharpCompress.Archives; using SharpCompress.Archives;
using SharpCompress.Archives.Zip; using SharpCompress.Archives.Zip;
using SharpCompress.Common; using SharpCompress.Common;
@ -32,7 +34,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestImportWhenClosed() public async Task TestImportWhenClosed()
{ {
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWhenClosed))) using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{ {
try try
{ {
@ -49,7 +51,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestImportThenDelete() public async Task TestImportThenDelete()
{ {
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenDelete))) using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{ {
try try
{ {
@ -70,7 +72,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestImportThenImport() public async Task TestImportThenImport()
{ {
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImport))) using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{ {
try try
{ {
@ -96,7 +98,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test] [Test]
public async Task TestImportThenImportWithReZip() public async Task TestImportThenImportWithReZip()
{ {
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImportWithReZip))) using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{ {
try try
{ {
@ -154,7 +156,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test] [Test]
public async Task TestImportThenImportWithChangedFile() public async Task TestImportThenImportWithChangedFile()
{ {
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImportWithChangedFile))) using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{ {
try try
{ {
@ -205,7 +207,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test] [Test]
public async Task TestImportThenImportWithDifferentFilename() public async Task TestImportThenImportWithDifferentFilename()
{ {
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImportWithDifferentFilename))) using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{ {
try try
{ {
@ -257,7 +259,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestImportCorruptThenImport() public async Task TestImportCorruptThenImport()
{ {
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportCorruptThenImport))) using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{ {
try try
{ {
@ -299,7 +301,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestRollbackOnFailure() public async Task TestRollbackOnFailure()
{ {
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestRollbackOnFailure))) using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{ {
try try
{ {
@ -376,7 +378,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestImportThenDeleteThenImport() public async Task TestImportThenDeleteThenImport()
{ {
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenDeleteThenImport))) using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{ {
try try
{ {
@ -404,7 +406,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestImportThenDeleteThenImportWithOnlineIDMismatch(bool set) public async Task TestImportThenDeleteThenImportWithOnlineIDMismatch(bool set)
{ {
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"{nameof(TestImportThenDeleteThenImportWithOnlineIDMismatch)}-{set}")) using (HeadlessGameHost host = new CleanRunHeadlessGameHost(set.ToString()))
{ {
try try
{ {
@ -438,7 +440,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestImportWithDuplicateBeatmapIDs() public async Task TestImportWithDuplicateBeatmapIDs()
{ {
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWithDuplicateBeatmapIDs))) using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{ {
try try
{ {
@ -524,7 +526,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test] [Test]
public async Task TestImportWhenFileOpen() public async Task TestImportWhenFileOpen()
{ {
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWhenFileOpen))) using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{ {
try try
{ {
@ -546,7 +548,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test] [Test]
public async Task TestImportWithDuplicateHashes() public async Task TestImportWithDuplicateHashes()
{ {
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWithDuplicateHashes))) using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{ {
try try
{ {
@ -588,7 +590,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test] [Test]
public async Task TestImportNestedStructure() public async Task TestImportNestedStructure()
{ {
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportNestedStructure))) using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{ {
try try
{ {
@ -633,7 +635,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test] [Test]
public async Task TestImportWithIgnoredDirectoryInArchive() public async Task TestImportWithIgnoredDirectoryInArchive()
{ {
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWithIgnoredDirectoryInArchive))) using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{ {
try try
{ {
@ -687,7 +689,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test] [Test]
public async Task TestUpdateBeatmapInfo() public async Task TestUpdateBeatmapInfo()
{ {
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestUpdateBeatmapInfo))) using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{ {
try try
{ {
@ -717,7 +719,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test] [Test]
public async Task TestUpdateBeatmapFile() public async Task TestUpdateBeatmapFile()
{ {
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestUpdateBeatmapFile))) using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{ {
try try
{ {
@ -756,6 +758,63 @@ namespace osu.Game.Tests.Beatmaps.IO
} }
} }
[Test]
public void TestCreateNewEmptyBeatmap()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
var osu = loadOsu(host);
var manager = osu.Dependencies.Get<BeatmapManager>();
var working = manager.CreateNew(new OsuRuleset().RulesetInfo, User.SYSTEM_USER);
manager.Save(working.BeatmapInfo, working.Beatmap);
var retrievedSet = manager.GetAllUsableBeatmapSets()[0];
// Check that the new file is referenced correctly by attempting a retrieval
Beatmap updatedBeatmap = (Beatmap)manager.GetWorkingBeatmap(retrievedSet.Beatmaps[0]).Beatmap;
Assert.That(updatedBeatmap.HitObjects.Count, Is.EqualTo(0));
}
finally
{
host.Exit();
}
}
}
[Test]
public void TestCreateNewBeatmapWithObject()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
var osu = loadOsu(host);
var manager = osu.Dependencies.Get<BeatmapManager>();
var working = manager.CreateNew(new OsuRuleset().RulesetInfo, User.SYSTEM_USER);
((Beatmap)working.Beatmap).HitObjects.Add(new HitCircle { StartTime = 5000 });
manager.Save(working.BeatmapInfo, working.Beatmap);
var retrievedSet = manager.GetAllUsableBeatmapSets()[0];
// Check that the new file is referenced correctly by attempting a retrieval
Beatmap updatedBeatmap = (Beatmap)manager.GetWorkingBeatmap(retrievedSet.Beatmaps[0]).Beatmap;
Assert.That(updatedBeatmap.HitObjects.Count, Is.EqualTo(1));
Assert.That(updatedBeatmap.HitObjects[0].StartTime, Is.EqualTo(5000));
}
finally
{
host.Exit();
}
}
}
public static async Task<BeatmapSetInfo> LoadOszIntoOsu(OsuGameBase osu, string path = null, bool virtualTrack = false) public static async Task<BeatmapSetInfo> LoadOszIntoOsu(OsuGameBase osu, string path = null, bool virtualTrack = false)
{ {
var temp = path ?? TestResources.GetTestBeatmapForImport(virtualTrack); var temp = path ?? TestResources.GetTestBeatmapForImport(virtualTrack);

View File

@ -351,7 +351,7 @@ namespace osu.Game.Tests.Editing
using (var encoded = new MemoryStream()) using (var encoded = new MemoryStream())
{ {
using (var sw = new StreamWriter(encoded)) using (var sw = new StreamWriter(encoded))
new LegacyBeatmapEncoder(beatmap).Encode(sw); new LegacyBeatmapEncoder(beatmap, null).Encode(sw);
return encoded.ToArray(); return encoded.ToArray();
} }

View File

@ -27,7 +27,7 @@ namespace osu.Game.Tests.Scores.IO
[Test] [Test]
public async Task TestBasicImport() public async Task TestBasicImport()
{ {
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestBasicImport")) using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{ {
try try
{ {
@ -66,7 +66,7 @@ namespace osu.Game.Tests.Scores.IO
[Test] [Test]
public async Task TestImportMods() public async Task TestImportMods()
{ {
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportMods")) using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{ {
try try
{ {
@ -92,7 +92,7 @@ namespace osu.Game.Tests.Scores.IO
[Test] [Test]
public async Task TestImportStatistics() public async Task TestImportStatistics()
{ {
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportStatistics")) using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{ {
try try
{ {
@ -122,7 +122,7 @@ namespace osu.Game.Tests.Scores.IO
[Test] [Test]
public async Task TestImportWithDeletedBeatmapSet() public async Task TestImportWithDeletedBeatmapSet()
{ {
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWithDeletedBeatmapSet")) using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{ {
try try
{ {
@ -159,7 +159,7 @@ namespace osu.Game.Tests.Scores.IO
[Test] [Test]
public async Task TestOnlineScoreIsAvailableLocally() public async Task TestOnlineScoreIsAvailableLocally()
{ {
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestOnlineScoreIsAvailableLocally")) using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{ {
try try
{ {

View File

@ -0,0 +1,54 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework;
using osu.Game.Overlays;
using osu.Game.Rulesets;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneOverlayActivation : OsuPlayerTestScene
{
protected new OverlayTestPlayer Player => base.Player as OverlayTestPlayer;
[Test]
public void TestGameplayOverlayActivation()
{
AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
}
[Test]
public void TestGameplayOverlayActivationPaused()
{
AddUntilStep("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
AddStep("pause gameplay", () => Player.Pause());
AddUntilStep("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered);
}
[Test]
public void TestGameplayOverlayActivationReplayLoaded()
{
AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
AddStep("load a replay", () => Player.DrawableRuleset.HasReplayLoaded.Value = true);
AddAssert("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered);
}
[Test]
public void TestGameplayOverlayActivationBreaks()
{
AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
AddStep("seek to break", () => Player.GameplayClockContainer.Seek(Beatmap.Value.Beatmap.Breaks.First().StartTime));
AddUntilStep("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered);
AddStep("seek to break end", () => Player.GameplayClockContainer.Seek(Beatmap.Value.Beatmap.Breaks.First().EndTime));
AddUntilStep("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
}
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new OverlayTestPlayer();
protected class OverlayTestPlayer : TestPlayer
{
public new OverlayActivation OverlayActivationMode => base.OverlayActivationMode.Value;
}
}
}

View File

@ -12,6 +12,7 @@ using osu.Game.Rulesets;
using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Mania;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Scoring; using osu.Game.Scoring;
using osu.Game.Screens;
using osu.Game.Screens.Menu; using osu.Game.Screens.Menu;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osu.Game.Screens.Ranking; using osu.Game.Screens.Ranking;

View File

@ -7,6 +7,7 @@ using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using System.Linq; using System.Linq;
using Newtonsoft.Json; using Newtonsoft.Json;
using osu.Framework.Testing;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.IO.Serialization; using osu.Game.IO.Serialization;
using osu.Game.Rulesets; using osu.Game.Rulesets;
@ -14,6 +15,7 @@ using osu.Game.Scoring;
namespace osu.Game.Beatmaps namespace osu.Game.Beatmaps
{ {
[ExcludeFromDynamicCompile]
[Serializable] [Serializable]
public class BeatmapInfo : IEquatable<BeatmapInfo>, IJsonSerializable, IHasPrimaryKey public class BeatmapInfo : IEquatable<BeatmapInfo>, IJsonSerializable, IHasPrimaryKey
{ {

View File

@ -27,6 +27,8 @@ using osu.Game.Online.API;
using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Users;
using osu.Game.Skinning;
using Decoder = osu.Game.Beatmaps.Formats.Decoder; using Decoder = osu.Game.Beatmaps.Formats.Decoder;
namespace osu.Game.Beatmaps namespace osu.Game.Beatmaps
@ -94,6 +96,34 @@ namespace osu.Game.Beatmaps
protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path)?.ToLowerInvariant() == ".osz"; protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path)?.ToLowerInvariant() == ".osz";
public WorkingBeatmap CreateNew(RulesetInfo ruleset, User user)
{
var metadata = new BeatmapMetadata
{
Artist = "artist",
Title = "title",
Author = user,
};
var set = new BeatmapSetInfo
{
Metadata = metadata,
Beatmaps = new List<BeatmapInfo>
{
new BeatmapInfo
{
BaseDifficulty = new BeatmapDifficulty(),
Ruleset = ruleset,
Metadata = metadata,
Version = "difficulty"
}
}
};
var working = Import(set).Result;
return GetWorkingBeatmap(working.Beatmaps.First());
}
protected override async Task Populate(BeatmapSetInfo beatmapSet, ArchiveReader archive, CancellationToken cancellationToken = default) protected override async Task Populate(BeatmapSetInfo beatmapSet, ArchiveReader archive, CancellationToken cancellationToken = default)
{ {
if (archive != null) if (archive != null)
@ -198,24 +228,35 @@ namespace osu.Game.Beatmaps
/// </summary> /// </summary>
/// <param name="info">The <see cref="BeatmapInfo"/> to save the content against. The file referenced by <see cref="BeatmapInfo.Path"/> will be replaced.</param> /// <param name="info">The <see cref="BeatmapInfo"/> to save the content against. The file referenced by <see cref="BeatmapInfo.Path"/> will be replaced.</param>
/// <param name="beatmapContent">The <see cref="IBeatmap"/> content to write.</param> /// <param name="beatmapContent">The <see cref="IBeatmap"/> content to write.</param>
public void Save(BeatmapInfo info, IBeatmap beatmapContent) /// <param name="beatmapSkin">The beatmap <see cref="ISkin"/> content to write, null if to be omitted.</param>
public void Save(BeatmapInfo info, IBeatmap beatmapContent, ISkin beatmapSkin = null)
{ {
var setInfo = QueryBeatmapSet(s => s.Beatmaps.Any(b => b.ID == info.ID)); var setInfo = QueryBeatmapSet(s => s.Beatmaps.Any(b => b.ID == info.ID));
using (var stream = new MemoryStream()) using (var stream = new MemoryStream())
{ {
using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true))
new LegacyBeatmapEncoder(beatmapContent).Encode(sw); new LegacyBeatmapEncoder(beatmapContent, beatmapSkin).Encode(sw);
stream.Seek(0, SeekOrigin.Begin); stream.Seek(0, SeekOrigin.Begin);
using (ContextFactory.GetForWrite()) using (ContextFactory.GetForWrite())
{ {
var beatmapInfo = setInfo.Beatmaps.Single(b => b.ID == info.ID); var beatmapInfo = setInfo.Beatmaps.Single(b => b.ID == info.ID);
var metadata = beatmapInfo.Metadata ?? setInfo.Metadata;
// grab the original file (or create a new one if not found).
var fileInfo = setInfo.Files.SingleOrDefault(f => string.Equals(f.Filename, beatmapInfo.Path, StringComparison.OrdinalIgnoreCase)) ?? new BeatmapSetFileInfo();
// metadata may have changed; update the path with the standard format.
beatmapInfo.Path = $"{metadata.Artist} - {metadata.Title} ({metadata.Author}) [{beatmapInfo.Version}].osu";
beatmapInfo.MD5Hash = stream.ComputeMD5Hash(); beatmapInfo.MD5Hash = stream.ComputeMD5Hash();
// update existing or populate new file's filename.
fileInfo.Filename = beatmapInfo.Path;
stream.Seek(0, SeekOrigin.Begin); stream.Seek(0, SeekOrigin.Begin);
UpdateFile(setInfo, setInfo.Files.Single(f => string.Equals(f.Filename, info.Path, StringComparison.OrdinalIgnoreCase)), stream); UpdateFile(setInfo, fileInfo, stream);
} }
} }

View File

@ -33,6 +33,9 @@ namespace osu.Game.Beatmaps
protected override IBeatmap GetBeatmap() protected override IBeatmap GetBeatmap()
{ {
if (BeatmapInfo.Path == null)
return new Beatmap { BeatmapInfo = BeatmapInfo };
try try
{ {
using (var stream = new LineBufferedReader(store.GetStream(getPathForFile(BeatmapInfo.Path)))) using (var stream = new LineBufferedReader(store.GetStream(getPathForFile(BeatmapInfo.Path))))
@ -67,6 +70,9 @@ namespace osu.Game.Beatmaps
protected override Track GetBeatmapTrack() protected override Track GetBeatmapTrack()
{ {
if (Metadata?.AudioFile == null)
return null;
try try
{ {
return trackStore.Get(getPathForFile(Metadata.AudioFile)); return trackStore.Get(getPathForFile(Metadata.AudioFile));
@ -80,6 +86,9 @@ namespace osu.Game.Beatmaps
protected override Waveform GetWaveform() protected override Waveform GetWaveform()
{ {
if (Metadata?.AudioFile == null)
return null;
try try
{ {
var trackData = store.GetStream(getPathForFile(Metadata.AudioFile)); var trackData = store.GetStream(getPathForFile(Metadata.AudioFile));

View File

@ -6,11 +6,13 @@ using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using System.Linq; using System.Linq;
using Newtonsoft.Json; using Newtonsoft.Json;
using osu.Framework.Testing;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Users; using osu.Game.Users;
namespace osu.Game.Beatmaps namespace osu.Game.Beatmaps
{ {
[ExcludeFromDynamicCompile]
[Serializable] [Serializable]
public class BeatmapMetadata : IEquatable<BeatmapMetadata>, IHasPrimaryKey public class BeatmapMetadata : IEquatable<BeatmapMetadata>, IHasPrimaryKey
{ {

View File

@ -5,10 +5,12 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using System.Linq; using System.Linq;
using osu.Framework.Testing;
using osu.Game.Database; using osu.Game.Database;
namespace osu.Game.Beatmaps namespace osu.Game.Beatmaps
{ {
[ExcludeFromDynamicCompile]
public class BeatmapSetInfo : IHasPrimaryKey, IHasFiles<BeatmapSetFileInfo>, ISoftDelete, IEquatable<BeatmapSetInfo> public class BeatmapSetInfo : IHasPrimaryKey, IHasFiles<BeatmapSetFileInfo>, ISoftDelete, IEquatable<BeatmapSetInfo>
{ {
public int ID { get; set; } public int ID { get; set; }

View File

@ -8,6 +8,6 @@ namespace osu.Game.Beatmaps.Formats
{ {
public interface IHasCustomColours public interface IHasCustomColours
{ {
Dictionary<string, Color4> CustomColours { get; set; } Dictionary<string, Color4> CustomColours { get; }
} }
} }

View File

@ -7,13 +7,16 @@ using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using JetBrains.Annotations;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Beatmaps.Legacy; using osu.Game.Beatmaps.Legacy;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Rulesets.Objects.Legacy;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Skinning;
using osuTK; using osuTK;
using osuTK.Graphics;
namespace osu.Game.Beatmaps.Formats namespace osu.Game.Beatmaps.Formats
{ {
@ -23,9 +26,18 @@ namespace osu.Game.Beatmaps.Formats
private readonly IBeatmap beatmap; private readonly IBeatmap beatmap;
public LegacyBeatmapEncoder(IBeatmap beatmap) [CanBeNull]
private readonly ISkin skin;
/// <summary>
/// Creates a new <see cref="LegacyBeatmapEncoder"/>.
/// </summary>
/// <param name="beatmap">The beatmap to encode.</param>
/// <param name="skin">The beatmap's skin, used for encoding combo colours.</param>
public LegacyBeatmapEncoder(IBeatmap beatmap, [CanBeNull] ISkin skin)
{ {
this.beatmap = beatmap; this.beatmap = beatmap;
this.skin = skin;
if (beatmap.BeatmapInfo.RulesetID < 0 || beatmap.BeatmapInfo.RulesetID > 3) if (beatmap.BeatmapInfo.RulesetID < 0 || beatmap.BeatmapInfo.RulesetID > 3)
throw new ArgumentException("Only beatmaps in the osu, taiko, catch, or mania rulesets can be encoded to the legacy beatmap format.", nameof(beatmap)); throw new ArgumentException("Only beatmaps in the osu, taiko, catch, or mania rulesets can be encoded to the legacy beatmap format.", nameof(beatmap));
@ -53,6 +65,9 @@ namespace osu.Game.Beatmaps.Formats
writer.WriteLine(); writer.WriteLine();
handleControlPoints(writer); handleControlPoints(writer);
writer.WriteLine();
handleColours(writer);
writer.WriteLine(); writer.WriteLine();
handleHitObjects(writer); handleHitObjects(writer);
} }
@ -196,6 +211,28 @@ namespace osu.Game.Beatmaps.Formats
} }
} }
private void handleColours(TextWriter writer)
{
var colours = skin?.GetConfig<GlobalSkinColours, IReadOnlyList<Color4>>(GlobalSkinColours.ComboColours)?.Value;
if (colours == null || colours.Count == 0)
return;
writer.WriteLine("[Colours]");
for (var i = 0; i < colours.Count; i++)
{
var comboColour = colours[i];
writer.Write(FormattableString.Invariant($"Combo{i}: "));
writer.Write(FormattableString.Invariant($"{(byte)(comboColour.R * byte.MaxValue)},"));
writer.Write(FormattableString.Invariant($"{(byte)(comboColour.G * byte.MaxValue)},"));
writer.Write(FormattableString.Invariant($"{(byte)(comboColour.B * byte.MaxValue)},"));
writer.Write(FormattableString.Invariant($"{(byte)(comboColour.A * byte.MaxValue)}"));
writer.WriteLine();
}
}
private void handleHitObjects(TextWriter writer) private void handleHitObjects(TextWriter writer)
{ {
if (beatmap.HitObjects.Count == 0) if (beatmap.HitObjects.Count == 0)

View File

@ -13,6 +13,7 @@ using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Framework.Statistics; using osu.Framework.Statistics;
using osu.Framework.Testing;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
@ -22,6 +23,7 @@ using osu.Game.Storyboards;
namespace osu.Game.Beatmaps namespace osu.Game.Beatmaps
{ {
[ExcludeFromDynamicCompile]
public abstract class WorkingBeatmap : IWorkingBeatmap public abstract class WorkingBeatmap : IWorkingBeatmap
{ {
public readonly BeatmapInfo BeatmapInfo; public readonly BeatmapInfo BeatmapInfo;
@ -53,7 +55,7 @@ namespace osu.Game.Beatmaps
{ {
const double excess_length = 1000; const double excess_length = 1000;
var lastObject = Beatmap.HitObjects.LastOrDefault(); var lastObject = Beatmap?.HitObjects.LastOrDefault();
double length; double length;

View File

@ -6,6 +6,7 @@ using osu.Framework.Configuration;
using osu.Framework.Configuration.Tracking; using osu.Framework.Configuration.Tracking;
using osu.Framework.Extensions; using osu.Framework.Extensions;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Framework.Testing;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Select; using osu.Game.Screens.Select;
@ -13,6 +14,7 @@ using osu.Game.Screens.Select.Filter;
namespace osu.Game.Configuration namespace osu.Game.Configuration
{ {
[ExcludeFromDynamicCompile]
public class OsuConfigManager : IniConfigManager<OsuSetting> public class OsuConfigManager : IniConfigManager<OsuSetting>
{ {
protected override void InitialiseDefaults() protected override void InitialiseDefaults()
@ -231,6 +233,6 @@ namespace osu.Game.Configuration
UIHoldActivationDelay, UIHoldActivationDelay,
HitLighting, HitLighting,
MenuBackgroundSource, MenuBackgroundSource,
GameplayDisableWinKey GameplayDisableWinKey,
} }
} }

View File

@ -397,15 +397,24 @@ namespace osu.Game.Database
} }
} }
/// <summary>
/// Update an existing file, or create a new entry if not already part of the <paramref name="model"/>'s files.
/// </summary>
/// <param name="model">The item to operate on.</param>
/// <param name="file">The file model to be updated or added.</param>
/// <param name="contents">The new file contents.</param>
public void UpdateFile(TModel model, TFileModel file, Stream contents) public void UpdateFile(TModel model, TFileModel file, Stream contents)
{ {
using (var usage = ContextFactory.GetForWrite()) using (var usage = ContextFactory.GetForWrite())
{ {
// Dereference the existing file info, since the file model will be removed. // Dereference the existing file info, since the file model will be removed.
Files.Dereference(file.FileInfo); if (file.FileInfo != null)
{
Files.Dereference(file.FileInfo);
// Remove the file model. // Remove the file model.
usage.Context.Set<TFileModel>().Remove(file); usage.Context.Set<TFileModel>().Remove(file);
}
// Add the new file info and containing file model. // Add the new file info and containing file model.
model.Files.Remove(file); model.Files.Remove(file);

View File

@ -5,6 +5,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Cursor;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Framework.Input.StateChanges;
namespace osu.Game.Graphics.Cursor namespace osu.Game.Graphics.Cursor
{ {
@ -47,7 +48,10 @@ namespace osu.Game.Graphics.Cursor
{ {
base.Update(); base.Update();
if (!CanShowCursor) var lastMouseSource = inputManager.CurrentState.Mouse.LastSource;
bool hasValidInput = lastMouseSource != null && !(lastMouseSource is ISourcedFromTouch);
if (!hasValidInput || !CanShowCursor)
{ {
currentTarget?.Cursor?.Hide(); currentTarget?.Cursor?.Hide();
currentTarget = null; currentTarget = null;

View File

@ -25,7 +25,13 @@ namespace osu.Game.IO
this.subPath = subPath; this.subPath = subPath;
} }
protected virtual string MutatePath(string path) => !string.IsNullOrEmpty(subPath) ? Path.Combine(subPath, path) : path; protected virtual string MutatePath(string path)
{
if (path == null)
return null;
return !string.IsNullOrEmpty(subPath) ? Path.Combine(subPath, path) : path;
}
protected virtual void ChangeTargetStorage(Storage newStorage) protected virtual void ChangeTargetStorage(Storage newStorage)
{ {

View File

@ -0,0 +1,25 @@
// 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.Input;
namespace osu.Game.Input
{
public class GameIdleTracker : IdleTracker
{
private InputManager inputManager;
public GameIdleTracker(int time)
: base(time)
{
}
protected override void LoadComplete()
{
base.LoadComplete();
inputManager = GetContainingInputManager();
}
protected override bool AllowIdle => inputManager.FocusedDrawable == null;
}
}

View File

@ -720,24 +720,6 @@ namespace osu.Game
overlayContent.ChangeChildDepth(overlay, (float)-Clock.CurrentTime); overlayContent.ChangeChildDepth(overlay, (float)-Clock.CurrentTime);
} }
public class GameIdleTracker : IdleTracker
{
private InputManager inputManager;
public GameIdleTracker(int time)
: base(time)
{
}
protected override void LoadComplete()
{
base.LoadComplete();
inputManager = GetContainingInputManager();
}
protected override bool AllowIdle => inputManager.FocusedDrawable == null;
}
private void forwardLoggedErrorsToNotifications() private void forwardLoggedErrorsToNotifications()
{ {
int recentLogCount = 0; int recentLogCount = 0;
@ -993,10 +975,4 @@ namespace osu.Game
Exit(); Exit();
} }
} }
public enum ScorePresentType
{
Results,
Gameplay
}
} }

View File

@ -12,7 +12,7 @@ namespace osu.Game.Overlays.BeatmapListing
public BeatmapListingTitle() public BeatmapListingTitle()
{ {
Title = "beatmap listing"; Title = "beatmap listing";
Description = "Browse for new beatmaps"; Description = "browse for new beatmaps";
IconTexture = "Icons/Hexacons/beatmap"; IconTexture = "Icons/Hexacons/beatmap";
} }
} }

View File

@ -115,7 +115,7 @@ namespace osu.Game.Overlays.Changelog
public ChangelogHeaderTitle() public ChangelogHeaderTitle()
{ {
Title = "changelog"; Title = "changelog";
Description = "Track recent dev updates in the osu! ecosystem"; Description = "track recent dev updates in the osu! ecosystem";
IconTexture = "Icons/Hexacons/devtools"; IconTexture = "Icons/Hexacons/devtools";
} }
} }

View File

@ -30,7 +30,7 @@ namespace osu.Game.Overlays
{ {
public string IconTexture => "Icons/Hexacons/messaging"; public string IconTexture => "Icons/Hexacons/messaging";
public string Title => "chat"; public string Title => "chat";
public string Description => "Join the real-time discussion"; public string Description => "join the real-time discussion";
private const float textbox_height = 60; private const float textbox_height = 60;
private const float channel_selection_min_height = 0.3f; private const float channel_selection_min_height = 0.3f;

View File

@ -12,7 +12,7 @@ namespace osu.Game.Overlays.Dashboard
public DashboardTitle() public DashboardTitle()
{ {
Title = "dashboard"; Title = "dashboard";
Description = "View your friends and other information"; Description = "view your friends and other information";
IconTexture = "Icons/Hexacons/social"; IconTexture = "Icons/Hexacons/social";
} }
} }

View File

@ -57,7 +57,7 @@ namespace osu.Game.Overlays.News
public NewsHeaderTitle() public NewsHeaderTitle()
{ {
Title = "news"; Title = "news";
Description = "Get up-to-date on community happenings"; Description = "get up-to-date on community happenings";
IconTexture = "Icons/Hexacons/news"; IconTexture = "Icons/Hexacons/news";
} }
} }

View File

@ -19,8 +19,8 @@ namespace osu.Game.Overlays
public class NotificationOverlay : OsuFocusedOverlayContainer, INamedOverlayComponent public class NotificationOverlay : OsuFocusedOverlayContainer, INamedOverlayComponent
{ {
public string IconTexture => "Icons/Hexacons/notification"; public string IconTexture => "Icons/Hexacons/notification";
public string Title => "Notifications"; public string Title => "notifications";
public string Description => "Waiting for 'ya"; public string Description => "waiting for 'ya";
private const float width = 320; private const float width = 320;

View File

@ -29,7 +29,7 @@ namespace osu.Game.Overlays
{ {
public string IconTexture => "Icons/Hexacons/music"; public string IconTexture => "Icons/Hexacons/music";
public string Title => "now playing"; public string Title => "now playing";
public string Description => "Manage the currently playing track"; public string Description => "manage the currently playing track";
private const float player_height = 130; private const float player_height = 130;
private const float transition_length = 800; private const float transition_length = 800;

View File

@ -30,7 +30,7 @@ namespace osu.Game.Overlays.Rankings
public RankingsTitle() public RankingsTitle()
{ {
Title = "ranking"; Title = "ranking";
Description = "Find out who's the best right now"; Description = "find out who's the best right now";
IconTexture = "Icons/Hexacons/rankings"; IconTexture = "Icons/Hexacons/rankings";
} }
} }

View File

@ -17,7 +17,7 @@ namespace osu.Game.Overlays
{ {
public string IconTexture => "Icons/Hexacons/settings"; public string IconTexture => "Icons/Hexacons/settings";
public string Title => "settings"; public string Title => "settings";
public string Description => "Change the way osu! behaves"; public string Description => "change the way osu! behaves";
protected override IEnumerable<SettingsSection> CreateSections() => new SettingsSection[] protected override IEnumerable<SettingsSection> CreateSections() => new SettingsSection[]
{ {

View File

@ -17,8 +17,8 @@ namespace osu.Game.Overlays.Toolbar
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
TooltipMain = "Home"; TooltipMain = "home";
TooltipSub = "Return to the main menu"; TooltipSub = "return to the main menu";
SetIcon("Icons/Hexacons/home"); SetIcon("Icons/Hexacons/home");
} }
} }

View File

@ -27,7 +27,7 @@ namespace osu.Game.Overlays.Toolbar
var rInstance = value.CreateInstance(); var rInstance = value.CreateInstance();
ruleset.TooltipMain = rInstance.Description; ruleset.TooltipMain = rInstance.Description;
ruleset.TooltipSub = $"Play some {rInstance.Description}"; ruleset.TooltipSub = $"play some {rInstance.Description}";
ruleset.SetIcon(rInstance.CreateIcon()); ruleset.SetIcon(rInstance.CreateIcon());
} }

View File

@ -9,6 +9,7 @@ using System.Reflection;
using Newtonsoft.Json; using Newtonsoft.Json;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.IO.Serialization; using osu.Game.IO.Serialization;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
@ -18,6 +19,7 @@ namespace osu.Game.Rulesets.Mods
/// <summary> /// <summary>
/// The base class for gameplay modifiers. /// The base class for gameplay modifiers.
/// </summary> /// </summary>
[ExcludeFromDynamicCompile]
public abstract class Mod : IMod, IJsonSerializable public abstract class Mod : IMod, IJsonSerializable
{ {
/// <summary> /// <summary>

View File

@ -23,10 +23,12 @@ using osu.Game.Scoring;
using osu.Game.Skinning; using osu.Game.Skinning;
using osu.Game.Users; using osu.Game.Users;
using JetBrains.Annotations; using JetBrains.Annotations;
using osu.Framework.Testing;
using osu.Game.Screens.Ranking.Statistics; using osu.Game.Screens.Ranking.Statistics;
namespace osu.Game.Rulesets namespace osu.Game.Rulesets
{ {
[ExcludeFromDynamicCompile]
public abstract class Ruleset public abstract class Ruleset
{ {
public RulesetInfo RulesetInfo { get; internal set; } public RulesetInfo RulesetInfo { get; internal set; }

View File

@ -5,9 +5,11 @@ using System;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using Newtonsoft.Json; using Newtonsoft.Json;
using osu.Framework.Testing;
namespace osu.Game.Rulesets namespace osu.Game.Rulesets
{ {
[ExcludeFromDynamicCompile]
public class RulesetInfo : IEquatable<RulesetInfo> public class RulesetInfo : IEquatable<RulesetInfo>
{ {
public int? ID { get; set; } public int? ID { get; set; }

View File

@ -32,8 +32,6 @@ namespace osu.Game.Screens.Edit.Components.Menus
Height = 1, Height = 1,
Colour = Color4.White.Opacity(0.2f), Colour = Color4.White.Opacity(0.2f),
}); });
Current.Value = EditorScreenMode.Compose;
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]

View File

@ -28,6 +28,7 @@ using osu.Framework.Logging;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics.Cursor; using osu.Game.Graphics.Cursor;
using osu.Game.Input.Bindings; using osu.Game.Input.Bindings;
using osu.Game.Online.API;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Screens.Edit.Compose; using osu.Game.Screens.Edit.Compose;
using osu.Game.Screens.Edit.Setup; using osu.Game.Screens.Edit.Setup;
@ -72,6 +73,9 @@ namespace osu.Game.Screens.Edit
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
=> dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); => dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
[Resolved]
private IAPIProvider api { get; set; }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours, GameHost host) private void load(OsuColour colours, GameHost host)
{ {
@ -89,6 +93,14 @@ namespace osu.Game.Screens.Edit
// todo: remove caching of this and consume via editorBeatmap? // todo: remove caching of this and consume via editorBeatmap?
dependencies.Cache(beatDivisor); dependencies.Cache(beatDivisor);
bool isNewBeatmap = false;
if (Beatmap.Value is DummyWorkingBeatmap)
{
isNewBeatmap = true;
Beatmap.Value = beatmapManager.CreateNew(Ruleset.Value, api.LocalUser.Value);
}
try try
{ {
playableBeatmap = Beatmap.Value.GetPlayableBeatmap(Beatmap.Value.BeatmapInfo.Ruleset); playableBeatmap = Beatmap.Value.GetPlayableBeatmap(Beatmap.Value.BeatmapInfo.Ruleset);
@ -101,9 +113,8 @@ namespace osu.Game.Screens.Edit
return; return;
} }
AddInternal(editorBeatmap = new EditorBeatmap(playableBeatmap)); AddInternal(editorBeatmap = new EditorBeatmap(playableBeatmap, Beatmap.Value.Skin));
dependencies.CacheAs(editorBeatmap); dependencies.CacheAs(editorBeatmap);
changeHandler = new EditorChangeHandler(editorBeatmap); changeHandler = new EditorChangeHandler(editorBeatmap);
dependencies.CacheAs<IEditorChangeHandler>(changeHandler); dependencies.CacheAs<IEditorChangeHandler>(changeHandler);
@ -148,6 +159,7 @@ namespace osu.Game.Screens.Edit
Anchor = Anchor.CentreLeft, Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft, Origin = Anchor.CentreLeft,
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Mode = { Value = isNewBeatmap ? EditorScreenMode.SongSetup : EditorScreenMode.Compose },
Items = new[] Items = new[]
{ {
new MenuItem("File") new MenuItem("File")
@ -399,7 +411,14 @@ namespace osu.Game.Screens.Edit
clock.SeekForward(!clock.IsRunning, amount); clock.SeekForward(!clock.IsRunning, amount);
} }
private void saveBeatmap() => beatmapManager.Save(playableBeatmap.BeatmapInfo, editorBeatmap); private void saveBeatmap()
{
// apply any set-level metadata changes.
beatmapManager.Update(playableBeatmap.BeatmapInfo.BeatmapSet);
// save the loaded beatmap's data stream.
beatmapManager.Save(playableBeatmap.BeatmapInfo, editorBeatmap, editorBeatmap.BeatmapSkin);
}
private void exportBeatmap() private void exportBeatmap()
{ {

View File

@ -15,6 +15,7 @@ using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Beatmaps.Timing; using osu.Game.Beatmaps.Timing;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Skinning;
namespace osu.Game.Screens.Edit namespace osu.Game.Screens.Edit
{ {
@ -47,6 +48,8 @@ namespace osu.Game.Screens.Edit
public readonly IBeatmap PlayableBeatmap; public readonly IBeatmap PlayableBeatmap;
public readonly ISkin BeatmapSkin;
[Resolved] [Resolved]
private BindableBeatDivisor beatDivisor { get; set; } private BindableBeatDivisor beatDivisor { get; set; }
@ -54,9 +57,10 @@ namespace osu.Game.Screens.Edit
private readonly Dictionary<HitObject, Bindable<double>> startTimeBindables = new Dictionary<HitObject, Bindable<double>>(); private readonly Dictionary<HitObject, Bindable<double>> startTimeBindables = new Dictionary<HitObject, Bindable<double>>();
public EditorBeatmap(IBeatmap playableBeatmap) public EditorBeatmap(IBeatmap playableBeatmap, ISkin beatmapSkin = null)
{ {
PlayableBeatmap = playableBeatmap; PlayableBeatmap = playableBeatmap;
BeatmapSkin = beatmapSkin;
beatmapProcessor = playableBeatmap.BeatmapInfo.Ruleset?.CreateInstance().CreateBeatmapProcessor(PlayableBeatmap); beatmapProcessor = playableBeatmap.BeatmapInfo.Ruleset?.CreateInstance().CreateBeatmapProcessor(PlayableBeatmap);

View File

@ -85,7 +85,7 @@ namespace osu.Game.Screens.Edit
using (var stream = new MemoryStream()) using (var stream = new MemoryStream())
{ {
using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true))
new LegacyBeatmapEncoder(editorBeatmap).Encode(sw); new LegacyBeatmapEncoder(editorBeatmap, editorBeatmap.BeatmapSkin).Encode(sw);
savedStates.Add(stream.ToArray()); savedStates.Add(stream.ToArray());
} }

View File

@ -1,30 +1,30 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Beatmaps.Timing; using osu.Game.Beatmaps.Timing;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Overlays.Settings; using osu.Game.Overlays.Settings;
namespace osu.Game.Screens.Edit.Timing namespace osu.Game.Screens.Edit.Timing
{ {
internal class TimingSection : Section<TimingControlPoint> internal class TimingSection : Section<TimingControlPoint>
{ {
private SettingsSlider<double> bpm; private SettingsSlider<double> bpmSlider;
private SettingsEnumDropdown<TimeSignatures> timeSignature; private SettingsEnumDropdown<TimeSignatures> timeSignature;
private BPMTextBox bpmTextEntry;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
Flow.AddRange(new Drawable[] Flow.AddRange(new Drawable[]
{ {
bpm = new BPMSlider bpmTextEntry = new BPMTextBox(),
{ bpmSlider = new BPMSlider(),
Bindable = new TimingControlPoint().BeatLengthBindable,
LabelText = "BPM",
},
timeSignature = new SettingsEnumDropdown<TimeSignatures> timeSignature = new SettingsEnumDropdown<TimeSignatures>
{ {
LabelText = "Time Signature" LabelText = "Time Signature"
@ -36,7 +36,8 @@ namespace osu.Game.Screens.Edit.Timing
{ {
if (point.NewValue != null) if (point.NewValue != null)
{ {
bpm.Bindable = point.NewValue.BeatLengthBindable; bpmSlider.Bindable = point.NewValue.BeatLengthBindable;
bpmTextEntry.Bindable = point.NewValue.BeatLengthBindable;
timeSignature.Bindable = point.NewValue.TimeSignatureBindable; timeSignature.Bindable = point.NewValue.TimeSignatureBindable;
} }
} }
@ -52,34 +53,88 @@ namespace osu.Game.Screens.Edit.Timing
}; };
} }
private class BPMTextBox : LabelledTextBox
{
private readonly BindableNumber<double> beatLengthBindable = new TimingControlPoint().BeatLengthBindable;
public BPMTextBox()
{
Label = "BPM";
OnCommit += (val, isNew) =>
{
if (!isNew) return;
if (double.TryParse(Current.Value, out double doubleVal))
{
try
{
beatLengthBindable.Value = beatLengthToBpm(doubleVal);
}
catch
{
// will restore the previous text value on failure.
beatLengthBindable.TriggerChange();
}
}
};
beatLengthBindable.BindValueChanged(val =>
{
Current.Value = beatLengthToBpm(val.NewValue).ToString("N2");
}, true);
}
public Bindable<double> Bindable
{
get => beatLengthBindable;
set
{
// incoming will be beat length, not bpm
beatLengthBindable.UnbindBindings();
beatLengthBindable.BindTo(value);
}
}
}
private class BPMSlider : SettingsSlider<double> private class BPMSlider : SettingsSlider<double>
{ {
private readonly BindableDouble beatLengthBindable = new BindableDouble(); private const double sane_minimum = 60;
private const double sane_maximum = 240;
private BindableDouble bpmBindable; private readonly BindableNumber<double> beatLengthBindable = new TimingControlPoint().BeatLengthBindable;
private readonly BindableDouble bpmBindable = new BindableDouble();
public BPMSlider()
{
beatLengthBindable.BindValueChanged(beatLength => updateCurrent(beatLengthToBpm(beatLength.NewValue)), true);
bpmBindable.BindValueChanged(bpm => bpmBindable.Default = beatLengthBindable.Value = beatLengthToBpm(bpm.NewValue));
base.Bindable = bpmBindable;
}
public override Bindable<double> Bindable public override Bindable<double> Bindable
{ {
get => base.Bindable; get => base.Bindable;
set set
{ {
// incoming will be beatlength // incoming will be beat length, not bpm
beatLengthBindable.UnbindBindings(); beatLengthBindable.UnbindBindings();
beatLengthBindable.BindTo(value); beatLengthBindable.BindTo(value);
base.Bindable = bpmBindable = new BindableDouble(beatLengthToBpm(beatLengthBindable.Value))
{
MinValue = beatLengthToBpm(beatLengthBindable.MaxValue),
MaxValue = beatLengthToBpm(beatLengthBindable.MinValue),
Default = beatLengthToBpm(beatLengthBindable.Default),
};
bpmBindable.BindValueChanged(bpm => beatLengthBindable.Value = beatLengthToBpm(bpm.NewValue));
} }
} }
private double beatLengthToBpm(double beatLength) => 60000 / beatLength; private void updateCurrent(double newValue)
{
// we use a more sane range for the slider display unless overridden by the user.
// if a value comes in outside our range, we should expand temporarily.
bpmBindable.MinValue = Math.Min(newValue, sane_minimum);
bpmBindable.MaxValue = Math.Max(newValue, sane_maximum);
bpmBindable.Value = newValue;
}
} }
private static double beatLengthToBpm(double beatLength) => 60000 / beatLength;
} }
} }

View File

@ -190,7 +190,7 @@ namespace osu.Game.Screens.Menu
{ {
"You can press Ctrl-T anywhere in the game to toggle the toolbar!", "You can press Ctrl-T anywhere in the game to toggle the toolbar!",
"You can press Ctrl-O anywhere in the game to access options!", "You can press Ctrl-O anywhere in the game to access options!",
"All settings are dynamic and take effect in real-time. Try changing the skin while playing!", "All settings are dynamic and take effect in real-time. Try pausing and changing the skin while playing!",
"New features are coming online every update. Make sure to stay up-to-date!", "New features are coming online every update. Make sure to stay up-to-date!",
"If you find the UI too large or small, try adjusting UI scale in settings!", "If you find the UI too large or small, try adjusting UI scale in settings!",
"Try adjusting the \"Screen Scaling\" mode to change your gameplay or UI area, even in fullscreen!", "Try adjusting the \"Screen Scaling\" mode to change your gameplay or UI area, even in fullscreen!",

View File

@ -98,7 +98,11 @@ namespace osu.Game.Screens.Menu
{ {
buttons = new ButtonSystem buttons = new ButtonSystem
{ {
OnEdit = delegate { this.Push(new Editor()); }, OnEdit = delegate
{
Beatmap.SetDefault();
this.Push(new Editor());
},
OnSolo = onSolo, OnSolo = onSolo,
OnMulti = delegate { this.Push(new Multiplayer()); }, OnMulti = delegate { this.Push(new Multiplayer()); },
OnExit = confirmAndExit, OnExit = confirmAndExit,

View File

@ -203,6 +203,10 @@ namespace osu.Game.Screens.Play
skipOverlay.Hide(); skipOverlay.Hide();
} }
DrawableRuleset.IsPaused.BindValueChanged(_ => updateOverlayActivationMode());
DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updateOverlayActivationMode());
breakTracker.IsBreakTime.BindValueChanged(_ => updateOverlayActivationMode());
DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true); DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true);
// bind clock into components that require it // bind clock into components that require it
@ -347,6 +351,16 @@ namespace osu.Game.Screens.Play
HUDOverlay.KeyCounter.IsCounting = !isBreakTime.NewValue; HUDOverlay.KeyCounter.IsCounting = !isBreakTime.NewValue;
} }
private void updateOverlayActivationMode()
{
bool canTriggerOverlays = DrawableRuleset.IsPaused.Value || breakTracker.IsBreakTime.Value;
if (DrawableRuleset.HasReplayLoaded.Value || canTriggerOverlays)
OverlayActivationMode.Value = OverlayActivation.UserTriggered;
else
OverlayActivationMode.Value = OverlayActivation.Disabled;
}
private void updatePauseOnFocusLostState() => private void updatePauseOnFocusLostState() =>
HUDOverlay.HoldToQuit.PauseOnFocusLost = PauseOnFocusLost HUDOverlay.HoldToQuit.PauseOnFocusLost = PauseOnFocusLost
&& !DrawableRuleset.HasReplayLoaded.Value && !DrawableRuleset.HasReplayLoaded.Value
@ -640,6 +654,8 @@ namespace osu.Game.Screens.Play
musicController.ResetTrackAdjustments(); musicController.ResetTrackAdjustments();
foreach (var mod in Mods.Value.OfType<IApplicableToTrack>()) foreach (var mod in Mods.Value.OfType<IApplicableToTrack>())
mod.ApplyToTrack(musicController.CurrentTrack); mod.ApplyToTrack(musicController.CurrentTrack);
updateOverlayActivationMode();
} }
public override void OnSuspending(IScreen next) public override void OnSuspending(IScreen next)

View File

@ -0,0 +1,11 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
namespace osu.Game.Screens
{
public enum ScorePresentType
{
Results,
Gameplay
}
}

View File

@ -23,7 +23,7 @@ namespace osu.Game.Skinning
public readonly int Keys; public readonly int Keys;
public Dictionary<string, Color4> CustomColours { get; set; } = new Dictionary<string, Color4>(); public Dictionary<string, Color4> CustomColours { get; } = new Dictionary<string, Color4>();
public Dictionary<string, string> ImageLookups = new Dictionary<string, string>(); public Dictionary<string, string> ImageLookups = new Dictionary<string, string>();

View File

@ -45,7 +45,7 @@ namespace osu.Game.Skinning
public void AddComboColours(params Color4[] colours) => comboColours.AddRange(colours); public void AddComboColours(params Color4[] colours) => comboColours.AddRange(colours);
public Dictionary<string, Color4> CustomColours { get; set; } = new Dictionary<string, Color4>(); public Dictionary<string, Color4> CustomColours { get; } = new Dictionary<string, Color4>();
public readonly Dictionary<string, string> ConfigDictionary = new Dictionary<string, string>(); public readonly Dictionary<string, string> ConfigDictionary = new Dictionary<string, string>();
} }

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Runtime.CompilerServices;
using osu.Framework.Platform; using osu.Framework.Platform;
namespace osu.Game.Tests namespace osu.Game.Tests
@ -10,8 +11,15 @@ namespace osu.Game.Tests
/// </summary> /// </summary>
public class CleanRunHeadlessGameHost : HeadlessGameHost public class CleanRunHeadlessGameHost : HeadlessGameHost
{ {
public CleanRunHeadlessGameHost(string gameName = @"", bool bindIPC = false, bool realtime = true) /// <summary>
: base(gameName, bindIPC, realtime) /// Create a new instance.
/// </summary>
/// <param name="gameSuffix">An optional suffix which will isolate this host from others called from the same method source.</param>
/// <param name="bindIPC">Whether to bind IPC channels.</param>
/// <param name="realtime">Whether the host should be forced to run in realtime, rather than accelerated test time.</param>
/// <param name="callingMethodName">The name of the calling method, used for test file isolation and clean-up.</param>
public CleanRunHeadlessGameHost(string gameSuffix = @"", bool bindIPC = false, bool realtime = true, [CallerMemberName] string callingMethodName = @"")
: base(callingMethodName + gameSuffix, bindIPC, realtime)
{ {
} }

View File

@ -30,6 +30,7 @@ using osu.Game.Tests.Beatmaps;
namespace osu.Game.Tests.Visual namespace osu.Game.Tests.Visual
{ {
[ExcludeFromDynamicCompile]
public abstract class OsuTestScene : TestScene public abstract class OsuTestScene : TestScene
{ {
protected Bindable<WorkingBeatmap> Beatmap { get; private set; } protected Bindable<WorkingBeatmap> Beatmap { get; private set; }

View File

@ -24,7 +24,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="ppy.osu.Framework" Version="2020.904.0" /> <PackageReference Include="ppy.osu.Framework" Version="2020.907.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.904.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2020.904.0" />
<PackageReference Include="Sentry" Version="2.1.6" /> <PackageReference Include="Sentry" Version="2.1.6" />
<PackageReference Include="SharpCompress" Version="0.26.0" /> <PackageReference Include="SharpCompress" Version="0.26.0" />

View File

@ -70,7 +70,7 @@
<Reference Include="System.Net.Http" /> <Reference Include="System.Net.Http" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="ppy.osu.Framework.iOS" Version="2020.904.0" /> <PackageReference Include="ppy.osu.Framework.iOS" Version="2020.907.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.904.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2020.904.0" />
</ItemGroup> </ItemGroup>
<!-- Xamarin.iOS does not automatically handle transitive dependencies from NuGet packages. --> <!-- Xamarin.iOS does not automatically handle transitive dependencies from NuGet packages. -->
@ -80,7 +80,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="ppy.osu.Framework" Version="2020.904.0" /> <PackageReference Include="ppy.osu.Framework" Version="2020.907.0" />
<PackageReference Include="SharpCompress" Version="0.26.0" /> <PackageReference Include="SharpCompress" Version="0.26.0" />
<PackageReference Include="NUnit" Version="3.12.0" /> <PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="SharpRaven" Version="2.4.0" /> <PackageReference Include="SharpRaven" Version="2.4.0" />