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

Merge branch 'master' into fix-tags-overflow

This commit is contained in:
Salman Ahmed 2022-08-01 12:40:43 +03:00 committed by GitHub
commit 2a127c6ef8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
72 changed files with 490 additions and 225 deletions

View File

@ -21,7 +21,11 @@ namespace osu.Desktop
{
public static class Program
{
#if DEBUG
private const string base_game_name = @"osu-development";
#else
private const string base_game_name = @"osu";
#endif
private static LegacyTcpIpcProvider legacyIpc;

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests.Mods

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System.Collections.Generic;
using NUnit.Framework;
using osu.Game.Beatmaps;

View File

@ -1,10 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System.Linq;
using NUnit.Framework;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
@ -35,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
private void runSpmTest(Mod mod)
{
SpinnerSpmCalculator spmCalculator = null;
SpinnerSpmCalculator? spmCalculator = null;
CreateModTest(new ModTestData
{
@ -61,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
return spmCalculator != null;
});
AddUntilStep("SPM is correct", () => Precision.AlmostEquals(spmCalculator.Result.Value, 477, 5));
AddUntilStep("SPM is correct", () => Precision.AlmostEquals(spmCalculator.AsNonNull().Result.Value, 477, 5));
}
}
}

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using NUnit.Framework;
using osu.Framework.Utils;
using osu.Game.Rulesets.Osu.Mods;

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
@ -162,7 +160,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
private class TestOsuModHidden : OsuModHidden
{
public new HitObject FirstObject => base.FirstObject;
public new HitObject? FirstObject => base.FirstObject;
}
}
}

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using NUnit.Framework;
using osu.Game.Rulesets.Osu.Mods;

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System.Linq;
using NUnit.Framework;
using osu.Framework.Testing;
@ -33,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
[Test]
public void TestModCopy()
{
OsuModMuted muted = null;
OsuModMuted muted = null!;
AddStep("create inversed mod", () => muted = new OsuModMuted
{

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using NUnit.Framework;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using System.Collections.Generic;
using System.Linq;
@ -30,8 +28,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
[Test]
public void TestSpinnerAutoCompleted()
{
DrawableSpinner spinner = null;
JudgementResult lastResult = null;
DrawableSpinner? spinner = null;
JudgementResult? lastResult = null;
CreateModTest(new ModTestData
{
@ -63,11 +61,11 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
[TestCase(null)]
[TestCase(typeof(OsuModDoubleTime))]
[TestCase(typeof(OsuModHalfTime))]
public void TestSpinRateUnaffectedByMods(Type additionalModType)
public void TestSpinRateUnaffectedByMods(Type? additionalModType)
{
var mods = new List<Mod> { new OsuModSpunOut() };
if (additionalModType != null)
mods.Add((Mod)Activator.CreateInstance(additionalModType));
mods.Add((Mod)Activator.CreateInstance(additionalModType)!);
CreateModTest(new ModTestData
{
@ -96,7 +94,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
[Test]
public void TestSpinnerGetsNoBonusScore()
{
DrawableSpinner spinner = null;
DrawableSpinner? spinner = null;
List<JudgementResult> results = new List<JudgementResult>();
CreateModTest(new ModTestData

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
namespace osu.Game.Rulesets.Osu.Mods
{
/// <summary>

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
namespace osu.Game.Rulesets.Osu.Mods
{
/// <summary>

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using osu.Framework.Bindables;
using osu.Framework.Graphics;

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using System.Collections.Generic;
using System.Linq;
@ -30,11 +28,11 @@ namespace osu.Game.Rulesets.Osu.Mods
public bool RestartOnFail => false;
private OsuInputManager inputManager;
private OsuInputManager inputManager = null!;
private IFrameStableClock gameplayClock;
private IFrameStableClock gameplayClock = null!;
private List<OsuReplayFrame> replayFrames;
private List<OsuReplayFrame> replayFrames = null!;
private int currentFrame;

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using System.Collections.Generic;
using System.Linq;

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
@ -33,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
public override Type[] IncompatibleMods => new[] { typeof(OsuModFlashlight) };
private DrawableOsuBlinds blinds;
private DrawableOsuBlinds blinds = null!;
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
{
@ -55,9 +53,12 @@ namespace osu.Game.Rulesets.Osu.Mods
/// <summary>
/// Black background boxes behind blind panel textures.
/// </summary>
private Box blackBoxLeft, blackBoxRight;
private Box blackBoxLeft = null!, blackBoxRight = null!;
private Drawable panelLeft, panelRight, bgPanelLeft, bgPanelRight;
private Drawable panelLeft = null!;
private Drawable panelRight = null!;
private Drawable bgPanelLeft = null!;
private Drawable bgPanelRight = null!;
private readonly Beatmap<OsuHitObject> beatmap;

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using System.Collections.Generic;
using System.Linq;

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using System.Linq;
using osu.Framework.Bindables;

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Osu.Mods

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
using osu.Game.Configuration;

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Configuration;

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Osu.Mods

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Osu.Mods

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using System.Linq;
using osu.Framework.Bindables;
@ -53,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override float DefaultFlashlightSize => 180;
private OsuFlashlight flashlight;
private OsuFlashlight flashlight = null!;
protected override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(this);

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
using osu.Game.Configuration;

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Osu.Mods

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using System.Linq;
using osu.Game.Rulesets.Mods;

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using System.Diagnostics;
using System.Linq;

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
@ -28,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModRelax), typeof(OsuModRepel) };
private IFrameStableClock gameplayClock;
private IFrameStableClock gameplayClock = null!;
[SettingSource("Attraction strength", "How strong the pull is.", 0)]
public BindableFloat AttractionStrength { get; } = new BindableFloat(0.5f)

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using osu.Framework.Bindables;
using osu.Game.Configuration;

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Objects;

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Objects;

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using System.Linq;
using osu.Game.Rulesets.Mods;

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using System.Linq;
using osu.Framework.Bindables;
@ -21,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public override string Description => "Where's the cursor?";
private PeriodTracker spinnerPeriods;
private PeriodTracker spinnerPeriods = null!;
[SettingSource(
"Hidden at combo",

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using osu.Framework.Bindables;
using osu.Framework.Graphics;

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using System.Linq;
using osu.Game.Rulesets.Mods;

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using System.Collections.Generic;
using System.Diagnostics;
@ -31,9 +29,9 @@ namespace osu.Game.Rulesets.Osu.Mods
private bool isDownState;
private bool wasLeft;
private OsuInputManager osuInputManager;
private OsuInputManager osuInputManager = null!;
private ReplayState<OsuAction> state;
private ReplayState<OsuAction> state = null!;
private double lastStateChangeTime;
private bool hasReplay;
@ -134,7 +132,7 @@ namespace osu.Game.Rulesets.Osu.Mods
wasLeft = !wasLeft;
}
state?.Apply(osuInputManager.CurrentState, osuInputManager);
state.Apply(osuInputManager.CurrentState, osuInputManager);
}
}
}

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using System.Linq;
using System.Threading;

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using System.Linq;
using osu.Game.Rulesets.Mods;

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using System.Collections.Generic;
using System.Linq;
@ -96,11 +94,7 @@ namespace osu.Game.Rulesets.Osu.Mods
#region Private Fields
private ControlPointInfo controlPointInfo;
private List<OsuHitObject> originalHitObjects;
private Random rng;
private ControlPointInfo controlPointInfo = null!;
#endregion
@ -171,16 +165,17 @@ namespace osu.Game.Rulesets.Osu.Mods
public override void ApplyToBeatmap(IBeatmap beatmap)
{
Seed.Value ??= RNG.Next();
rng = new Random(Seed.Value.Value);
var rng = new Random(Seed.Value.Value);
var osuBeatmap = (OsuBeatmap)beatmap;
if (osuBeatmap.HitObjects.Count == 0) return;
controlPointInfo = osuBeatmap.ControlPointInfo;
originalHitObjects = osuBeatmap.HitObjects.OrderBy(x => x.StartTime).ToList();
var hitObjects = generateBeats(osuBeatmap)
var originalHitObjects = osuBeatmap.HitObjects.OrderBy(x => x.StartTime).ToList();
var hitObjects = generateBeats(osuBeatmap, originalHitObjects)
.Select(beat =>
{
var newCircle = new HitCircle();
@ -189,18 +184,18 @@ namespace osu.Game.Rulesets.Osu.Mods
return (OsuHitObject)newCircle;
}).ToList();
addHitSamples(hitObjects);
addHitSamples(hitObjects, originalHitObjects);
fixComboInfo(hitObjects);
fixComboInfo(hitObjects, originalHitObjects);
randomizeCirclePos(hitObjects);
randomizeCirclePos(hitObjects, rng);
osuBeatmap.HitObjects = hitObjects;
base.ApplyToBeatmap(beatmap);
}
private IEnumerable<double> generateBeats(IBeatmap beatmap)
private IEnumerable<double> generateBeats(IBeatmap beatmap, IReadOnlyCollection<OsuHitObject> originalHitObjects)
{
double startTime = originalHitObjects.First().StartTime;
double endTime = originalHitObjects.Last().GetEndTime();
@ -213,7 +208,7 @@ namespace osu.Game.Rulesets.Osu.Mods
// Remove beats before startTime
.Where(beat => almostBigger(beat, startTime))
// Remove beats during breaks
.Where(beat => !isInsideBreakPeriod(beatmap.Breaks, beat))
.Where(beat => !isInsideBreakPeriod(originalHitObjects, beatmap.Breaks, beat))
.ToList();
// Remove beats that are too close to the next one (e.g. due to timing point changes)
@ -228,7 +223,7 @@ namespace osu.Game.Rulesets.Osu.Mods
return beats;
}
private void addHitSamples(IEnumerable<OsuHitObject> hitObjects)
private void addHitSamples(IEnumerable<OsuHitObject> hitObjects, List<OsuHitObject> originalHitObjects)
{
foreach (var obj in hitObjects)
{
@ -240,7 +235,7 @@ namespace osu.Game.Rulesets.Osu.Mods
}
}
private void fixComboInfo(List<OsuHitObject> hitObjects)
private void fixComboInfo(List<OsuHitObject> hitObjects, List<OsuHitObject> originalHitObjects)
{
// Copy combo indices from an original object at the same time or from the closest preceding object
// (Objects lying between two combos are assumed to belong to the preceding combo)
@ -274,7 +269,7 @@ namespace osu.Game.Rulesets.Osu.Mods
}
}
private void randomizeCirclePos(IReadOnlyList<OsuHitObject> hitObjects)
private void randomizeCirclePos(IReadOnlyList<OsuHitObject> hitObjects, Random rng)
{
if (hitObjects.Count == 0) return;
@ -355,9 +350,10 @@ namespace osu.Game.Rulesets.Osu.Mods
/// The given time is also considered to be inside a break if it is earlier than the
/// start time of the first original hit object after the break.
/// </remarks>
/// <param name="originalHitObjects">Hit objects order by time.</param>
/// <param name="breaks">The breaks of the beatmap.</param>
/// <param name="time">The time to be checked.</param>=
private bool isInsideBreakPeriod(IEnumerable<BreakPeriod> breaks, double time)
private bool isInsideBreakPeriod(IReadOnlyCollection<OsuHitObject> originalHitObjects, IEnumerable<BreakPeriod> breaks, double time)
{
return breaks.Any(breakPeriod =>
{
@ -405,7 +401,7 @@ namespace osu.Game.Rulesets.Osu.Mods
/// <param name="hitObjects">The list of hit objects in a beatmap, ordered by StartTime</param>
/// <param name="time">The point in time to get samples for</param>
/// <returns>Hit samples</returns>
private IList<HitSampleInfo> getSamplesAtTime(IEnumerable<OsuHitObject> hitObjects, double time)
private IList<HitSampleInfo>? getSamplesAtTime(IEnumerable<OsuHitObject> hitObjects, double time)
{
// Get a hit object that
// either has StartTime equal to the target time

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Osu.Mods

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
@ -59,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Mods
}
}
private void applyCirclePieceState(DrawableOsuHitObject hitObject, IDrawable hitCircle = null)
private void applyCirclePieceState(DrawableOsuHitObject hitObject, IDrawable? hitCircle = null)
{
var h = hitObject.HitObject;
using (hitObject.BeginAbsoluteSequence(h.StartTime - h.TimePreempt))

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System.Reflection;
using NUnit.Framework;
using osu.Framework.IO.Stores;
@ -17,7 +15,6 @@ namespace osu.Game.Rulesets.Taiko.Tests
protected override IResourceStore<byte[]> RulesetResources => new DllResourceStore(Assembly.GetAssembly(typeof(TestSceneTaikoHitObjectSamples)));
[TestCase("taiko-normal-hitnormal")]
[TestCase("normal-hitnormal")]
[TestCase("hitnormal")]
public void TestDefaultCustomSampleFromBeatmap(string expectedSample)
{
@ -29,7 +26,6 @@ namespace osu.Game.Rulesets.Taiko.Tests
}
[TestCase("taiko-normal-hitnormal")]
[TestCase("normal-hitnormal")]
[TestCase("hitnormal")]
public void TestDefaultCustomSampleFromUserSkinFallback(string expectedSample)
{
@ -41,7 +37,6 @@ namespace osu.Game.Rulesets.Taiko.Tests
}
[TestCase("taiko-normal-hitnormal2")]
[TestCase("normal-hitnormal2")]
public void TestUserSkinLookupIgnoresSampleBank(string unwantedSample)
{
SetupSkins(string.Empty, unwantedSample);

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using System.Collections.Generic;
using osu.Framework.Audio.Sample;
@ -24,7 +22,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
hasExplosion = new Lazy<bool>(() => GetTexture(getHitName(TaikoSkinComponents.TaikoExplosionGreat)) != null);
}
public override Drawable GetDrawableComponent(ISkinComponent component)
public override Drawable? GetDrawableComponent(ISkinComponent component)
{
if (component is GameplaySkinComponent<HitResult>)
{
@ -151,7 +149,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
throw new ArgumentOutOfRangeException(nameof(component), $"Invalid component type: {component}");
}
public override ISample GetSample(ISampleInfo sampleInfo)
public override ISample? GetSample(ISampleInfo sampleInfo)
{
if (sampleInfo is HitSampleInfo hitSampleInfo)
return base.GetSample(new LegacyTaikoSampleInfo(hitSampleInfo));
@ -173,9 +171,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
{
foreach (string name in base.LookupNames)
yield return name.Insert(name.LastIndexOf('/') + 1, "taiko-");
foreach (string name in base.LookupNames)
yield return name;
}
}
}

View File

@ -702,6 +702,8 @@ namespace osu.Game.Tests.Database
var firstImport = await importer.Import(new ImportTask(pathMissingOneBeatmap));
Assert.That(firstImport, Is.Not.Null);
realm.Run(r => r.Refresh());
Assert.That(realm.Realm.All<BeatmapSetInfo>().Where(s => !s.DeletePending), Has.Count.EqualTo(1));
Assert.That(realm.Realm.All<BeatmapSetInfo>().First(s => !s.DeletePending).Beatmaps, Has.Count.EqualTo(11));
@ -709,6 +711,8 @@ namespace osu.Game.Tests.Database
var secondImport = await importer.Import(new ImportTask(pathOriginal));
Assert.That(secondImport, Is.Not.Null);
realm.Run(r => r.Refresh());
Assert.That(realm.Realm.All<BeatmapInfo>(), Has.Count.EqualTo(23));
Assert.That(realm.Realm.All<BeatmapSetInfo>(), Has.Count.EqualTo(2));

View File

@ -0,0 +1,128 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores;
using osu.Game.Audio;
using osu.Game.IO;
using osu.Game.IO.Archives;
using osu.Game.Screens.Play.HUD;
using osu.Game.Screens.Play.HUD.HitErrorMeters;
using osu.Game.Skinning;
using osu.Game.Tests.Resources;
namespace osu.Game.Tests.Skins
{
/// <summary>
/// Test that the main components (which are serialised based on namespace/class name)
/// remain compatible with any changes.
/// </summary>
/// <remarks>
/// If this test breaks, check any naming or class structure changes.
/// Migration rules may need to be added to <see cref="Skin"/>.
/// </remarks>
[TestFixture]
public class SkinDeserialisationTest
{
private static readonly string[] available_skins =
{
// Covers song progress before namespace changes, and most other components.
"Archives/modified-default-20220723.osk",
"Archives/modified-classic-20220723.osk",
// Covers legacy song progress, UR counter, colour hit error metre.
"Archives/modified-classic-20220801.osk"
};
/// <summary>
/// If this test fails, new test resources should be added to include new components.
/// </summary>
[Test]
public void TestSkinnableComponentsCoveredByDeserialisationTests()
{
HashSet<Type> instantiatedTypes = new HashSet<Type>();
foreach (string oskFile in available_skins)
{
using (var stream = TestResources.OpenResource(oskFile))
using (var storage = new ZipArchiveReader(stream))
{
var skin = new TestSkin(new SkinInfo(), null, storage);
foreach (var target in skin.DrawableComponentInfo)
{
foreach (var info in target.Value)
instantiatedTypes.Add(info.Type);
}
}
}
var editableTypes = SkinnableInfo.GetAllAvailableDrawables().Where(t => (Activator.CreateInstance(t) as ISkinnableDrawable)?.IsEditable == true);
Assert.That(instantiatedTypes, Is.EquivalentTo(editableTypes));
}
[Test]
public void TestDeserialiseModifiedDefault()
{
using (var stream = TestResources.OpenResource("Archives/modified-default-20220723.osk"))
using (var storage = new ZipArchiveReader(stream))
{
var skin = new TestSkin(new SkinInfo(), null, storage);
Assert.That(skin.DrawableComponentInfo, Has.Count.EqualTo(2));
Assert.That(skin.DrawableComponentInfo[SkinnableTarget.MainHUDComponents], Has.Length.EqualTo(9));
}
}
[Test]
public void TestDeserialiseModifiedClassic()
{
using (var stream = TestResources.OpenResource("Archives/modified-classic-20220723.osk"))
using (var storage = new ZipArchiveReader(stream))
{
var skin = new TestSkin(new SkinInfo(), null, storage);
Assert.That(skin.DrawableComponentInfo, Has.Count.EqualTo(2));
Assert.That(skin.DrawableComponentInfo[SkinnableTarget.MainHUDComponents], Has.Length.EqualTo(6));
Assert.That(skin.DrawableComponentInfo[SkinnableTarget.SongSelect], Has.Length.EqualTo(1));
var skinnableInfo = skin.DrawableComponentInfo[SkinnableTarget.SongSelect].First();
Assert.That(skinnableInfo.Type, Is.EqualTo(typeof(SkinnableSprite)));
Assert.That(skinnableInfo.Settings.First().Key, Is.EqualTo("sprite_name"));
Assert.That(skinnableInfo.Settings.First().Value, Is.EqualTo("ppy_logo-2.png"));
}
using (var stream = TestResources.OpenResource("Archives/modified-classic-20220801.osk"))
using (var storage = new ZipArchiveReader(stream))
{
var skin = new TestSkin(new SkinInfo(), null, storage);
Assert.That(skin.DrawableComponentInfo[SkinnableTarget.MainHUDComponents], Has.Length.EqualTo(8));
Assert.That(skin.DrawableComponentInfo[SkinnableTarget.MainHUDComponents].Select(i => i.Type), Contains.Item(typeof(UnstableRateCounter)));
Assert.That(skin.DrawableComponentInfo[SkinnableTarget.MainHUDComponents].Select(i => i.Type), Contains.Item(typeof(ColourHitErrorMeter)));
Assert.That(skin.DrawableComponentInfo[SkinnableTarget.MainHUDComponents].Select(i => i.Type), Contains.Item(typeof(LegacySongProgress)));
}
}
private class TestSkin : Skin
{
public TestSkin(SkinInfo skin, IStorageResourceProvider? resources, IResourceStore<byte[]>? storage = null, string configurationFilename = "skin.ini")
: base(skin, resources, storage, configurationFilename)
{
}
public override Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => throw new NotImplementedException();
public override IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => throw new NotImplementedException();
public override ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
}
}
}

View File

@ -136,6 +136,20 @@ namespace osu.Game.Tests.Visual.Editing
AddAssert("track is not virtual", () => Beatmap.Value.Track is not TrackVirtual);
AddAssert("track length changed", () => Beatmap.Value.Track.Length > 60000);
AddStep("test play", () => Editor.TestGameplay());
AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog != null);
AddStep("confirm save", () => InputManager.Key(Key.Number1));
AddUntilStep("wait for return to editor", () => Editor.IsCurrentScreen());
AddAssert("track is still not virtual", () => Beatmap.Value.Track is not TrackVirtual);
AddAssert("track length correct", () => Beatmap.Value.Track.Length > 60000);
AddUntilStep("track not playing", () => !EditorClock.IsRunning);
AddStep("play track", () => InputManager.Key(Key.Space));
AddUntilStep("wait for track playing", () => EditorClock.IsRunning);
}
[Test]

View File

@ -5,10 +5,14 @@ using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Overlays;
using osu.Game.Overlays.Mods;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mania.Mods;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
@ -16,29 +20,133 @@ namespace osu.Game.Tests.Visual.UserInterface
{
public class TestSceneModPresetColumn : OsuTestScene
{
protected override bool UseFreshStoragePerRun => true;
private RulesetStore rulesets = null!;
[Cached]
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green);
[Test]
public void TestBasicAppearance()
[BackgroundDependencyLoader]
private void load()
{
ModPresetColumn modPresetColumn = null!;
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
Dependencies.Cache(Realm);
}
[SetUpSteps]
public void SetUpSteps()
{
AddStep("clear contents", Clear);
AddStep("reset storage", () =>
{
Realm.Write(realm =>
{
realm.RemoveAll<ModPreset>();
var testPresets = createTestPresets();
foreach (var preset in testPresets)
preset.Ruleset = realm.Find<RulesetInfo>(preset.Ruleset.ShortName);
realm.Add(testPresets);
});
});
}
[Test]
public void TestBasicOperation()
{
AddStep("set osu! ruleset", () => Ruleset.Value = rulesets.GetRuleset(0));
AddStep("create content", () => Child = new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding(30),
Child = modPresetColumn = new ModPresetColumn
Child = new ModPresetColumn
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Presets = createTestPresets().ToArray()
}
});
AddStep("change presets", () => modPresetColumn.Presets = createTestPresets().Skip(1).ToArray());
AddUntilStep("3 panels visible", () => this.ChildrenOfType<ModPresetPanel>().Count() == 3);
AddStep("change ruleset to mania", () => Ruleset.Value = rulesets.GetRuleset(3));
AddUntilStep("1 panel visible", () => this.ChildrenOfType<ModPresetPanel>().Count() == 1);
AddStep("add another mania preset", () => Realm.Write(r => r.Add(new ModPreset
{
Name = "and another one",
Mods = new Mod[]
{
new ManiaModMirror(),
new ManiaModNightcore(),
new ManiaModHardRock()
},
Ruleset = r.Find<RulesetInfo>("mania")
})));
AddUntilStep("2 panels visible", () => this.ChildrenOfType<ModPresetPanel>().Count() == 2);
AddStep("add another osu! preset", () => Realm.Write(r => r.Add(new ModPreset
{
Name = "hdhr",
Mods = new Mod[]
{
new OsuModHidden(),
new OsuModHardRock()
},
Ruleset = r.Find<RulesetInfo>("osu")
})));
AddUntilStep("2 panels visible", () => this.ChildrenOfType<ModPresetPanel>().Count() == 2);
AddStep("remove mania preset", () => Realm.Write(r =>
{
var toRemove = r.All<ModPreset>().Single(preset => preset.Name == "Different ruleset");
r.Remove(toRemove);
}));
AddUntilStep("1 panel visible", () => this.ChildrenOfType<ModPresetPanel>().Count() == 1);
AddStep("set osu! ruleset", () => Ruleset.Value = rulesets.GetRuleset(0));
AddUntilStep("4 panels visible", () => this.ChildrenOfType<ModPresetPanel>().Count() == 4);
}
private static IEnumerable<ModPreset> createTestPresets() => new[]
[Test]
public void TestSoftDeleteSupport()
{
AddStep("set osu! ruleset", () => Ruleset.Value = rulesets.GetRuleset(0));
AddStep("create content", () => Child = new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding(30),
Child = new ModPresetColumn
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
}
});
AddUntilStep("3 panels visible", () => this.ChildrenOfType<ModPresetPanel>().Count() == 3);
AddStep("soft delete preset", () => Realm.Write(r =>
{
var toSoftDelete = r.All<ModPreset>().Single(preset => preset.Name == "AR0");
toSoftDelete.DeletePending = true;
}));
AddUntilStep("2 panels visible", () => this.ChildrenOfType<ModPresetPanel>().Count() == 2);
AddStep("soft delete all presets", () => Realm.Write(r =>
{
foreach (var preset in r.All<ModPreset>())
preset.DeletePending = true;
}));
AddUntilStep("no panels visible", () => this.ChildrenOfType<ModPresetPanel>().Count() == 0);
AddStep("undelete preset", () => Realm.Write(r =>
{
foreach (var preset in r.All<ModPreset>())
preset.DeletePending = false;
}));
AddUntilStep("3 panels visible", () => this.ChildrenOfType<ModPresetPanel>().Count() == 3);
}
private ICollection<ModPreset> createTestPresets() => new[]
{
new ModPreset
{
@ -48,7 +156,8 @@ namespace osu.Game.Tests.Visual.UserInterface
{
new OsuModHardRock(),
new OsuModDoubleTime()
}
},
Ruleset = rulesets.GetRuleset(0).AsNonNull()
},
new ModPreset
{
@ -60,7 +169,8 @@ namespace osu.Game.Tests.Visual.UserInterface
{
ApproachRate = { Value = 0 }
}
}
},
Ruleset = rulesets.GetRuleset(0).AsNonNull()
},
new ModPreset
{
@ -70,7 +180,19 @@ namespace osu.Game.Tests.Visual.UserInterface
{
new OsuModFlashlight(),
new OsuModSpinIn()
}
},
Ruleset = rulesets.GetRuleset(0).AsNonNull()
},
new ModPreset
{
Name = "Different ruleset",
Description = "Just to shake things up",
Mods = new Mod[]
{
new ManiaModKey4(),
new ManiaModFadeIn()
},
Ruleset = rulesets.GetRuleset(3).AsNonNull()
}
};
}

View File

@ -7,9 +7,11 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Database;
using osu.Game.Overlays;
using osu.Game.Overlays.Mods;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osuTK;
@ -31,7 +33,7 @@ namespace osu.Game.Tests.Visual.UserInterface
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Spacing = new Vector2(0, 5),
ChildrenEnumerable = createTestPresets().Select(preset => new ModPresetPanel(preset))
ChildrenEnumerable = createTestPresets().Select(preset => new ModPresetPanel(preset.ToLiveUnmanaged()))
});
}
@ -45,7 +47,8 @@ namespace osu.Game.Tests.Visual.UserInterface
{
new OsuModHardRock(),
new OsuModDoubleTime()
}
},
Ruleset = new OsuRuleset().RulesetInfo
},
new ModPreset
{
@ -57,7 +60,8 @@ namespace osu.Game.Tests.Visual.UserInterface
{
ApproachRate = { Value = 0 }
}
}
},
Ruleset = new OsuRuleset().RulesetInfo
},
new ModPreset
{
@ -67,7 +71,8 @@ namespace osu.Game.Tests.Visual.UserInterface
{
new OsuModFlashlight(),
new OsuModSpinIn()
}
},
Ruleset = new OsuRuleset().RulesetInfo
}
};
}

