diff --git a/osu.Android.props b/osu.Android.props index c4cdffa8a5..3b2e6574ac 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -62,7 +62,7 @@ - - + + diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj index 9acf47a67c..4100404da6 100644 --- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj +++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj index df5131dd8b..013d2a71d4 100644 --- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj +++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternType.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternType.cs index a3cd455886..e4a28167ec 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternType.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternType.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// /// Keep the same as last row. /// - ForceStack = 1 << 0, + ForceStack = 1, /// /// Keep different from last row. diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs index 8102718edf..a92e56d3c3 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs @@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces } } - private Cached subtractionCache = new Cached(); + private readonly Cached subtractionCache = new Cached(); public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) { diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj index bb3e5a66f3..92c5c77aac 100644 --- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj +++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index 401bd28d7c..ca72f18e9c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -2,13 +2,19 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; +using System.Linq; using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.StateChanges; using osu.Game.Graphics; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Replays; +using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModAutopilot : Mod + public class OsuModAutopilot : Mod, IApplicableFailOverride, IUpdatableByPlayfield, IApplicableToDrawableRuleset { public override string Name => "Autopilot"; public override string Acronym => "AP"; @@ -17,5 +23,40 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Description => @"Automatic cursor movement - just follow the rhythm."; public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail), typeof(ModAutoplay) }; + + public bool AllowFail => false; + + private OsuInputManager inputManager; + + private List replayFrames; + + private int currentFrame; + + public void Update(Playfield playfield) + { + if (currentFrame == replayFrames.Count - 1) return; + + double time = playfield.Time.Current; + + // Very naive implementation of autopilot based on proximity to replay frames. + // TODO: this needs to be based on user interactions to better match stable (pausing until judgement is registered). + if (Math.Abs(replayFrames[currentFrame + 1].Time - time) <= Math.Abs(replayFrames[currentFrame].Time - time)) + { + currentFrame++; + new MousePositionAbsoluteInput { Position = playfield.ToScreenSpace(replayFrames[currentFrame].Position) }.Apply(inputManager.CurrentState, inputManager); + } + + // TODO: Implement the functionality to automatically spin spinners + } + + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + { + // Grab the input manager to disable the user's cursor, and for future use + inputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager; + inputManager.AllowUserCursorMovement = false; + + // Generate the replay frames the cursor should follow + replayFrames = new OsuAutoGenerator(drawableRuleset.Beatmap).Generate().Frames.Cast().ToList(); + } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs index 0590ca1d96..70a1bad4a3 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs @@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces var spanProgress = slider.ProgressAt(completionProgress); double start = 0; - double end = SnakingIn.Value ? MathHelper.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / slider.TimeFadeIn, 0, 1) : 1; + double end = SnakingIn.Value ? MathHelper.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / (slider.TimePreempt / 3), 0, 1) : 1; if (span >= slider.SpanCount() - 1) { diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index d1221fd2d3..b52bfcd181 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Osu.Objects base.ApplyDefaultsToSelf(controlPointInfo, difficulty); TimePreempt = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450); - TimeFadeIn = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1200, 800, 300); + TimeFadeIn = 400; // as per osu-stable Scale = (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5) / 2; } diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index a4638c31f2..d3279652c7 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Objects public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity; public double Duration => EndTime - StartTime; - private Cached endPositionCache; + private readonly Cached endPositionCache = new Cached(); public override Vector2 EndPosition => endPositionCache.IsValid ? endPositionCache.Value : endPositionCache.Value = Position + this.CurvePositionAt(1); diff --git a/osu.Game.Rulesets.Osu/OsuInputManager.cs b/osu.Game.Rulesets.Osu/OsuInputManager.cs index b9e083d35b..cdea7276f3 100644 --- a/osu.Game.Rulesets.Osu/OsuInputManager.cs +++ b/osu.Game.Rulesets.Osu/OsuInputManager.cs @@ -18,6 +18,12 @@ namespace osu.Game.Rulesets.Osu set => ((OsuKeyBindingContainer)KeyBindingContainer).AllowUserPresses = value; } + /// + /// Whether the user's cursor movement events should be accepted. + /// Can be used to block only movement while still accepting button input. + /// + public bool AllowUserCursorMovement { get; set; } = true; + protected override RulesetKeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) => new OsuKeyBindingContainer(ruleset, variant, unique); @@ -26,6 +32,13 @@ namespace osu.Game.Rulesets.Osu { } + protected override bool Handle(UIEvent e) + { + if (e is MouseMoveEvent && !AllowUserCursorMovement) return false; + + return base.Handle(e); + } + private class OsuKeyBindingContainer : RulesetKeyBindingContainer { public bool AllowUserPresses = true; diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj index 5510c3a9d9..82055ecaee 100644 --- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj +++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index d087251e7e..535320530d 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -482,5 +482,17 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(hitObjects[0].Samples[0].Bank, hitObjects[0].Samples[1].Bank); } } + + [Test] + public void TestInvalidEventStillPasses() + { + var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + + using (var badResStream = TestResources.OpenResource("invalid-events.osu")) + using (var badStream = new StreamReader(badResStream)) + { + Assert.DoesNotThrow(() => decoder.Decode(badStream)); + } + } } } diff --git a/osu.Game.Tests/Resources/invalid-events.osu b/osu.Game.Tests/Resources/invalid-events.osu new file mode 100644 index 0000000000..df86b26dba --- /dev/null +++ b/osu.Game.Tests/Resources/invalid-events.osu @@ -0,0 +1,14 @@ +osu file format v14 + +[Events] +bad,event,this,should,fail +//Background and Video events +0,0,"machinetop_background.jpg",0,0 +//Break Periods +2,122474,140135 +//Storyboard Layer 0 (Background) +this,is,also,bad +//Storyboard Layer 1 (Fail) +//Storyboard Layer 2 (Pass) +//Storyboard Layer 3 (Foreground) +//Storyboard Sound Samples diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs index f114559114..3061a3a542 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs @@ -119,14 +119,14 @@ namespace osu.Game.Tests.Visual.Background { performFullSetup(); createFakeStoryboard(); - AddStep("Storyboard Enabled", () => + AddStep("Enable Storyboard", () => { player.ReplacesBackground.Value = true; player.StoryboardEnabled.Value = true; }); waitForDim(); AddAssert("Background is invisible, storyboard is visible", () => songSelect.IsBackgroundInvisible() && player.IsStoryboardVisible); - AddStep("Storyboard Disabled", () => + AddStep("Disable Storyboard", () => { player.ReplacesBackground.Value = false; player.StoryboardEnabled.Value = false; @@ -149,22 +149,44 @@ namespace osu.Game.Tests.Visual.Background } /// - /// Check if the is properly accepting user-defined visual changes at all. + /// Ensure is properly accepting user-defined visual changes for a background. /// [Test] - public void DisableUserDimTest() + public void DisableUserDimBackgroundTest() { performFullSetup(); waitForDim(); AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); - AddStep("EnableUserDim disabled", () => songSelect.DimEnabled.Value = false); + AddStep("Enable user dim", () => songSelect.DimEnabled.Value = false); waitForDim(); AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsUserBlurDisabled()); - AddStep("EnableUserDim enabled", () => songSelect.DimEnabled.Value = true); + AddStep("Disable user dim", () => songSelect.DimEnabled.Value = true); waitForDim(); AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); } + /// + /// Ensure is properly accepting user-defined visual changes for a storyboard. + /// + [Test] + public void DisableUserDimStoryboardTest() + { + performFullSetup(); + createFakeStoryboard(); + AddStep("Enable Storyboard", () => + { + player.ReplacesBackground.Value = true; + player.StoryboardEnabled.Value = true; + }); + AddStep("Enable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = true); + AddStep("Set dim level to 1", () => songSelect.DimLevel.Value = 1f); + waitForDim(); + AddAssert("Storyboard is invisible", () => !player.IsStoryboardVisible); + AddStep("Disable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = false); + waitForDim(); + AddAssert("Storyboard is visible", () => player.IsStoryboardVisible); + } + /// /// Check if the visual settings container retains dim and blur when pausing /// diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 80408ab43b..be50200e1c 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -82,14 +82,13 @@ namespace osu.Game.Tests.Visual.UserInterface var easierMods = instance.GetModsFor(ModType.DifficultyReduction); var harderMods = instance.GetModsFor(ModType.DifficultyIncrease); - var assistMods = instance.GetModsFor(ModType.Automation); var noFailMod = easierMods.FirstOrDefault(m => m is OsuModNoFail); var hiddenMod = harderMods.FirstOrDefault(m => m is OsuModHidden); var doubleTimeMod = harderMods.OfType().FirstOrDefault(m => m.Mods.Any(a => a is OsuModDoubleTime)); - var autoPilotMod = assistMods.FirstOrDefault(m => m is OsuModAutopilot); + var spunOutMod = easierMods.FirstOrDefault(m => m is OsuModSpunOut); var easy = easierMods.FirstOrDefault(m => m is OsuModEasy); var hardRock = harderMods.FirstOrDefault(m => m is OsuModHardRock); @@ -101,7 +100,7 @@ namespace osu.Game.Tests.Visual.UserInterface testMultiplierTextColour(noFailMod, modSelect.LowMultiplierColour); testMultiplierTextColour(hiddenMod, modSelect.HighMultiplierColour); - testUnimplementedMod(autoPilotMod); + testUnimplementedMod(spunOutMod); } [Test] diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 659f5415c3..50530088c2 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -5,7 +5,7 @@ - + diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj index dad2fe0877..257db89a20 100644 --- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj +++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj @@ -7,7 +7,7 @@ - + WinExe diff --git a/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs b/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs index 67531ce5d3..83a41a662f 100644 --- a/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs +++ b/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs @@ -88,7 +88,7 @@ namespace osu.Game.Tournament.Screens.Ladder }; } - private Cached layout = new Cached(); + private readonly Cached layout = new Cached(); protected override void Update() { diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 4ebeee40bf..a09a1bb9cb 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -60,5 +60,7 @@ namespace osu.Game.Beatmaps public class Beatmap : Beatmap { public new Beatmap Clone() => (Beatmap)base.Clone(); + + public override string ToString() => BeatmapInfo?.ToString() ?? base.ToString(); } } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 3cd425ea44..02d969b571 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -5,7 +5,6 @@ using System; using System.IO; using System.Linq; using osu.Framework.IO.File; -using osu.Framework.Logging; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Beatmaps.ControlPoints; @@ -290,8 +289,9 @@ namespace osu.Game.Beatmaps.Formats string[] split = line.Split(','); EventType type; + if (!Enum.TryParse(split[0], out type)) - throw new InvalidDataException($@"Unknown event type {split[0]}"); + throw new InvalidDataException($@"Unknown event type: {split[0]}"); switch (type) { @@ -319,90 +319,79 @@ namespace osu.Game.Beatmaps.Formats private void handleTimingPoint(string line) { - try + string[] split = line.Split(','); + + double time = getOffsetTime(Parsing.ParseDouble(split[0].Trim())); + double beatLength = Parsing.ParseDouble(split[1].Trim()); + double speedMultiplier = beatLength < 0 ? 100.0 / -beatLength : 1; + + TimeSignatures timeSignature = TimeSignatures.SimpleQuadruple; + if (split.Length >= 3) + timeSignature = split[2][0] == '0' ? TimeSignatures.SimpleQuadruple : (TimeSignatures)Parsing.ParseInt(split[2]); + + LegacySampleBank sampleSet = defaultSampleBank; + if (split.Length >= 4) + sampleSet = (LegacySampleBank)Parsing.ParseInt(split[3]); + + int customSampleBank = 0; + if (split.Length >= 5) + customSampleBank = Parsing.ParseInt(split[4]); + + int sampleVolume = defaultSampleVolume; + if (split.Length >= 6) + sampleVolume = Parsing.ParseInt(split[5]); + + bool timingChange = true; + if (split.Length >= 7) + timingChange = split[6][0] == '1'; + + bool kiaiMode = false; + bool omitFirstBarSignature = false; + + if (split.Length >= 8) { - string[] split = line.Split(','); - - double time = getOffsetTime(Parsing.ParseDouble(split[0].Trim())); - double beatLength = Parsing.ParseDouble(split[1].Trim()); - double speedMultiplier = beatLength < 0 ? 100.0 / -beatLength : 1; - - TimeSignatures timeSignature = TimeSignatures.SimpleQuadruple; - if (split.Length >= 3) - timeSignature = split[2][0] == '0' ? TimeSignatures.SimpleQuadruple : (TimeSignatures)Parsing.ParseInt(split[2]); - - LegacySampleBank sampleSet = defaultSampleBank; - if (split.Length >= 4) - sampleSet = (LegacySampleBank)Parsing.ParseInt(split[3]); - - int customSampleBank = 0; - if (split.Length >= 5) - customSampleBank = Parsing.ParseInt(split[4]); - - int sampleVolume = defaultSampleVolume; - if (split.Length >= 6) - sampleVolume = Parsing.ParseInt(split[5]); - - bool timingChange = true; - if (split.Length >= 7) - timingChange = split[6][0] == '1'; - - bool kiaiMode = false; - bool omitFirstBarSignature = false; - - if (split.Length >= 8) - { - EffectFlags effectFlags = (EffectFlags)Parsing.ParseInt(split[7]); - kiaiMode = effectFlags.HasFlag(EffectFlags.Kiai); - omitFirstBarSignature = effectFlags.HasFlag(EffectFlags.OmitFirstBarLine); - } - - string stringSampleSet = sampleSet.ToString().ToLowerInvariant(); - if (stringSampleSet == @"none") - stringSampleSet = @"normal"; - - if (timingChange) - { - var controlPoint = CreateTimingControlPoint(); - controlPoint.Time = time; - controlPoint.BeatLength = beatLength; - controlPoint.TimeSignature = timeSignature; - - handleTimingControlPoint(controlPoint); - } - - handleDifficultyControlPoint(new DifficultyControlPoint - { - Time = time, - SpeedMultiplier = speedMultiplier, - AutoGenerated = timingChange - }); - - handleEffectControlPoint(new EffectControlPoint - { - Time = time, - KiaiMode = kiaiMode, - OmitFirstBarLine = omitFirstBarSignature, - AutoGenerated = timingChange - }); - - handleSampleControlPoint(new LegacySampleControlPoint - { - Time = time, - SampleBank = stringSampleSet, - SampleVolume = sampleVolume, - CustomSampleBank = customSampleBank, - AutoGenerated = timingChange - }); + EffectFlags effectFlags = (EffectFlags)Parsing.ParseInt(split[7]); + kiaiMode = effectFlags.HasFlag(EffectFlags.Kiai); + omitFirstBarSignature = effectFlags.HasFlag(EffectFlags.OmitFirstBarLine); } - catch (FormatException) + + string stringSampleSet = sampleSet.ToString().ToLowerInvariant(); + if (stringSampleSet == @"none") + stringSampleSet = @"normal"; + + if (timingChange) { - Logger.Log("A timing point could not be parsed correctly and will be ignored", LoggingTarget.Runtime, LogLevel.Important); + var controlPoint = CreateTimingControlPoint(); + controlPoint.Time = time; + controlPoint.BeatLength = beatLength; + controlPoint.TimeSignature = timeSignature; + + handleTimingControlPoint(controlPoint); } - catch (OverflowException) + + handleDifficultyControlPoint(new DifficultyControlPoint { - Logger.Log("A timing point could not be parsed correctly and will be ignored", LoggingTarget.Runtime, LogLevel.Important); - } + Time = time, + SpeedMultiplier = speedMultiplier, + AutoGenerated = timingChange + }); + + handleEffectControlPoint(new EffectControlPoint + { + Time = time, + KiaiMode = kiaiMode, + OmitFirstBarLine = omitFirstBarSignature, + AutoGenerated = timingChange + }); + + handleSampleControlPoint(new LegacySampleControlPoint + { + Time = time, + SampleBank = stringSampleSet, + SampleVolume = sampleVolume, + CustomSampleBank = customSampleBank, + AutoGenerated = timingChange + }); } private void handleTimingControlPoint(TimingControlPoint newPoint) diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 7999c82761..9a8197ad82 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -36,7 +36,7 @@ namespace osu.Game.Beatmaps.Formats { if (!Enum.TryParse(line.Substring(1, line.Length - 2), out section)) { - Logger.Log($"Unknown section \"{line}\" in {output}"); + Logger.Log($"Unknown section \"{line}\" in \"{output}\""); section = Section.None; } @@ -49,7 +49,7 @@ namespace osu.Game.Beatmaps.Formats } catch (Exception e) { - Logger.Error(e, $"Failed to process line \"{line}\" into {output}"); + Logger.Log($"Failed to process line \"{line}\" into \"{output}\": {e.Message}", LoggingTarget.Runtime, LogLevel.Important); } } } diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index f6e2bf6966..3ae1c3ef12 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -82,8 +82,9 @@ namespace osu.Game.Beatmaps.Formats storyboardSprite = null; EventType type; + if (!Enum.TryParse(split[0], out type)) - throw new InvalidDataException($@"Unknown event type {split[0]}"); + throw new InvalidDataException($@"Unknown event type: {split[0]}"); switch (type) { diff --git a/osu.Game/Beatmaps/Legacy/LegacyMods.cs b/osu.Game/Beatmaps/Legacy/LegacyMods.cs index 8e53c24e7b..583e950e49 100644 --- a/osu.Game/Beatmaps/Legacy/LegacyMods.cs +++ b/osu.Game/Beatmaps/Legacy/LegacyMods.cs @@ -9,7 +9,7 @@ namespace osu.Game.Beatmaps.Legacy public enum LegacyMods { None = 0, - NoFail = 1 << 0, + NoFail = 1, Easy = 1 << 1, TouchDevice = 1 << 2, Hidden = 1 << 3, diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index efb76deff8..52d3f013ce 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -31,10 +31,21 @@ namespace osu.Game.Database /// /// The model type. /// The associated file join type. - public abstract class ArchiveModelManager : ArchiveModelManager, ICanAcceptFiles, IModelManager + public abstract class ArchiveModelManager : ICanAcceptFiles, IModelManager where TModel : class, IHasFiles, IHasPrimaryKey, ISoftDelete where TFileModel : INamedFileInfo, new() { + private const int import_queue_request_concurrency = 1; + + /// + /// A singleton scheduler shared by all . + /// + /// + /// This scheduler generally performs IO and CPU intensive work so concurrency is limited harshly. + /// It is mainly being used as a queue mechanism for large imports. + /// + private static readonly ThreadedTaskScheduler import_scheduler = new ThreadedTaskScheduler(import_queue_request_concurrency, nameof(ArchiveModelManager)); + /// /// Set an endpoint for notifications to be posted to. /// @@ -336,7 +347,7 @@ namespace osu.Game.Database flushEvents(true); return item; - }, cancellationToken, TaskCreationOptions.HideScheduler, IMPORT_SCHEDULER).Unwrap(); + }, cancellationToken, TaskCreationOptions.HideScheduler, import_scheduler).Unwrap(); /// /// Perform an update of the specified item. @@ -646,18 +657,4 @@ namespace osu.Game.Database #endregion } - - public abstract class ArchiveModelManager - { - private const int import_queue_request_concurrency = 1; - - /// - /// A singleton scheduler shared by all . - /// - /// - /// This scheduler generally performs IO and CPU intensive work so concurrency is limited harshly. - /// It is mainly being used as a queue mechanism for large imports. - /// - protected static readonly ThreadedTaskScheduler IMPORT_SCHEDULER = new ThreadedTaskScheduler(import_queue_request_concurrency, nameof(ArchiveModelManager)); - } } diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs index 2b68e8530d..dffa0c4fd5 100644 --- a/osu.Game/Graphics/Backgrounds/Triangles.cs +++ b/osu.Game/Graphics/Backgrounds/Triangles.cs @@ -191,7 +191,7 @@ namespace osu.Game.Graphics.Backgrounds private readonly List parts = new List(); private Vector2 size; - private TriangleBatch vertexBatch; + private QuadBatch vertexBatch; public TrianglesDrawNode(Triangles source) : base(source) @@ -217,7 +217,7 @@ namespace osu.Game.Graphics.Backgrounds if (Source.AimCount > 0 && (vertexBatch == null || vertexBatch.Size != Source.AimCount)) { vertexBatch?.Dispose(); - vertexBatch = new TriangleBatch(Source.AimCount, 1); + vertexBatch = new QuadBatch(Source.AimCount, 1); } shader.Bind(); diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index 03de5f651f..2b7635cc88 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -6,7 +6,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Configuration; -using osuTK.Graphics; namespace osu.Game.Graphics.Containers { @@ -36,6 +35,8 @@ namespace osu.Game.Graphics.Containers protected Bindable ShowStoryboard { get; private set; } + protected double DimLevel => EnableUserDim.Value ? UserDimLevel.Value : 0; + protected override Container Content => dimContent; private Container dimContent { get; } @@ -78,8 +79,8 @@ namespace osu.Game.Graphics.Containers { ContentDisplayed = ShowDimContent; - dimContent.FadeTo((ContentDisplayed) ? 1 : 0, BACKGROUND_FADE_DURATION, Easing.OutQuint); - dimContent.FadeColour(EnableUserDim.Value ? OsuColour.Gray(1 - (float)UserDimLevel.Value) : Color4.White, BACKGROUND_FADE_DURATION, Easing.OutQuint); + dimContent.FadeTo(ContentDisplayed ? 1 : 0, BACKGROUND_FADE_DURATION, Easing.OutQuint); + dimContent.FadeColour(OsuColour.Gray(1 - (float)DimLevel), BACKGROUND_FADE_DURATION, Easing.OutQuint); } } } diff --git a/osu.Game/Graphics/UserInterface/Bar.cs b/osu.Game/Graphics/UserInterface/Bar.cs index 2a858ccbcf..f8d5955503 100644 --- a/osu.Game/Graphics/UserInterface/Bar.cs +++ b/osu.Game/Graphics/UserInterface/Bar.cs @@ -110,7 +110,7 @@ namespace osu.Game.Graphics.UserInterface [Flags] public enum BarDirection { - LeftToRight = 1 << 0, + LeftToRight = 1, RightToLeft = 1 << 1, TopToBottom = 1 << 2, BottomToTop = 1 << 3, diff --git a/osu.Game/Graphics/UserInterface/LineGraph.cs b/osu.Game/Graphics/UserInterface/LineGraph.cs index 714e953816..6d65b77cbf 100644 --- a/osu.Game/Graphics/UserInterface/LineGraph.cs +++ b/osu.Game/Graphics/UserInterface/LineGraph.cs @@ -93,7 +93,7 @@ namespace osu.Game.Graphics.UserInterface return base.Invalidate(invalidation, source, shallPropagate); } - private Cached pathCached = new Cached(); + private readonly Cached pathCached = new Cached(); protected override void Update() { diff --git a/osu.Game/Graphics/UserInterface/ScreenTitle.cs b/osu.Game/Graphics/UserInterface/ScreenTitle.cs index 7b39238e5e..10fc312d8b 100644 --- a/osu.Game/Graphics/UserInterface/ScreenTitle.cs +++ b/osu.Game/Graphics/UserInterface/ScreenTitle.cs @@ -15,7 +15,7 @@ namespace osu.Game.Graphics.UserInterface { public const float ICON_WIDTH = ICON_SIZE + icon_spacing; - protected const float ICON_SIZE = 25; + public const float ICON_SIZE = 25; private SpriteIcon iconSprite; private readonly OsuSpriteText titleText, pageText; diff --git a/osu.Game/Graphics/UserInterface/ScreenTitleTextureIcon.cs b/osu.Game/Graphics/UserInterface/ScreenTitleTextureIcon.cs new file mode 100644 index 0000000000..f590e7e357 --- /dev/null +++ b/osu.Game/Graphics/UserInterface/ScreenTitleTextureIcon.cs @@ -0,0 +1,64 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osuTK; + +namespace osu.Game.Graphics.UserInterface +{ + /// + /// A custom icon class for use with based off a texture resource. + /// + public class ScreenTitleTextureIcon : CompositeDrawable + { + private const float circle_allowance = 0.8f; + + private readonly string textureName; + + public ScreenTitleTextureIcon(string textureName) + { + this.textureName = textureName; + } + + [BackgroundDependencyLoader] + private void load(TextureStore textures, OsuColour colours) + { + Size = new Vector2(ScreenTitle.ICON_SIZE / circle_allowance); + + InternalChildren = new Drawable[] + { + new CircularContainer + { + Masking = true, + BorderColour = colours.Violet, + BorderThickness = 3, + MaskingSmoothness = 1, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Sprite + { + RelativeSizeAxes = Axes.Both, + Texture = textures.Get(textureName), + Size = new Vector2(circle_allowance), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.Violet, + Alpha = 0, + AlwaysPresent = true, + }, + } + }, + }; + } + } +} diff --git a/osu.Game/Online/Chat/ErrorMessage.cs b/osu.Game/Online/Chat/ErrorMessage.cs index a8ff0e9a98..87a65fb3f1 100644 --- a/osu.Game/Online/Chat/ErrorMessage.cs +++ b/osu.Game/Online/Chat/ErrorMessage.cs @@ -8,7 +8,7 @@ namespace osu.Game.Online.Chat public ErrorMessage(string message) : base(message) { - Sender.Colour = @"ff0000"; + // todo: this should likely be styled differently in the future. } } } diff --git a/osu.Game/Overlays/Changelog/ChangelogHeader.cs b/osu.Game/Overlays/Changelog/ChangelogHeader.cs index fca62fbb44..b2e9be24b3 100644 --- a/osu.Game/Overlays/Changelog/ChangelogHeader.cs +++ b/osu.Game/Overlays/Changelog/ChangelogHeader.cs @@ -7,13 +7,11 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API.Requests.Responses; -using osuTK; namespace osu.Game.Overlays.Changelog { @@ -123,48 +121,7 @@ namespace osu.Game.Overlays.Changelog AccentColour = colours.Violet; } - protected override Drawable CreateIcon() => new ChangelogIcon(); - - internal class ChangelogIcon : CompositeDrawable - { - private const float circle_allowance = 0.8f; - - [BackgroundDependencyLoader] - private void load(TextureStore textures, OsuColour colours) - { - Size = new Vector2(ICON_SIZE / circle_allowance); - - InternalChildren = new Drawable[] - { - new CircularContainer - { - Masking = true, - BorderColour = colours.Violet, - BorderThickness = 3, - MaskingSmoothness = 1, - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Sprite - { - RelativeSizeAxes = Axes.Both, - Texture = textures.Get(@"Icons/changelog"), - Size = new Vector2(circle_allowance), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colours.Violet, - Alpha = 0, - AlwaysPresent = true, - }, - } - }, - }; - } - } + protected override Drawable CreateIcon() => new ScreenTitleTextureIcon(@"Icons/changelog"); } } } diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index d70c1bf7d3..e990938291 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -10,7 +10,6 @@ using osu.Game.Beatmaps.Formats; using osu.Game.Audio; using System.Linq; using JetBrains.Annotations; -using osu.Framework.Logging; using osu.Framework.MathUtils; namespace osu.Game.Rulesets.Objects.Legacy @@ -41,208 +40,192 @@ namespace osu.Game.Rulesets.Objects.Legacy [CanBeNull] public override HitObject Parse(string text) { - try + string[] split = text.Split(','); + + Vector2 pos = new Vector2((int)Parsing.ParseFloat(split[0], Parsing.MAX_COORDINATE_VALUE), (int)Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE)); + + double startTime = Parsing.ParseDouble(split[2]) + Offset; + + ConvertHitObjectType type = (ConvertHitObjectType)Parsing.ParseInt(split[3]); + + int comboOffset = (int)(type & ConvertHitObjectType.ComboOffset) >> 4; + type &= ~ConvertHitObjectType.ComboOffset; + + bool combo = type.HasFlag(ConvertHitObjectType.NewCombo); + type &= ~ConvertHitObjectType.NewCombo; + + var soundType = (LegacySoundType)Parsing.ParseInt(split[4]); + var bankInfo = new SampleBankInfo(); + + HitObject result = null; + + if (type.HasFlag(ConvertHitObjectType.Circle)) { - string[] split = text.Split(','); + result = CreateHit(pos, combo, comboOffset); - Vector2 pos = new Vector2((int)Parsing.ParseFloat(split[0], Parsing.MAX_COORDINATE_VALUE), (int)Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE)); + if (split.Length > 5) + readCustomSampleBanks(split[5], bankInfo); + } + else if (type.HasFlag(ConvertHitObjectType.Slider)) + { + PathType pathType = PathType.Catmull; + double? length = null; - double startTime = Parsing.ParseDouble(split[2]) + Offset; + string[] pointSplit = split[5].Split('|'); - ConvertHitObjectType type = (ConvertHitObjectType)Parsing.ParseInt(split[3]); + int pointCount = 1; + foreach (var t in pointSplit) + if (t.Length > 1) + pointCount++; - int comboOffset = (int)(type & ConvertHitObjectType.ComboOffset) >> 4; - type &= ~ConvertHitObjectType.ComboOffset; + var points = new Vector2[pointCount]; - bool combo = type.HasFlag(ConvertHitObjectType.NewCombo); - type &= ~ConvertHitObjectType.NewCombo; + int pointIndex = 1; - var soundType = (LegacySoundType)Parsing.ParseInt(split[4]); - var bankInfo = new SampleBankInfo(); - - HitObject result = null; - - if (type.HasFlag(ConvertHitObjectType.Circle)) + foreach (string t in pointSplit) { - result = CreateHit(pos, combo, comboOffset); - - if (split.Length > 5) - readCustomSampleBanks(split[5], bankInfo); - } - else if (type.HasFlag(ConvertHitObjectType.Slider)) - { - PathType pathType = PathType.Catmull; - double? length = null; - - string[] pointSplit = split[5].Split('|'); - - int pointCount = 1; - foreach (var t in pointSplit) - if (t.Length > 1) - pointCount++; - - var points = new Vector2[pointCount]; - - int pointIndex = 1; - - foreach (string t in pointSplit) + if (t.Length == 1) { - if (t.Length == 1) + switch (t) { - switch (t) - { - case @"C": - pathType = PathType.Catmull; - break; - - case @"B": - pathType = PathType.Bezier; - break; - - case @"L": - pathType = PathType.Linear; - break; - - case @"P": - pathType = PathType.PerfectCurve; - break; - } - - continue; - } - - string[] temp = t.Split(':'); - points[pointIndex++] = new Vector2((int)Parsing.ParseDouble(temp[0], Parsing.MAX_COORDINATE_VALUE), (int)Parsing.ParseDouble(temp[1], Parsing.MAX_COORDINATE_VALUE)) - pos; - } - - // osu-stable special-cased colinear perfect curves to a CurveType.Linear - bool isLinear(Vector2[] p) => Precision.AlmostEquals(0, (p[1].Y - p[0].Y) * (p[2].X - p[0].X) - (p[1].X - p[0].X) * (p[2].Y - p[0].Y)); - - if (points.Length == 3 && pathType == PathType.PerfectCurve && isLinear(points)) - pathType = PathType.Linear; - - int repeatCount = Parsing.ParseInt(split[6]); - - if (repeatCount > 9000) - throw new ArgumentOutOfRangeException(nameof(repeatCount), @"Repeat count is way too high"); - - // osu-stable treated the first span of the slider as a repeat, but no repeats are happening - repeatCount = Math.Max(0, repeatCount - 1); - - if (split.Length > 7) - { - length = Math.Max(0, Parsing.ParseDouble(split[7])); - if (length == 0) - length = null; - } - - if (split.Length > 10) - readCustomSampleBanks(split[10], bankInfo); - - // One node for each repeat + the start and end nodes - int nodes = repeatCount + 2; - - // Populate node sample bank infos with the default hit object sample bank - var nodeBankInfos = new List(); - for (int i = 0; i < nodes; i++) - nodeBankInfos.Add(bankInfo.Clone()); - - // Read any per-node sample banks - if (split.Length > 9 && split[9].Length > 0) - { - string[] sets = split[9].Split('|'); - - for (int i = 0; i < nodes; i++) - { - if (i >= sets.Length) + case @"C": + pathType = PathType.Catmull; break; - SampleBankInfo info = nodeBankInfos[i]; - readCustomSampleBanks(sets[i], info); - } - } - - // Populate node sound types with the default hit object sound type - var nodeSoundTypes = new List(); - for (int i = 0; i < nodes; i++) - nodeSoundTypes.Add(soundType); - - // Read any per-node sound types - if (split.Length > 8 && split[8].Length > 0) - { - string[] adds = split[8].Split('|'); - - for (int i = 0; i < nodes; i++) - { - if (i >= adds.Length) + case @"B": + pathType = PathType.Bezier; break; - int sound; - int.TryParse(adds[i], out sound); - nodeSoundTypes[i] = (LegacySoundType)sound; + case @"L": + pathType = PathType.Linear; + break; + + case @"P": + pathType = PathType.PerfectCurve; + break; } + + continue; } - // Generate the final per-node samples - var nodeSamples = new List>(nodes); + string[] temp = t.Split(':'); + points[pointIndex++] = new Vector2((int)Parsing.ParseDouble(temp[0], Parsing.MAX_COORDINATE_VALUE), (int)Parsing.ParseDouble(temp[1], Parsing.MAX_COORDINATE_VALUE)) - pos; + } + + // osu-stable special-cased colinear perfect curves to a CurveType.Linear + bool isLinear(Vector2[] p) => Precision.AlmostEquals(0, (p[1].Y - p[0].Y) * (p[2].X - p[0].X) - (p[1].X - p[0].X) * (p[2].Y - p[0].Y)); + + if (points.Length == 3 && pathType == PathType.PerfectCurve && isLinear(points)) + pathType = PathType.Linear; + + int repeatCount = Parsing.ParseInt(split[6]); + + if (repeatCount > 9000) + throw new ArgumentOutOfRangeException(nameof(repeatCount), @"Repeat count is way too high"); + + // osu-stable treated the first span of the slider as a repeat, but no repeats are happening + repeatCount = Math.Max(0, repeatCount - 1); + + if (split.Length > 7) + { + length = Math.Max(0, Parsing.ParseDouble(split[7])); + if (length == 0) + length = null; + } + + if (split.Length > 10) + readCustomSampleBanks(split[10], bankInfo); + + // One node for each repeat + the start and end nodes + int nodes = repeatCount + 2; + + // Populate node sample bank infos with the default hit object sample bank + var nodeBankInfos = new List(); + for (int i = 0; i < nodes; i++) + nodeBankInfos.Add(bankInfo.Clone()); + + // Read any per-node sample banks + if (split.Length > 9 && split[9].Length > 0) + { + string[] sets = split[9].Split('|'); + for (int i = 0; i < nodes; i++) - nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i])); - - result = CreateSlider(pos, combo, comboOffset, points, length, pathType, repeatCount, nodeSamples); - - // The samples are played when the slider ends, which is the last node - result.Samples = nodeSamples[nodeSamples.Count - 1]; - } - else if (type.HasFlag(ConvertHitObjectType.Spinner)) - { - double endTime = Math.Max(startTime, Parsing.ParseDouble(split[5]) + Offset); - - result = CreateSpinner(new Vector2(512, 384) / 2, combo, comboOffset, endTime); - - if (split.Length > 6) - readCustomSampleBanks(split[6], bankInfo); - } - else if (type.HasFlag(ConvertHitObjectType.Hold)) - { - // Note: Hold is generated by BMS converts - - double endTime = Math.Max(startTime, Parsing.ParseDouble(split[2])); - - if (split.Length > 5 && !string.IsNullOrEmpty(split[5])) { - string[] ss = split[5].Split(':'); - endTime = Math.Max(startTime, Parsing.ParseDouble(ss[0])); - readCustomSampleBanks(string.Join(":", ss.Skip(1)), bankInfo); + if (i >= sets.Length) + break; + + SampleBankInfo info = nodeBankInfos[i]; + readCustomSampleBanks(sets[i], info); } - - result = CreateHold(pos, combo, comboOffset, endTime + Offset); } - if (result == null) + // Populate node sound types with the default hit object sound type + var nodeSoundTypes = new List(); + for (int i = 0; i < nodes; i++) + nodeSoundTypes.Add(soundType); + + // Read any per-node sound types + if (split.Length > 8 && split[8].Length > 0) { - Logger.Log($"Unknown hit object type: {type}. Skipped.", level: LogLevel.Error); - return null; + string[] adds = split[8].Split('|'); + + for (int i = 0; i < nodes; i++) + { + if (i >= adds.Length) + break; + + int sound; + int.TryParse(adds[i], out sound); + nodeSoundTypes[i] = (LegacySoundType)sound; + } } - result.StartTime = startTime; + // Generate the final per-node samples + var nodeSamples = new List>(nodes); + for (int i = 0; i < nodes; i++) + nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i])); - if (result.Samples.Count == 0) - result.Samples = convertSoundType(soundType, bankInfo); + result = CreateSlider(pos, combo, comboOffset, points, length, pathType, repeatCount, nodeSamples); - FirstObject = false; - - return result; + // The samples are played when the slider ends, which is the last node + result.Samples = nodeSamples[nodeSamples.Count - 1]; } - catch (FormatException) + else if (type.HasFlag(ConvertHitObjectType.Spinner)) { - Logger.Log("A hitobject could not be parsed correctly and will be ignored", LoggingTarget.Runtime, LogLevel.Important); + double endTime = Math.Max(startTime, Parsing.ParseDouble(split[5]) + Offset); + + result = CreateSpinner(new Vector2(512, 384) / 2, combo, comboOffset, endTime); + + if (split.Length > 6) + readCustomSampleBanks(split[6], bankInfo); } - catch (OverflowException) + else if (type.HasFlag(ConvertHitObjectType.Hold)) { - Logger.Log("A hitobject could not be parsed correctly and will be ignored", LoggingTarget.Runtime, LogLevel.Important); + // Note: Hold is generated by BMS converts + + double endTime = Math.Max(startTime, Parsing.ParseDouble(split[2])); + + if (split.Length > 5 && !string.IsNullOrEmpty(split[5])) + { + string[] ss = split[5].Split(':'); + endTime = Math.Max(startTime, Parsing.ParseDouble(ss[0])); + readCustomSampleBanks(string.Join(":", ss.Skip(1)), bankInfo); + } + + result = CreateHold(pos, combo, comboOffset, endTime + Offset); } - return null; + if (result == null) + throw new InvalidDataException($"Unknown hit object type: {split[3]}"); + + result.StartTime = startTime; + + if (result.Samples.Count == 0) + result.Samples = convertSoundType(soundType, bankInfo); + + FirstObject = false; + + return result; } private void readCustomSampleBanks(string str, SampleBankInfo bankInfo) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectType.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectType.cs index c9f7224643..eab37b682c 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectType.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectType.cs @@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Objects.Legacy [Flags] internal enum ConvertHitObjectType { - Circle = 1 << 0, + Circle = 1, Slider = 1 << 1, NewCombo = 1 << 2, Spinner = 1 << 3, diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 2e863f7edb..ba2375bec1 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -313,6 +313,9 @@ namespace osu.Game.Rulesets.Scoring /// /// Applies the score change of a to this . /// + /// + /// Any changes applied via this method can be reverted via . + /// /// The to apply. protected virtual void ApplyResult(JudgementResult result) { @@ -357,7 +360,7 @@ namespace osu.Game.Rulesets.Scoring } /// - /// Reverts the score change of a that was applied to this . + /// Reverts the score change of a that was applied to this via . /// /// The judgement scoring result. protected virtual void RevertResult(JudgementResult result) diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 069e2d1a0b..19247d8a37 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.UI.Scrolling [Resolved] private IScrollingInfo scrollingInfo { get; set; } - private Cached initialStateCache = new Cached(); + private readonly Cached initialStateCache = new Cached(); public ScrollingHitObjectContainer() { diff --git a/osu.Game/Screens/Play/DimmableStoryboard.cs b/osu.Game/Screens/Play/DimmableStoryboard.cs index 45dff039b6..2154526e54 100644 --- a/osu.Game/Screens/Play/DimmableStoryboard.cs +++ b/osu.Game/Screens/Play/DimmableStoryboard.cs @@ -33,7 +33,7 @@ namespace osu.Game.Screens.Play base.LoadComplete(); } - protected override bool ShowDimContent => ShowStoryboard.Value && UserDimLevel.Value < 1; + protected override bool ShowDimContent => ShowStoryboard.Value && DimLevel < 1; private void initializeStoryboard(bool async) { diff --git a/osu.Game/Screens/Play/SquareGraph.cs b/osu.Game/Screens/Play/SquareGraph.cs index 5b7a9574b6..9c56725c4e 100644 --- a/osu.Game/Screens/Play/SquareGraph.cs +++ b/osu.Game/Screens/Play/SquareGraph.cs @@ -75,7 +75,7 @@ namespace osu.Game.Screens.Play return base.Invalidate(invalidation, source, shallPropagate); } - private Cached layout = new Cached(); + private readonly Cached layout = new Cached(); private ScheduledDelegate scheduledCreate; protected override void Update() diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index cf88b697d9..7366fa8c17 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -93,8 +93,8 @@ namespace osu.Game.Screens.Select } private readonly List yPositions = new List(); - private Cached itemsCache = new Cached(); - private Cached scrollPositionCache = new Cached(); + private readonly Cached itemsCache = new Cached(); + private readonly Cached scrollPositionCache = new Cached(); private readonly Container scrollableContent; diff --git a/osu.Game/Storyboards/CommandTimeline.cs b/osu.Game/Storyboards/CommandTimeline.cs index aa1f137cf3..bcf642b4ea 100644 --- a/osu.Game/Storyboards/CommandTimeline.cs +++ b/osu.Game/Storyboards/CommandTimeline.cs @@ -15,10 +15,10 @@ namespace osu.Game.Storyboards public IEnumerable Commands => commands.OrderBy(c => c.StartTime); public bool HasCommands => commands.Count > 0; - private Cached startTimeBacking; + private readonly Cached startTimeBacking = new Cached(); public double StartTime => startTimeBacking.IsValid ? startTimeBacking : startTimeBacking.Value = HasCommands ? commands.Min(c => c.StartTime) : double.MinValue; - private Cached endTimeBacking; + private readonly Cached endTimeBacking = new Cached(); public double EndTime => endTimeBacking.IsValid ? endTimeBacking : endTimeBacking.Value = HasCommands ? commands.Max(c => c.EndTime) : double.MaxValue; public T StartValue => HasCommands ? commands.OrderBy(c => c.StartTime).First().StartValue : default; diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index b738eff4a6..a5f3578711 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -187,6 +187,7 @@ namespace osu.Game.Users public static readonly User SYSTEM_USER = new User { Username = "system", + Colour = @"9c0101", Id = 0 }; diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 02bf053468..1dd19ac7ed 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -14,8 +14,8 @@ - - + + diff --git a/osu.iOS.props b/osu.iOS.props index 5b43a6f46f..1c3faeed39 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -104,9 +104,9 @@ - - - + + +