View File

@ -66,8 +66,9 @@ namespace osu.Game.Database
/// 19 2022-07-19 Added DateSubmitted and DateRanked to BeatmapSetInfo.
/// 20 2022-07-21 Added LastAppliedDifficultyVersion to RulesetInfo, changed default value of BeatmapInfo.StarRating to -1.
/// 21 2022-07-27 Migrate collections to realm (BeatmapCollection).
/// 22 2022-07-31 Added ModPreset.
/// </summary>
private const int schema_version = 21;
private const int schema_version = 22;
/// <summary>
/// Lock object which is held during <see cref="BlockAllOperations"/> sections, blocking realm retrieval during blocking periods.

View File

@ -96,12 +96,22 @@ namespace osu.Game.IO
if (!destination.Exists)
Directory.CreateDirectory(destination.FullName);
foreach (System.IO.FileInfo fi in source.GetFiles())
foreach (System.IO.FileInfo fileInfo in source.GetFiles())
{
if (topLevelExcludes && IgnoreFiles.Contains(fi.Name))
if (topLevelExcludes && IgnoreFiles.Contains(fileInfo.Name))
continue;
AttemptOperation(() => fi.CopyTo(Path.Combine(destination.FullName, fi.Name), true));
AttemptOperation(() =>
{
fileInfo.Refresh();
// A temporary file may have been deleted since the initial GetFiles operation.
// We don't want the whole migration process to fail in such a case.
if (!fileInfo.Exists)
return;
fileInfo.CopyTo(Path.Combine(destination.FullName, fileInfo.Name), true);
});
}
foreach (DirectoryInfo dir in source.GetDirectories())

View File

@ -212,6 +212,10 @@ namespace osu.Game
{
Name = @"osu!";
#if DEBUG
Name += " (development)";
#endif
allowableExceptions = UnhandledExceptionsBeforeCrash;
}

View File

@ -7,31 +7,24 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Game.Database;
using osu.Game.Graphics;
using osu.Game.Localisation;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osuTK;
using Realms;
namespace osu.Game.Overlays.Mods
{
public class ModPresetColumn : ModSelectColumn
{
private IReadOnlyList<ModPreset> presets = Array.Empty<ModPreset>();
[Resolved]
private RealmAccess realm { get; set; } = null!;
/// <summary>
/// Sets the collection of available mod presets.
/// </summary>
public IReadOnlyList<ModPreset> Presets
{
get => presets;
set
{
presets = value;
if (IsLoaded)
asyncLoadPanels();
}
}
[Resolved]
private IBindable<RulesetInfo> ruleset { get; set; } = null!;
[BackgroundDependencyLoader]
private void load(OsuColour colours)
@ -44,7 +37,20 @@ namespace osu.Game.Overlays.Mods
{
base.LoadComplete();
asyncLoadPanels();
ruleset.BindValueChanged(_ => rulesetChanged(), true);
}
private IDisposable? presetSubscription;
private void rulesetChanged()
{
presetSubscription?.Dispose();
presetSubscription = realm.RegisterForNotifications(r =>
r.All<ModPreset>()
.Filter($"{nameof(ModPreset.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $0"
+ $" && {nameof(ModPreset.DeletePending)} == false", ruleset.Value.ShortName)
.OrderBy(preset => preset.Name),
(presets, _, _) => asyncLoadPanels(presets));
}
private CancellationTokenSource? cancellationTokenSource;
@ -52,11 +58,17 @@ namespace osu.Game.Overlays.Mods
private Task? latestLoadTask;
internal bool ItemsLoaded => latestLoadTask == null;
private void asyncLoadPanels()
private void asyncLoadPanels(IReadOnlyList<ModPreset> presets)
{
cancellationTokenSource?.Cancel();
var panels = presets.Select(preset => new ModPresetPanel(preset)
if (!presets.Any())
{
ItemsFlow.Clear();
return;
}
var panels = presets.Select(preset => new ModPresetPanel(preset.ToLive(realm))
{
Shear = Vector2.Zero
});
@ -73,5 +85,12 @@ namespace osu.Game.Overlays.Mods
latestLoadTask = null;
});
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
presetSubscription?.Dispose();
}
}
}

View File

@ -4,6 +4,7 @@
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Cursor;
using osu.Game.Database;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
@ -11,16 +12,16 @@ namespace osu.Game.Overlays.Mods
{
public class ModPresetPanel : ModSelectPanel, IHasCustomTooltip<ModPreset>
{
public readonly ModPreset Preset;
public readonly Live<ModPreset> Preset;
public override BindableBool Active { get; } = new BindableBool();
public ModPresetPanel(ModPreset preset)
public ModPresetPanel(Live<ModPreset> preset)
{
Preset = preset;
Title = preset.Name;
Description = preset.Description;
Title = preset.Value.Name;
Description = preset.Value.Description;
}
[BackgroundDependencyLoader]
@ -29,7 +30,7 @@ namespace osu.Game.Overlays.Mods
AccentColour = colours.Orange1;
}
public ModPreset TooltipContent => Preset;
public ModPreset TooltipContent => Preset.Value;
public ITooltip<ModPreset> GetCustomTooltip() => new ModPresetTooltip(ColourProvider);
}
}

View File

@ -50,7 +50,7 @@ namespace osu.Game.Overlays.Mods
public void SetContent(ModPreset preset)
{
if (preset == lastPreset)
if (ReferenceEquals(preset, lastPreset))
return;
lastPreset = preset;

View File

@ -70,7 +70,11 @@ namespace osu.Game.Overlays
/// <summary>
/// Forcefully reload the current <see cref="WorkingBeatmap"/>'s track from disk.
/// </summary>
public void ReloadCurrentTrack() => changeTrack();
public void ReloadCurrentTrack()
{
changeTrack();
TrackChanged?.Invoke(current, TrackChangeDirection.None);
}
/// <summary>
/// Returns whether the beatmap track is playing.

View File

@ -3,6 +3,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Game.Database;
using osu.Game.Online.API;
using Realms;
namespace osu.Game.Rulesets.Mods
{
@ -10,12 +16,18 @@ namespace osu.Game.Rulesets.Mods
/// A mod preset is a named collection of configured mods.
/// Presets are presented to the user in the mod select overlay for convenience.
/// </summary>
public class ModPreset
public class ModPreset : RealmObject, IHasGuidPrimaryKey, ISoftDelete
{
/// <summary>
/// The internal database ID of the preset.
/// </summary>
[PrimaryKey]
public Guid ID { get; set; } = Guid.NewGuid();
/// <summary>
/// The ruleset that the preset is valid for.
/// </summary>
public RulesetInfo RulesetInfo { get; set; } = null!;
public RulesetInfo Ruleset { get; set; } = null!;
/// <summary>
/// The name of the mod preset.
@ -30,6 +42,34 @@ namespace osu.Game.Rulesets.Mods
/// <summary>
/// The set of configured mods that are part of the preset.
/// </summary>
public ICollection<Mod> Mods { get; set; } = Array.Empty<Mod>();
[Ignored]
public ICollection<Mod> Mods
{
get
{
if (string.IsNullOrEmpty(ModsJson))
return Array.Empty<Mod>();
var apiMods = JsonConvert.DeserializeObject<IEnumerable<APIMod>>(ModsJson);
var ruleset = Ruleset.CreateInstance();
return apiMods.AsNonNull().Select(mod => mod.ToMod(ruleset)).ToArray();
}
set
{
var apiMods = value.Select(mod => new APIMod(mod)).ToArray();
ModsJson = JsonConvert.SerializeObject(apiMods);
}
}
/// <summary>
/// The set of configured mods that are part of the preset, serialised as a JSON blob.
/// </summary>
[MapTo("Mods")]
public string ModsJson { get; set; } = string.Empty;
/// <summary>
/// Whether the preset has been soft-deleted by the user.
/// </summary>
public bool DeletePending { get; set; }
}
}

View File

@ -329,6 +329,9 @@ namespace osu.Game.Screens.Edit
changeHandler?.CanRedo.BindValueChanged(v => redoMenuItem.Action.Disabled = !v.NewValue, true);
}
[Resolved]
private MusicController musicController { get; set; }
protected override void LoadComplete()
{
base.LoadComplete();
@ -336,12 +339,18 @@ namespace osu.Game.Screens.Edit
Mode.Value = isNewBeatmap ? EditorScreenMode.SongSetup : EditorScreenMode.Compose;
Mode.BindValueChanged(onModeChanged, true);
musicController.TrackChanged += onTrackChanged;
}
/// <summary>
/// If the beatmap's track has changed, this method must be called to keep the editor in a valid state.
/// </summary>
public void UpdateClockSource() => clock.ChangeSource(Beatmap.Value.Track);
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
musicController.TrackChanged -= onTrackChanged;
}
private void onTrackChanged(WorkingBeatmap working, TrackChangeDirection direction) => clock.ChangeSource(working.Track);
/// <summary>
/// Creates an <see cref="EditorState"/> instance representing the current state of the editor.

View File

@ -30,8 +30,8 @@ namespace osu.Game.Screens.Edit.Setup
[Resolved]
private IBindable<WorkingBeatmap> working { get; set; }
[Resolved(canBeNull: true)]
private Editor editor { get; set; }
[Resolved]
private EditorBeatmap editorBeatmap { get; set; }
[Resolved]
private SetupScreenHeader header { get; set; }
@ -88,6 +88,8 @@ namespace osu.Game.Screens.Edit.Setup
beatmaps.AddFile(set, stream, destination.Name);
}
editorBeatmap.SaveState();
working.Value.Metadata.BackgroundFile = destination.Name;
header.Background.UpdateBackground();
@ -117,9 +119,9 @@ namespace osu.Game.Screens.Edit.Setup
working.Value.Metadata.AudioFile = destination.Name;
editorBeatmap.SaveState();
music.ReloadCurrentTrack();
editor?.UpdateClockSource();
return true;
}

View File

@ -204,6 +204,9 @@ namespace osu.Game.Screens.OnlinePlay
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
{
if (!AllowSelection)
return false;
switch (e.Action)
{
case GlobalAction.SelectNext:
@ -224,9 +227,6 @@ namespace osu.Game.Screens.OnlinePlay
private void selectNext(int direction)
{
if (!AllowSelection)
return;
var visibleItems = ListContainer.AsEnumerable().Where(r => r.IsPresent);
PlaylistItem item;

View File

@ -98,5 +98,14 @@ namespace osu.Game.Screens.Play.HUD
return Drawable.Empty();
}
}
public static Type[] GetAllAvailableDrawables()
{
return typeof(OsuGame).Assembly.GetTypes()
.Where(t => !t.IsInterface && !t.IsAbstract)
.Where(t => typeof(ISkinnableDrawable).IsAssignableFrom(t))
.OrderBy(t => t.Name)
.ToArray();
}
}
}

View File

@ -1,11 +1,8 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using System.Diagnostics;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@ -16,24 +13,25 @@ using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays;
using osu.Game.Screens.Edit.Components;
using osu.Game.Screens.Play.HUD;
using osuTK;
namespace osu.Game.Skinning.Editor
{
public class SkinComponentToolbox : EditorSidebarSection
{
public Action<Type> RequestPlacement;
public Action<Type>? RequestPlacement;
private readonly CompositeDrawable target;
private readonly CompositeDrawable? target;
public SkinComponentToolbox(CompositeDrawable target = null)
private FillFlowContainer fill = null!;
public SkinComponentToolbox(CompositeDrawable? target = null)
: base("Components")
{
this.target = target;
}
private FillFlowContainer fill;
[BackgroundDependencyLoader]
private void load()
{
@ -52,12 +50,7 @@ namespace osu.Game.Skinning.Editor
{
fill.Clear();
var skinnableTypes = typeof(OsuGame).Assembly.GetTypes()
.Where(t => !t.IsInterface && !t.IsAbstract)
.Where(t => typeof(ISkinnableDrawable).IsAssignableFrom(t))
.OrderBy(t => t.Name)
.ToArray();
var skinnableTypes = SkinnableInfo.GetAllAvailableDrawables();
foreach (var type in skinnableTypes)
attemptAddComponent(type);
}
@ -90,21 +83,21 @@ namespace osu.Game.Skinning.Editor
public class ToolboxComponentButton : OsuButton
{
public Action<Type>? RequestPlacement;
protected override bool ShouldBeConsideredForInput(Drawable child) => false;
public override bool PropagateNonPositionalInputSubTree => false;
private readonly Drawable component;
private readonly CompositeDrawable dependencySource;
private readonly CompositeDrawable? dependencySource;
public Action<Type> RequestPlacement;
private Container innerContainer;
private Container innerContainer = null!;
private const float contracted_size = 60;
private const float expanded_size = 120;
public ToolboxComponentButton(Drawable component, CompositeDrawable dependencySource)
public ToolboxComponentButton(Drawable component, CompositeDrawable? dependencySource)
{
this.component = component;
this.dependencySource = dependencySource;
@ -184,9 +177,9 @@ namespace osu.Game.Skinning.Editor
public class DependencyBorrowingContainer : Container
{
private readonly CompositeDrawable donor;
private readonly CompositeDrawable? donor;
public DependencyBorrowingContainer(CompositeDrawable donor)
public DependencyBorrowingContainer(CompositeDrawable? donor)
{
this.donor = donor;
}