From d68d7edea332e37df2610fa91316ceb7692dabd4 Mon Sep 17 00:00:00 2001 From: voidedWarranties Date: Sat, 7 Mar 2020 14:08:13 -0800 Subject: [PATCH 01/61] Start background video playback based on provided offset --- osu.Game/Beatmaps/BeatmapMetadata.cs | 2 + .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 7 +- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 2 +- .../20200307015200_AddVideoOffset.Designer.cs | 508 ++++++++++++++++++ .../20200307015200_AddVideoOffset.cs | 23 + .../Migrations/OsuDbContextModelSnapshot.cs | 2 + osu.Game/Screens/Play/DimmableVideo.cs | 42 +- .../Screens/Play/GameplayClockContainer.cs | 4 + osu.Game/Screens/Play/Player.cs | 2 +- 9 files changed, 581 insertions(+), 11 deletions(-) create mode 100644 osu.Game/Migrations/20200307015200_AddVideoOffset.Designer.cs create mode 100644 osu.Game/Migrations/20200307015200_AddVideoOffset.cs diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index 9267527d79..a353b1a0b6 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -52,6 +52,7 @@ namespace osu.Game.Beatmaps public int PreviewTime { get; set; } public string AudioFile { get; set; } public string BackgroundFile { get; set; } + public int VideoOffset { get; set; } public string VideoFile { get; set; } public override string ToString() => $"{Artist} - {Title} ({Author})"; @@ -83,6 +84,7 @@ namespace osu.Game.Beatmaps && PreviewTime == other.PreviewTime && AudioFile == other.AudioFile && BackgroundFile == other.BackgroundFile + && VideoOffset == other.VideoOffset && VideoFile == other.VideoFile; } } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 4b01b2490e..8fe08a61b7 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -6,11 +6,11 @@ using System.Collections.Generic; using System.IO; using System.Linq; using osu.Framework.Extensions; -using osu.Game.Beatmaps.Timing; -using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.IO; using osu.Game.Beatmaps.Legacy; +using osu.Game.Beatmaps.Timing; +using osu.Game.IO; +using osu.Game.Rulesets.Objects.Legacy; namespace osu.Game.Beatmaps.Formats { @@ -304,6 +304,7 @@ namespace osu.Game.Beatmaps.Formats break; case LegacyEventType.Video: + beatmap.BeatmapInfo.Metadata.VideoOffset = Parsing.ParseInt(split[1]); beatmap.BeatmapInfo.Metadata.VideoFile = CleanFilename(split[2]); break; diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 09f40ce7b6..7e3e3aacd8 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -134,7 +134,7 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine(FormattableString.Invariant($"{(int)LegacyEventType.Background},0,\"{beatmap.BeatmapInfo.Metadata.BackgroundFile}\",0,0")); if (!string.IsNullOrEmpty(beatmap.BeatmapInfo.Metadata.VideoFile)) - writer.WriteLine(FormattableString.Invariant($"{(int)LegacyEventType.Video},0,\"{beatmap.BeatmapInfo.Metadata.VideoFile}\",0,0")); + writer.WriteLine(FormattableString.Invariant($"{(int)LegacyEventType.Video},{beatmap.BeatmapInfo.Metadata.VideoOffset},\"{beatmap.BeatmapInfo.Metadata.VideoFile}\",0,0")); foreach (var b in beatmap.Breaks) writer.WriteLine(FormattableString.Invariant($"{(int)LegacyEventType.Break},{b.StartTime},{b.EndTime}")); diff --git a/osu.Game/Migrations/20200307015200_AddVideoOffset.Designer.cs b/osu.Game/Migrations/20200307015200_AddVideoOffset.Designer.cs new file mode 100644 index 0000000000..10fea5a8bc --- /dev/null +++ b/osu.Game/Migrations/20200307015200_AddVideoOffset.Designer.cs @@ -0,0 +1,508 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20200307015200_AddVideoOffset")] + partial class AddVideoOffset + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BPM"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("Length"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.Property("VideoFile"); + + b.Property("VideoOffset"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Key") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("SkinInfoID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("SkinInfoID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("ScoreInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("ScoreInfoID"); + + b.ToTable("ScoreFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Accuracy") + .HasColumnType("DECIMAL(1,4)"); + + b.Property("BeatmapInfoID"); + + b.Property("Combo"); + + b.Property("Date"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MaxCombo"); + + b.Property("ModsJson") + .HasColumnName("Mods"); + + b.Property("OnlineScoreID"); + + b.Property("PP"); + + b.Property("Rank"); + + b.Property("RulesetID"); + + b.Property("StatisticsJson") + .HasColumnName("Statistics"); + + b.Property("TotalScore"); + + b.Property("UserID") + .HasColumnName("UserID"); + + b.Property("UserString") + .HasColumnName("User"); + + b.HasKey("ID"); + + b.HasIndex("BeatmapInfoID"); + + b.HasIndex("OnlineScoreID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("ScoreInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Settings") + .HasForeignKey("SkinInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Scoring.ScoreInfo") + .WithMany("Files") + .HasForeignKey("ScoreInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") + .WithMany("Scores") + .HasForeignKey("BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20200307015200_AddVideoOffset.cs b/osu.Game/Migrations/20200307015200_AddVideoOffset.cs new file mode 100644 index 0000000000..06c456c551 --- /dev/null +++ b/osu.Game/Migrations/20200307015200_AddVideoOffset.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddVideoOffset : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "VideoOffset", + table: "BeatmapMetadata", + nullable: false, + defaultValue: 0); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "VideoOffset", + table: "BeatmapMetadata"); + } + } +} diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs index bc4fc3342d..6f91688ddb 100644 --- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs +++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs @@ -141,6 +141,8 @@ namespace osu.Game.Migrations b.Property("VideoFile"); + b.Property("VideoOffset"); + b.HasKey("ID"); b.ToTable("BeatmapMetadata"); diff --git a/osu.Game/Screens/Play/DimmableVideo.cs b/osu.Game/Screens/Play/DimmableVideo.cs index 1a01cace17..2e080d9c2b 100644 --- a/osu.Game/Screens/Play/DimmableVideo.cs +++ b/osu.Game/Screens/Play/DimmableVideo.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Video; +using osu.Framework.Timing; using osu.Game.Graphics.Containers; using osuTK.Graphics; @@ -14,11 +15,13 @@ namespace osu.Game.Screens.Play public class DimmableVideo : UserDimContainer { private readonly VideoSprite video; + private readonly int offset; private DrawableVideo drawableVideo; - public DimmableVideo(VideoSprite video) + public DimmableVideo(VideoSprite video, int offset) { this.video = video; + this.offset = offset; } [BackgroundDependencyLoader] @@ -46,7 +49,7 @@ namespace osu.Game.Screens.Play if (!ShowVideo.Value && !IgnoreUserSettings.Value) return; - drawableVideo = new DrawableVideo(video); + drawableVideo = new DrawableVideo(video, offset); if (async) LoadComponentAsync(drawableVideo, Add); @@ -56,8 +59,15 @@ namespace osu.Game.Screens.Play private class DrawableVideo : Container { - public DrawableVideo(VideoSprite video) + private readonly Drawable cover; + private readonly int offset; + private readonly ManualClock videoClock; + private bool videoStarted; + + public DrawableVideo(VideoSprite video, int offset) { + this.offset = offset; + RelativeSizeAxes = Axes.Both; Masking = true; @@ -66,14 +76,17 @@ namespace osu.Game.Screens.Play video.Anchor = Anchor.Centre; video.Origin = Anchor.Centre; - AddRangeInternal(new Drawable[] + videoClock = new ManualClock(); + video.Clock = new FramedClock(videoClock); + + AddRangeInternal(new[] { - new Box + video, + cover = new Box { RelativeSizeAxes = Axes.Both, Colour = Color4.Black, }, - video, }); } @@ -83,6 +96,23 @@ namespace osu.Game.Screens.Play if (clock != null) Clock = clock; } + + protected override void Update() + { + if (videoClock != null && Clock.CurrentTime > offset) + { + if (!videoStarted) + { + cover.FadeOut(500); + videoStarted = true; + } + + // handle seeking before the video starts (break skipping, replay seek) + videoClock.CurrentTime = Clock.CurrentTime - offset; + } + + base.Update(); + } } } } diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 591e969ad8..da4829d484 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -116,6 +116,10 @@ namespace osu.Game.Screens.Play if (beatmap.BeatmapInfo.AudioLeadIn > 0) startTime = Math.Min(startTime, firstHitObjectTime - beatmap.BeatmapInfo.AudioLeadIn); + // some beatmaps have no AudioLeadIn but the video starts before the first object + if (beatmap.Video != null && beatmap.Metadata.VideoOffset != 0) + startTime = Math.Min(startTime, beatmap.Metadata.VideoOffset); + Seek(startTime); adjustableClock.ProcessFrame(); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index bcadba14af..22d90d4ac1 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -189,7 +189,7 @@ namespace osu.Game.Screens.Play private void addUnderlayComponents(Container target) { - target.Add(DimmableVideo = new DimmableVideo(Beatmap.Value.Video) { RelativeSizeAxes = Axes.Both }); + target.Add(DimmableVideo = new DimmableVideo(Beatmap.Value.Video, Beatmap.Value.Metadata.VideoOffset) { RelativeSizeAxes = Axes.Both }); target.Add(DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard) { RelativeSizeAxes = Axes.Both }); } From 76c832518fcf15f53581c5972efbfd7451584884 Mon Sep 17 00:00:00 2001 From: voidedWarranties Date: Sat, 7 Mar 2020 21:32:03 -0800 Subject: [PATCH 02/61] Render video as a part of the storyboard --- osu.Game.Tests/WaveformTestBeatmap.cs | 3 - osu.Game/Beatmaps/BeatmapManager.cs | 2 - .../Beatmaps/BeatmapManager_WorkingBeatmap.cs | 19 - osu.Game/Beatmaps/BeatmapMetadata.cs | 2 - osu.Game/Beatmaps/DummyWorkingBeatmap.cs | 3 - .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 5 - .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 3 - .../Formats/LegacyStoryboardDecoder.cs | 17 +- osu.Game/Beatmaps/IWorkingBeatmap.cs | 6 - osu.Game/Beatmaps/WorkingBeatmap.cs | 15 +- .../20200307015200_AddVideoOffset.Designer.cs | 508 ------------------ .../20200307015200_AddVideoOffset.cs | 23 - osu.Game/Rulesets/Mods/ModCinema.cs | 1 - osu.Game/Screens/Play/DimmableVideo.cs | 118 ---- .../Screens/Play/GameplayClockContainer.cs | 11 +- osu.Game/Screens/Play/Player.cs | 3 - .../Drawables/DrawableStoryboardVideo.cs | 82 +++ osu.Game/Storyboards/Storyboard.cs | 3 +- osu.Game/Storyboards/StoryboardVideo.cs | 25 + .../Tests/Beatmaps/BeatmapConversionTest.cs | 3 - osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs | 3 - 21 files changed, 136 insertions(+), 719 deletions(-) delete mode 100644 osu.Game/Migrations/20200307015200_AddVideoOffset.Designer.cs delete mode 100644 osu.Game/Migrations/20200307015200_AddVideoOffset.cs delete mode 100644 osu.Game/Screens/Play/DimmableVideo.cs create mode 100644 osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs create mode 100644 osu.Game/Storyboards/StoryboardVideo.cs diff --git a/osu.Game.Tests/WaveformTestBeatmap.cs b/osu.Game.Tests/WaveformTestBeatmap.cs index 53ce5def32..90c91eb007 100644 --- a/osu.Game.Tests/WaveformTestBeatmap.cs +++ b/osu.Game.Tests/WaveformTestBeatmap.cs @@ -6,7 +6,6 @@ using System.Linq; using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Graphics.Textures; -using osu.Framework.Graphics.Video; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; using osu.Game.IO; @@ -51,8 +50,6 @@ namespace osu.Game.Tests protected override Texture GetBackground() => null; - protected override VideoSprite GetVideo() => null; - protected override Waveform GetWaveform() => new Waveform(trackStore.GetStream(firstAudioFile)); protected override Track GetTrack() => trackStore.Get(firstAudioFile); diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 31869f9310..abb3f8ac42 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -14,7 +14,6 @@ using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Extensions; using osu.Framework.Graphics.Textures; -using osu.Framework.Graphics.Video; using osu.Framework.Lists; using osu.Framework.Logging; using osu.Framework.Platform; @@ -403,7 +402,6 @@ namespace osu.Game.Beatmaps protected override IBeatmap GetBeatmap() => beatmap; protected override Texture GetBackground() => null; - protected override VideoSprite GetVideo() => null; protected override Track GetTrack() => null; } diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs index 1991770518..e62a9bb39d 100644 --- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs @@ -6,7 +6,6 @@ using System.Linq; using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Graphics.Textures; -using osu.Framework.Graphics.Video; using osu.Framework.IO.Stores; using osu.Framework.Logging; using osu.Game.Beatmaps.Formats; @@ -67,24 +66,6 @@ namespace osu.Game.Beatmaps } } - protected override VideoSprite GetVideo() - { - if (Metadata?.VideoFile == null) - return null; - - try - { - var stream = textureStore.GetStream(getPathForFile(Metadata.VideoFile)); - - return stream == null ? null : new VideoSprite(stream); - } - catch (Exception e) - { - Logger.Error(e, "Video failed to load"); - return null; - } - } - protected override Track GetTrack() { try diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index a353b1a0b6..9267527d79 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -52,7 +52,6 @@ namespace osu.Game.Beatmaps public int PreviewTime { get; set; } public string AudioFile { get; set; } public string BackgroundFile { get; set; } - public int VideoOffset { get; set; } public string VideoFile { get; set; } public override string ToString() => $"{Artist} - {Title} ({Author})"; @@ -84,7 +83,6 @@ namespace osu.Game.Beatmaps && PreviewTime == other.PreviewTime && AudioFile == other.AudioFile && BackgroundFile == other.BackgroundFile - && VideoOffset == other.VideoOffset && VideoFile == other.VideoFile; } } diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs index bfcc38e4a9..8080e94075 100644 --- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs @@ -7,7 +7,6 @@ using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics.Textures; -using osu.Framework.Graphics.Video; using osu.Game.Rulesets; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; @@ -45,8 +44,6 @@ namespace osu.Game.Beatmaps protected override Texture GetBackground() => textures?.Get(@"Backgrounds/bg4"); - protected override VideoSprite GetVideo() => null; - protected override Track GetTrack() => GetVirtualTrack(); private class DummyRulesetInfo : RulesetInfo diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 8fe08a61b7..f5b27eddd2 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -303,11 +303,6 @@ namespace osu.Game.Beatmaps.Formats beatmap.BeatmapInfo.Metadata.BackgroundFile = CleanFilename(split[2]); break; - case LegacyEventType.Video: - beatmap.BeatmapInfo.Metadata.VideoOffset = Parsing.ParseInt(split[1]); - beatmap.BeatmapInfo.Metadata.VideoFile = CleanFilename(split[2]); - break; - case LegacyEventType.Break: double start = getOffsetTime(Parsing.ParseDouble(split[1])); diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 7e3e3aacd8..ec2ca30535 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -133,9 +133,6 @@ namespace osu.Game.Beatmaps.Formats if (!string.IsNullOrEmpty(beatmap.BeatmapInfo.Metadata.BackgroundFile)) writer.WriteLine(FormattableString.Invariant($"{(int)LegacyEventType.Background},0,\"{beatmap.BeatmapInfo.Metadata.BackgroundFile}\",0,0")); - if (!string.IsNullOrEmpty(beatmap.BeatmapInfo.Metadata.VideoFile)) - writer.WriteLine(FormattableString.Invariant($"{(int)LegacyEventType.Video},{beatmap.BeatmapInfo.Metadata.VideoOffset},\"{beatmap.BeatmapInfo.Metadata.VideoFile}\",0,0")); - foreach (var b in beatmap.Breaks) writer.WriteLine(FormattableString.Invariant($"{(int)LegacyEventType.Break},{b.StartTime},{b.EndTime}")); } diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index 6569f76b2d..b44d4947d4 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -5,13 +5,13 @@ using System; using System.Collections.Generic; using System.Globalization; using System.IO; -using osuTK; -using osuTK.Graphics; using osu.Framework.Graphics; +using osu.Framework.Utils; +using osu.Game.Beatmaps.Legacy; using osu.Game.IO; using osu.Game.Storyboards; -using osu.Game.Beatmaps.Legacy; -using osu.Framework.Utils; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Beatmaps.Formats { @@ -88,6 +88,15 @@ namespace osu.Game.Beatmaps.Formats switch (type) { + case LegacyEventType.Video: + { + var offset = Parsing.ParseInt(split[1]); + var filename = CleanFilename(split[2]); + + storyboard.GetLayer("Video").Add(new StoryboardVideo(filename, offset)); + break; + } + case LegacyEventType.Sprite: { var layer = parseLayer(split[1]); diff --git a/osu.Game/Beatmaps/IWorkingBeatmap.cs b/osu.Game/Beatmaps/IWorkingBeatmap.cs index 5f1f0d1e40..155e603d30 100644 --- a/osu.Game/Beatmaps/IWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/IWorkingBeatmap.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using osu.Framework.Audio.Track; using osu.Framework.Graphics.Textures; -using osu.Framework.Graphics.Video; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; @@ -26,11 +25,6 @@ namespace osu.Game.Beatmaps /// Texture Background { get; } - /// - /// Retrieves the video background file for this . - /// - VideoSprite Video { get; } - /// /// Retrieves the audio track for this . /// diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 1e1ffad81e..ad94ae8050 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -1,23 +1,22 @@ // 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.Audio.Track; -using osu.Framework.Graphics.Textures; -using osu.Game.Rulesets.Mods; using System; using System.Collections.Generic; -using osu.Game.Storyboards; using System.Linq; using System.Threading; using System.Threading.Tasks; using osu.Framework.Audio; +using osu.Framework.Audio.Track; +using osu.Framework.Graphics.Textures; +using osu.Framework.Logging; using osu.Framework.Statistics; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.UI; using osu.Game.Skinning; -using osu.Framework.Graphics.Video; -using osu.Framework.Logging; +using osu.Game.Storyboards; namespace osu.Game.Beatmaps { @@ -208,10 +207,6 @@ namespace osu.Game.Beatmaps protected abstract Texture GetBackground(); private readonly RecyclableLazy background; - public VideoSprite Video => GetVideo(); - - protected abstract VideoSprite GetVideo(); - public bool TrackLoaded => track.IsResultAvailable; public Track Track => track.Value; protected abstract Track GetTrack(); diff --git a/osu.Game/Migrations/20200307015200_AddVideoOffset.Designer.cs b/osu.Game/Migrations/20200307015200_AddVideoOffset.Designer.cs deleted file mode 100644 index 10fea5a8bc..0000000000 --- a/osu.Game/Migrations/20200307015200_AddVideoOffset.Designer.cs +++ /dev/null @@ -1,508 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using osu.Game.Database; - -namespace osu.Game.Migrations -{ - [DbContext(typeof(OsuDbContext))] - [Migration("20200307015200_AddVideoOffset")] - partial class AddVideoOffset - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("ApproachRate"); - - b.Property("CircleSize"); - - b.Property("DrainRate"); - - b.Property("OverallDifficulty"); - - b.Property("SliderMultiplier"); - - b.Property("SliderTickRate"); - - b.HasKey("ID"); - - b.ToTable("BeatmapDifficulty"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("AudioLeadIn"); - - b.Property("BPM"); - - b.Property("BaseDifficultyID"); - - b.Property("BeatDivisor"); - - b.Property("BeatmapSetInfoID"); - - b.Property("Countdown"); - - b.Property("DistanceSpacing"); - - b.Property("GridSize"); - - b.Property("Hash"); - - b.Property("Hidden"); - - b.Property("Length"); - - b.Property("LetterboxInBreaks"); - - b.Property("MD5Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapID"); - - b.Property("Path"); - - b.Property("RulesetID"); - - b.Property("SpecialStyle"); - - b.Property("StackLeniency"); - - b.Property("StarDifficulty"); - - b.Property("Status"); - - b.Property("StoredBookmarks"); - - b.Property("TimelineZoom"); - - b.Property("Version"); - - b.Property("WidescreenStoryboard"); - - b.HasKey("ID"); - - b.HasIndex("BaseDifficultyID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("Hash"); - - b.HasIndex("MD5Hash"); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("BeatmapInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Artist"); - - b.Property("ArtistUnicode"); - - b.Property("AudioFile"); - - b.Property("AuthorString") - .HasColumnName("Author"); - - b.Property("BackgroundFile"); - - b.Property("PreviewTime"); - - b.Property("Source"); - - b.Property("Tags"); - - b.Property("Title"); - - b.Property("TitleUnicode"); - - b.Property("VideoFile"); - - b.Property("VideoOffset"); - - b.HasKey("ID"); - - b.ToTable("BeatmapMetadata"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("BeatmapSetInfoID"); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.HasKey("ID"); - - b.HasIndex("BeatmapSetInfoID"); - - b.HasIndex("FileInfoID"); - - b.ToTable("BeatmapSetFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("DateAdded"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MetadataID"); - - b.Property("OnlineBeatmapSetID"); - - b.Property("Protected"); - - b.Property("Status"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("MetadataID"); - - b.HasIndex("OnlineBeatmapSetID") - .IsUnique(); - - b.ToTable("BeatmapSetInfo"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Key") - .HasColumnName("Key"); - - b.Property("RulesetID"); - - b.Property("SkinInfoID"); - - b.Property("StringValue") - .HasColumnName("Value"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("SkinInfoID"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("Settings"); - }); - - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - - modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("IntAction") - .HasColumnName("Action"); - - b.Property("KeysString") - .HasColumnName("Keys"); - - b.Property("RulesetID"); - - b.Property("Variant"); - - b.HasKey("ID"); - - b.HasIndex("IntAction"); - - b.HasIndex("RulesetID", "Variant"); - - b.ToTable("KeyBinding"); - }); - - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Available"); - - b.Property("InstantiationInfo"); - - b.Property("Name"); - - b.Property("ShortName"); - - b.HasKey("ID"); - - b.HasIndex("Available"); - - b.HasIndex("ShortName") - .IsUnique(); - - b.ToTable("RulesetInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("ScoreInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("ScoreInfoID"); - - b.ToTable("ScoreFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Accuracy") - .HasColumnType("DECIMAL(1,4)"); - - b.Property("BeatmapInfoID"); - - b.Property("Combo"); - - b.Property("Date"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("MaxCombo"); - - b.Property("ModsJson") - .HasColumnName("Mods"); - - b.Property("OnlineScoreID"); - - b.Property("PP"); - - b.Property("Rank"); - - b.Property("RulesetID"); - - b.Property("StatisticsJson") - .HasColumnName("Statistics"); - - b.Property("TotalScore"); - - b.Property("UserID") - .HasColumnName("UserID"); - - b.Property("UserString") - .HasColumnName("User"); - - b.HasKey("ID"); - - b.HasIndex("BeatmapInfoID"); - - b.HasIndex("OnlineScoreID") - .IsUnique(); - - b.HasIndex("RulesetID"); - - b.ToTable("ScoreInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("FileInfoID"); - - b.Property("Filename") - .IsRequired(); - - b.Property("SkinInfoID"); - - b.HasKey("ID"); - - b.HasIndex("FileInfoID"); - - b.HasIndex("SkinInfoID"); - - b.ToTable("SkinFileInfo"); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Creator"); - - b.Property("DeletePending"); - - b.Property("Hash"); - - b.Property("Name"); - - b.HasKey("ID"); - - b.HasIndex("DeletePending"); - - b.HasIndex("Hash") - .IsUnique(); - - b.ToTable("SkinInfo"); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") - .WithMany() - .HasForeignKey("BaseDifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") - .WithMany("Beatmaps") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("Beatmaps") - .HasForeignKey("MetadataID"); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithMany("Files") - .HasForeignKey("BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany("BeatmapSets") - .HasForeignKey("MetadataID"); - }); - - modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => - { - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Settings") - .HasForeignKey("SkinInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Scoring.ScoreInfo") - .WithMany("Files") - .HasForeignKey("ScoreInfoID"); - }); - - modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") - .WithMany("Scores") - .HasForeignKey("BeatmapInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") - .WithMany() - .HasForeignKey("RulesetID") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => - { - b.HasOne("osu.Game.IO.FileInfo", "FileInfo") - .WithMany() - .HasForeignKey("FileInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Skinning.SkinInfo") - .WithMany("Files") - .HasForeignKey("SkinInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/osu.Game/Migrations/20200307015200_AddVideoOffset.cs b/osu.Game/Migrations/20200307015200_AddVideoOffset.cs deleted file mode 100644 index 06c456c551..0000000000 --- a/osu.Game/Migrations/20200307015200_AddVideoOffset.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace osu.Game.Migrations -{ - public partial class AddVideoOffset : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "VideoOffset", - table: "BeatmapMetadata", - nullable: false, - defaultValue: 0); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "VideoOffset", - table: "BeatmapMetadata"); - } - } -} diff --git a/osu.Game/Rulesets/Mods/ModCinema.cs b/osu.Game/Rulesets/Mods/ModCinema.cs index cd08aee453..cf8128301c 100644 --- a/osu.Game/Rulesets/Mods/ModCinema.cs +++ b/osu.Game/Rulesets/Mods/ModCinema.cs @@ -39,7 +39,6 @@ namespace osu.Game.Rulesets.Mods { player.Background.EnableUserDim.Value = false; - player.DimmableVideo.IgnoreUserSettings.Value = true; player.DimmableStoryboard.IgnoreUserSettings.Value = true; player.BreakOverlay.Hide(); diff --git a/osu.Game/Screens/Play/DimmableVideo.cs b/osu.Game/Screens/Play/DimmableVideo.cs deleted file mode 100644 index 2e080d9c2b..0000000000 --- a/osu.Game/Screens/Play/DimmableVideo.cs +++ /dev/null @@ -1,118 +0,0 @@ -// 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.Video; -using osu.Framework.Timing; -using osu.Game.Graphics.Containers; -using osuTK.Graphics; - -namespace osu.Game.Screens.Play -{ - public class DimmableVideo : UserDimContainer - { - private readonly VideoSprite video; - private readonly int offset; - private DrawableVideo drawableVideo; - - public DimmableVideo(VideoSprite video, int offset) - { - this.video = video; - this.offset = offset; - } - - [BackgroundDependencyLoader] - private void load() - { - initializeVideo(false); - } - - protected override void LoadComplete() - { - ShowVideo.BindValueChanged(_ => initializeVideo(true), true); - base.LoadComplete(); - } - - protected override bool ShowDimContent => IgnoreUserSettings.Value || (ShowVideo.Value && DimLevel < 1); - - private void initializeVideo(bool async) - { - if (video == null) - return; - - if (drawableVideo != null) - return; - - if (!ShowVideo.Value && !IgnoreUserSettings.Value) - return; - - drawableVideo = new DrawableVideo(video, offset); - - if (async) - LoadComponentAsync(drawableVideo, Add); - else - Add(drawableVideo); - } - - private class DrawableVideo : Container - { - private readonly Drawable cover; - private readonly int offset; - private readonly ManualClock videoClock; - private bool videoStarted; - - public DrawableVideo(VideoSprite video, int offset) - { - this.offset = offset; - - RelativeSizeAxes = Axes.Both; - Masking = true; - - video.RelativeSizeAxes = Axes.Both; - video.FillMode = FillMode.Fit; - video.Anchor = Anchor.Centre; - video.Origin = Anchor.Centre; - - videoClock = new ManualClock(); - video.Clock = new FramedClock(videoClock); - - AddRangeInternal(new[] - { - video, - cover = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - }, - }); - } - - [BackgroundDependencyLoader] - private void load(GameplayClock clock) - { - if (clock != null) - Clock = clock; - } - - protected override void Update() - { - if (videoClock != null && Clock.CurrentTime > offset) - { - if (!videoStarted) - { - cover.FadeOut(500); - videoStarted = true; - } - - // handle seeking before the video starts (break skipping, replay seek) - videoClock.CurrentTime = Clock.CurrentTime - offset; - } - - base.Update(); - } - } - } -} diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index da4829d484..ac0a4bcadc 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -117,8 +117,15 @@ namespace osu.Game.Screens.Play startTime = Math.Min(startTime, firstHitObjectTime - beatmap.BeatmapInfo.AudioLeadIn); // some beatmaps have no AudioLeadIn but the video starts before the first object - if (beatmap.Video != null && beatmap.Metadata.VideoOffset != 0) - startTime = Math.Min(startTime, beatmap.Metadata.VideoOffset); + var videoLayer = beatmap.Storyboard.GetLayer("Video"); + + if (videoLayer.Elements.Any()) + { + var videoOffset = videoLayer.Elements.First().StartTime; + + if (videoOffset != 0) + startTime = Math.Min(startTime, videoOffset); + } Seek(startTime); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 22d90d4ac1..b90d9d982a 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -85,7 +85,6 @@ namespace osu.Game.Screens.Play protected GameplayClockContainer GameplayClockContainer { get; private set; } public DimmableStoryboard DimmableStoryboard { get; private set; } - public DimmableVideo DimmableVideo { get; private set; } [Cached] [Cached(Type = typeof(IBindable>))] @@ -189,7 +188,6 @@ namespace osu.Game.Screens.Play private void addUnderlayComponents(Container target) { - target.Add(DimmableVideo = new DimmableVideo(Beatmap.Value.Video, Beatmap.Value.Metadata.VideoOffset) { RelativeSizeAxes = Axes.Both }); target.Add(DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard) { RelativeSizeAxes = Axes.Both }); } @@ -549,7 +547,6 @@ namespace osu.Game.Screens.Play // bind component bindables. Background.IsBreakTime.BindTo(BreakOverlay.IsBreakTime); DimmableStoryboard.IsBreakTime.BindTo(BreakOverlay.IsBreakTime); - DimmableVideo.IsBreakTime.BindTo(BreakOverlay.IsBreakTime); Background.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground); DimmableStoryboard.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground); diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs new file mode 100644 index 0000000000..2c887a1553 --- /dev/null +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs @@ -0,0 +1,82 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Textures; +using osu.Framework.Graphics.Video; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Screens.Play; + +namespace osu.Game.Storyboards.Drawables +{ + public class DrawableStoryboardVideo : Container + { + public readonly StoryboardVideo Video; + private VideoSprite videoSprite; + private ManualClock videoClock; + private GameplayClock clock; + + private bool videoStarted; + + public override bool RemoveWhenNotAlive => false; + + public DrawableStoryboardVideo(StoryboardVideo video) + { + Video = video; + + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load(GameplayClock clock, IBindable beatmap, TextureStore textureStore) + { + if (clock != null) + this.clock = clock; + + var path = beatmap.Value.BeatmapSetInfo?.Files?.Find(f => f.Filename.Equals(Video.Path, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; + + if (path == null) + return; + + var stream = textureStore.GetStream(path); + + if (stream == null) + return; + + AddInternal(videoSprite = new VideoSprite(stream) + { + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fill, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AlwaysPresent = true, + Alpha = 0 + }); + + videoClock = new ManualClock(); + videoSprite.Clock = new FramedClock(videoClock); + } + + protected override void Update() + { + if (clock.CurrentTime > Video.StartTime) + { + if (!videoStarted) + { + videoSprite.FadeIn(500); + videoStarted = true; + } + + // handle seeking before the video starts (break skipping, replay seek) + videoClock.CurrentTime = clock.CurrentTime - Video.StartTime; + } + + base.Update(); + } + } +} diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index 35bfe8c229..e58c422c6d 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -21,6 +21,7 @@ namespace osu.Game.Storyboards public Storyboard() { + layers.Add("Video", new StoryboardLayer("Video", 4)); layers.Add("Background", new StoryboardLayer("Background", 3)); layers.Add("Fail", new StoryboardLayer("Fail", 2) { EnabledWhenPassing = false, }); layers.Add("Pass", new StoryboardLayer("Pass", 1) { EnabledWhenFailing = false, }); @@ -53,7 +54,7 @@ namespace osu.Game.Storyboards public DrawableStoryboard CreateDrawable(WorkingBeatmap working = null) { var drawable = new DrawableStoryboard(this); - drawable.Width = drawable.Height * (BeatmapInfo.WidescreenStoryboard ? 16 / 9f : 4 / 3f); + drawable.Width = drawable.Height * (BeatmapInfo.WidescreenStoryboard || GetLayer("Video").Elements.Any() ? 16 / 9f : 4 / 3f); return drawable; } } diff --git a/osu.Game/Storyboards/StoryboardVideo.cs b/osu.Game/Storyboards/StoryboardVideo.cs new file mode 100644 index 0000000000..4652e45852 --- /dev/null +++ b/osu.Game/Storyboards/StoryboardVideo.cs @@ -0,0 +1,25 @@ +// 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.Graphics; +using osu.Game.Storyboards.Drawables; + +namespace osu.Game.Storyboards +{ + public class StoryboardVideo : IStoryboardElement + { + public string Path { get; } + + public bool IsDrawable => true; + + public double StartTime { get; } + + public StoryboardVideo(string path, int offset) + { + Path = path; + StartTime = offset; + } + + public Drawable CreateDrawable() => new DrawableStoryboardVideo(this); + } +} diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs index ef86186e41..b60add6e3b 100644 --- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs @@ -10,7 +10,6 @@ using Newtonsoft.Json; using NUnit.Framework; using osu.Framework.Audio.Track; using osu.Framework.Graphics.Textures; -using osu.Framework.Graphics.Video; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; using osu.Game.IO; @@ -207,8 +206,6 @@ namespace osu.Game.Tests.Beatmaps protected override Texture GetBackground() => throw new NotImplementedException(); - protected override VideoSprite GetVideo() => throw new NotImplementedException(); - protected override Track GetTrack() => throw new NotImplementedException(); protected override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap, Ruleset ruleset) diff --git a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs index 871d8ee3f1..6db34af20c 100644 --- a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs @@ -3,7 +3,6 @@ using osu.Framework.Audio.Track; using osu.Framework.Graphics.Textures; -using osu.Framework.Graphics.Video; using osu.Game.Beatmaps; using osu.Game.Storyboards; @@ -32,8 +31,6 @@ namespace osu.Game.Tests.Beatmaps protected override Texture GetBackground() => null; - protected override VideoSprite GetVideo() => null; - protected override Track GetTrack() => null; } } From 48282dea8bd4fbd172efa6848aa88c87b4a103e9 Mon Sep 17 00:00:00 2001 From: voidedWarranties Date: Sat, 7 Mar 2020 22:08:38 -0800 Subject: [PATCH 03/61] Remove individual setting to disable videos, fix tests --- .../Beatmaps/Formats/LegacyStoryboardDecoderTest.cs | 2 +- osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs | 5 +++++ osu.Game/Configuration/OsuConfigManager.cs | 2 -- osu.Game/Graphics/Containers/UserDimContainer.cs | 4 ---- .../Settings/Sections/Graphics/DetailSettings.cs | 5 ----- .../Screens/Backgrounds/BackgroundScreenBeatmap.cs | 2 +- osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs | 3 --- .../Storyboards/Drawables/DrawableStoryboardVideo.cs | 10 ++++++---- 8 files changed, 13 insertions(+), 20 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs index 96ff6b81e3..edb0d9f04b 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs @@ -26,7 +26,7 @@ namespace osu.Game.Tests.Beatmaps.Formats var storyboard = decoder.Decode(stream); Assert.IsTrue(storyboard.HasDrawable); - Assert.AreEqual(4, storyboard.Layers.Count()); + Assert.AreEqual(5, storyboard.Layers.Count()); StoryboardLayer background = storyboard.Layers.FirstOrDefault(l => l.Depth == 3); Assert.IsNotNull(background); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs index ff8437311e..16a985c796 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Overlays; +using osu.Game.Screens.Play; using osu.Game.Storyboards.Drawables; using osuTK.Graphics; @@ -24,9 +25,13 @@ namespace osu.Game.Tests.Visual.Gameplay [Cached] private MusicController musicController = new MusicController(); + [Cached] + private GameplayClock gameplayClock; + public TestSceneStoryboard() { Clock = new FramedClock(); + gameplayClock = new GameplayClock(Clock); AddRange(new Drawable[] { diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 21de654670..41f6747b74 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -70,7 +70,6 @@ namespace osu.Game.Configuration Set(OsuSetting.ShowFpsDisplay, false); Set(OsuSetting.ShowStoryboard, true); - Set(OsuSetting.ShowVideoBackground, true); Set(OsuSetting.BeatmapSkins, true); Set(OsuSetting.BeatmapHitsounds, true); @@ -176,7 +175,6 @@ namespace osu.Game.Configuration BlurLevel, LightenDuringBreaks, ShowStoryboard, - ShowVideoBackground, KeyOverlay, ScoreMeter, FloatingComments, diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index 65c104b92f..4485ce3447 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -55,8 +55,6 @@ namespace osu.Game.Graphics.Containers protected Bindable ShowStoryboard { get; private set; } - protected Bindable ShowVideo { get; private set; } - private float breakLightening => LightenDuringBreaks.Value && IsBreakTime.Value ? BREAK_LIGHTEN_AMOUNT : 0; protected float DimLevel => Math.Max(EnableUserDim.Value && !IgnoreUserSettings.Value ? (float)UserDimLevel.Value - breakLightening : 0, 0); @@ -79,14 +77,12 @@ namespace osu.Game.Graphics.Containers UserDimLevel = config.GetBindable(OsuSetting.DimLevel); LightenDuringBreaks = config.GetBindable(OsuSetting.LightenDuringBreaks); ShowStoryboard = config.GetBindable(OsuSetting.ShowStoryboard); - ShowVideo = config.GetBindable(OsuSetting.ShowVideoBackground); EnableUserDim.ValueChanged += _ => UpdateVisuals(); UserDimLevel.ValueChanged += _ => UpdateVisuals(); LightenDuringBreaks.ValueChanged += _ => UpdateVisuals(); IsBreakTime.ValueChanged += _ => UpdateVisuals(); ShowStoryboard.ValueChanged += _ => UpdateVisuals(); - ShowVideo.ValueChanged += _ => UpdateVisuals(); StoryboardReplacesBackground.ValueChanged += _ => UpdateVisuals(); IgnoreUserSettings.ValueChanged += _ => UpdateVisuals(); } diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs index ea2811e5cd..acf33f00b3 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs @@ -22,11 +22,6 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics Bindable = config.GetBindable(OsuSetting.ShowStoryboard) }, new SettingsCheckbox - { - LabelText = "Video", - Bindable = config.GetBindable(OsuSetting.ShowVideoBackground) - }, - new SettingsCheckbox { LabelText = "Hit Lighting", Bindable = config.GetBindable(OsuSetting.HitLighting) diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs index 50fd127093..b08455be95 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs @@ -166,7 +166,7 @@ namespace osu.Game.Screens.Backgrounds BlurAmount.ValueChanged += _ => UpdateVisuals(); } - protected override bool ShowDimContent => !ShowStoryboard.Value || !StoryboardReplacesBackground.Value || !ShowVideo.Value; // The background needs to be hidden in the case of it being replaced by the storyboard + protected override bool ShowDimContent => !ShowStoryboard.Value || !StoryboardReplacesBackground.Value; // The background needs to be hidden in the case of it being replaced by the storyboard protected override void UpdateVisuals() { diff --git a/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs b/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs index 9db3a587fa..bfb77e823f 100644 --- a/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs @@ -15,7 +15,6 @@ namespace osu.Game.Screens.Play.PlayerSettings private readonly PlayerSliderBar dimSliderBar; private readonly PlayerSliderBar blurSliderBar; private readonly PlayerCheckbox showStoryboardToggle; - private readonly PlayerCheckbox showVideoToggle; private readonly PlayerCheckbox beatmapSkinsToggle; private readonly PlayerCheckbox beatmapHitsoundsToggle; @@ -44,7 +43,6 @@ namespace osu.Game.Screens.Play.PlayerSettings Text = "Toggles:" }, showStoryboardToggle = new PlayerCheckbox { LabelText = "Storyboards" }, - showVideoToggle = new PlayerCheckbox { LabelText = "Video" }, beatmapSkinsToggle = new PlayerCheckbox { LabelText = "Beatmap skins" }, beatmapHitsoundsToggle = new PlayerCheckbox { LabelText = "Beatmap hitsounds" } }; @@ -56,7 +54,6 @@ namespace osu.Game.Screens.Play.PlayerSettings dimSliderBar.Bindable = config.GetBindable(OsuSetting.DimLevel); blurSliderBar.Bindable = config.GetBindable(OsuSetting.BlurLevel); showStoryboardToggle.Current = config.GetBindable(OsuSetting.ShowStoryboard); - showVideoToggle.Current = config.GetBindable(OsuSetting.ShowVideoBackground); beatmapSkinsToggle.Current = config.GetBindable(OsuSetting.BeatmapSkins); beatmapHitsoundsToggle.Current = config.GetBindable(OsuSetting.BeatmapHitsounds); } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs index 2c887a1553..ef14ccd4d7 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs @@ -32,11 +32,13 @@ namespace osu.Game.Storyboards.Drawables RelativeSizeAxes = Axes.Both; } - [BackgroundDependencyLoader] + [BackgroundDependencyLoader(true)] private void load(GameplayClock clock, IBindable beatmap, TextureStore textureStore) { - if (clock != null) - this.clock = clock; + if (clock == null) + return; + + this.clock = clock; var path = beatmap.Value.BeatmapSetInfo?.Files?.Find(f => f.Filename.Equals(Video.Path, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; @@ -64,7 +66,7 @@ namespace osu.Game.Storyboards.Drawables protected override void Update() { - if (clock.CurrentTime > Video.StartTime) + if (clock != null && clock.CurrentTime > Video.StartTime) { if (!videoStarted) { From 22dd93a4f6a0ef4367e0ee0591b672659d35ad13 Mon Sep 17 00:00:00 2001 From: voidedWarranties Date: Sun, 8 Mar 2020 14:02:39 -0700 Subject: [PATCH 04/61] Code quality, read position offsets --- osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs | 4 +++- osu.Game/Beatmaps/Legacy/LegacyStoryLayer.cs | 5 +++-- osu.Game/Migrations/OsuDbContextModelSnapshot.cs | 2 -- osu.Game/Screens/Play/GameplayClockContainer.cs | 12 +++++------- .../Storyboards/Drawables/DrawableStoryboardVideo.cs | 4 +++- osu.Game/Storyboards/Storyboard.cs | 7 +++++-- osu.Game/Storyboards/StoryboardVideo.cs | 8 +++++++- 7 files changed, 26 insertions(+), 16 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index b44d4947d4..0358ec2e42 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -92,8 +92,10 @@ namespace osu.Game.Beatmaps.Formats { var offset = Parsing.ParseInt(split[1]); var filename = CleanFilename(split[2]); + var xOffset = split.Length > 3 ? Parsing.ParseInt(split[3]) : 0; + var yOffset = split.Length > 4 ? Parsing.ParseInt(split[4]) : 0; - storyboard.GetLayer("Video").Add(new StoryboardVideo(filename, offset)); + storyboard.GetLayer(LegacyStoryLayer.Video).Add(new StoryboardVideo(filename, offset, xOffset, yOffset)); break; } diff --git a/osu.Game/Beatmaps/Legacy/LegacyStoryLayer.cs b/osu.Game/Beatmaps/Legacy/LegacyStoryLayer.cs index 5237445640..c1329921ec 100644 --- a/osu.Game/Beatmaps/Legacy/LegacyStoryLayer.cs +++ b/osu.Game/Beatmaps/Legacy/LegacyStoryLayer.cs @@ -3,11 +3,12 @@ namespace osu.Game.Beatmaps.Legacy { - internal enum LegacyStoryLayer + public enum LegacyStoryLayer { Background = 0, Fail = 1, Pass = 2, - Foreground = 3 + Foreground = 3, + Video = 4 } } diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs index 6f91688ddb..bc4fc3342d 100644 --- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs +++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs @@ -141,8 +141,6 @@ namespace osu.Game.Migrations b.Property("VideoFile"); - b.Property("VideoOffset"); - b.HasKey("ID"); b.ToTable("BeatmapMetadata"); diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index ac0a4bcadc..87b0c196d3 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -14,6 +14,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Legacy; using osu.Game.Configuration; using osu.Game.Rulesets.Mods; @@ -117,15 +118,12 @@ namespace osu.Game.Screens.Play startTime = Math.Min(startTime, firstHitObjectTime - beatmap.BeatmapInfo.AudioLeadIn); // some beatmaps have no AudioLeadIn but the video starts before the first object - var videoLayer = beatmap.Storyboard.GetLayer("Video"); + var videoLayer = beatmap.Storyboard.GetLayer(LegacyStoryLayer.Video); - if (videoLayer.Elements.Any()) - { - var videoOffset = videoLayer.Elements.First().StartTime; + var videoOffset = videoLayer.Elements.SingleOrDefault()?.StartTime; - if (videoOffset != 0) - startTime = Math.Min(startTime, videoOffset); - } + if (videoOffset != null) + startTime = Math.Min(startTime, videoOffset.GetValueOrDefault()); Seek(startTime); diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs index ef14ccd4d7..b46150785b 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Video; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Screens.Play; +using osuTK; namespace osu.Game.Storyboards.Drawables { @@ -57,7 +58,8 @@ namespace osu.Game.Storyboards.Drawables Anchor = Anchor.Centre, Origin = Anchor.Centre, AlwaysPresent = true, - Alpha = 0 + Alpha = 0, + Position = new Vector2(Video.XOffset, Video.YOffset) }); videoClock = new ManualClock(); diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index e58c422c6d..fac489838e 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Legacy; using osu.Game.Storyboards.Drawables; using System.Collections.Generic; using System.Linq; @@ -28,6 +29,8 @@ namespace osu.Game.Storyboards layers.Add("Foreground", new StoryboardLayer("Foreground", 0)); } + public StoryboardLayer GetLayer(LegacyStoryLayer layer) => GetLayer(layer.ToString()); + public StoryboardLayer GetLayer(string name) { if (!layers.TryGetValue(name, out var layer)) @@ -47,14 +50,14 @@ namespace osu.Game.Storyboards if (backgroundPath == null) return false; - return GetLayer("Background").Elements.Any(e => e.Path.ToLowerInvariant() == backgroundPath); + return GetLayer(LegacyStoryLayer.Background).Elements.Any(e => e.Path.ToLowerInvariant() == backgroundPath); } } public DrawableStoryboard CreateDrawable(WorkingBeatmap working = null) { var drawable = new DrawableStoryboard(this); - drawable.Width = drawable.Height * (BeatmapInfo.WidescreenStoryboard || GetLayer("Video").Elements.Any() ? 16 / 9f : 4 / 3f); + drawable.Width = drawable.Height * (BeatmapInfo.WidescreenStoryboard || GetLayer(LegacyStoryLayer.Video).Elements.Any() ? 16 / 9f : 4 / 3f); return drawable; } } diff --git a/osu.Game/Storyboards/StoryboardVideo.cs b/osu.Game/Storyboards/StoryboardVideo.cs index 4652e45852..7d22b8f7c9 100644 --- a/osu.Game/Storyboards/StoryboardVideo.cs +++ b/osu.Game/Storyboards/StoryboardVideo.cs @@ -14,10 +14,16 @@ namespace osu.Game.Storyboards public double StartTime { get; } - public StoryboardVideo(string path, int offset) + public int XOffset { get; } + + public int YOffset { get; } + + public StoryboardVideo(string path, int offset, int xOffset, int yOffset) { Path = path; StartTime = offset; + XOffset = xOffset; + YOffset = yOffset; } public Drawable CreateDrawable() => new DrawableStoryboardVideo(this); From 4624582703724ef12fabd3410d5fcaf6abcd63bb Mon Sep 17 00:00:00 2001 From: voidedWarranties Date: Sun, 8 Mar 2020 14:40:36 -0700 Subject: [PATCH 05/61] Revert position offset change for separate pull --- osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs | 4 +--- osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs | 4 +--- osu.Game/Storyboards/StoryboardVideo.cs | 8 +------- 3 files changed, 3 insertions(+), 13 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index 0358ec2e42..be82721a76 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -92,10 +92,8 @@ namespace osu.Game.Beatmaps.Formats { var offset = Parsing.ParseInt(split[1]); var filename = CleanFilename(split[2]); - var xOffset = split.Length > 3 ? Parsing.ParseInt(split[3]) : 0; - var yOffset = split.Length > 4 ? Parsing.ParseInt(split[4]) : 0; - storyboard.GetLayer(LegacyStoryLayer.Video).Add(new StoryboardVideo(filename, offset, xOffset, yOffset)); + storyboard.GetLayer(LegacyStoryLayer.Video).Add(new StoryboardVideo(filename, offset)); break; } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs index b46150785b..ef14ccd4d7 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics.Video; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Screens.Play; -using osuTK; namespace osu.Game.Storyboards.Drawables { @@ -58,8 +57,7 @@ namespace osu.Game.Storyboards.Drawables Anchor = Anchor.Centre, Origin = Anchor.Centre, AlwaysPresent = true, - Alpha = 0, - Position = new Vector2(Video.XOffset, Video.YOffset) + Alpha = 0 }); videoClock = new ManualClock(); diff --git a/osu.Game/Storyboards/StoryboardVideo.cs b/osu.Game/Storyboards/StoryboardVideo.cs index 7d22b8f7c9..4652e45852 100644 --- a/osu.Game/Storyboards/StoryboardVideo.cs +++ b/osu.Game/Storyboards/StoryboardVideo.cs @@ -14,16 +14,10 @@ namespace osu.Game.Storyboards public double StartTime { get; } - public int XOffset { get; } - - public int YOffset { get; } - - public StoryboardVideo(string path, int offset, int xOffset, int yOffset) + public StoryboardVideo(string path, int offset) { Path = path; StartTime = offset; - XOffset = xOffset; - YOffset = yOffset; } public Drawable CreateDrawable() => new DrawableStoryboardVideo(this); From 5aa99d8b34aff0c44911f852e2a673135a0fd416 Mon Sep 17 00:00:00 2001 From: voidedWarranties Date: Mon, 9 Mar 2020 16:04:23 -0700 Subject: [PATCH 06/61] Hide background image when video is present --- osu.Game/Storyboards/Storyboard.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index fac489838e..64ad0208c9 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -50,6 +50,9 @@ namespace osu.Game.Storyboards if (backgroundPath == null) return false; + if (GetLayer(LegacyStoryLayer.Video).Elements.Any()) + return true; + return GetLayer(LegacyStoryLayer.Background).Elements.Any(e => e.Path.ToLowerInvariant() == backgroundPath); } } From 6de244389bdff557869a0491ceeb0193458e2cdb Mon Sep 17 00:00:00 2001 From: voidedWarranties Date: Tue, 10 Mar 2020 22:50:20 -0700 Subject: [PATCH 07/61] Use OffsetClock instead of ManualClock --- .../Visual/Gameplay/TestSceneStoryboard.cs | 5 --- .../Screens/Play/GameplayClockContainer.cs | 9 ------ .../Drawables/DrawableStoryboardVideo.cs | 32 +++++-------------- 3 files changed, 8 insertions(+), 38 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs index 16a985c796..ff8437311e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Overlays; -using osu.Game.Screens.Play; using osu.Game.Storyboards.Drawables; using osuTK.Graphics; @@ -25,13 +24,9 @@ namespace osu.Game.Tests.Visual.Gameplay [Cached] private MusicController musicController = new MusicController(); - [Cached] - private GameplayClock gameplayClock; - public TestSceneStoryboard() { Clock = new FramedClock(); - gameplayClock = new GameplayClock(Clock); AddRange(new Drawable[] { diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 87b0c196d3..591e969ad8 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -14,7 +14,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; using osu.Game.Beatmaps; -using osu.Game.Beatmaps.Legacy; using osu.Game.Configuration; using osu.Game.Rulesets.Mods; @@ -117,14 +116,6 @@ namespace osu.Game.Screens.Play if (beatmap.BeatmapInfo.AudioLeadIn > 0) startTime = Math.Min(startTime, firstHitObjectTime - beatmap.BeatmapInfo.AudioLeadIn); - // some beatmaps have no AudioLeadIn but the video starts before the first object - var videoLayer = beatmap.Storyboard.GetLayer(LegacyStoryLayer.Video); - - var videoOffset = videoLayer.Elements.SingleOrDefault()?.StartTime; - - if (videoOffset != null) - startTime = Math.Min(startTime, videoOffset.GetValueOrDefault()); - Seek(startTime); adjustableClock.ProcessFrame(); diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs index ef14ccd4d7..d3f77e03ae 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Video; using osu.Framework.Timing; using osu.Game.Beatmaps; -using osu.Game.Screens.Play; namespace osu.Game.Storyboards.Drawables { @@ -18,10 +17,6 @@ namespace osu.Game.Storyboards.Drawables { public readonly StoryboardVideo Video; private VideoSprite videoSprite; - private ManualClock videoClock; - private GameplayClock clock; - - private bool videoStarted; public override bool RemoveWhenNotAlive => false; @@ -33,13 +28,8 @@ namespace osu.Game.Storyboards.Drawables } [BackgroundDependencyLoader(true)] - private void load(GameplayClock clock, IBindable beatmap, TextureStore textureStore) + private void load(IBindable beatmap, TextureStore textureStore) { - if (clock == null) - return; - - this.clock = clock; - var path = beatmap.Value.BeatmapSetInfo?.Files?.Find(f => f.Filename.Equals(Video.Path, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; if (path == null) @@ -56,29 +46,23 @@ namespace osu.Game.Storyboards.Drawables FillMode = FillMode.Fill, Anchor = Anchor.Centre, Origin = Anchor.Centre, - AlwaysPresent = true, Alpha = 0 }); - - videoClock = new ManualClock(); - videoSprite.Clock = new FramedClock(videoClock); } - protected override void Update() + protected override void LoadComplete() { - if (clock != null && clock.CurrentTime > Video.StartTime) + using (videoSprite.BeginAbsoluteSequence(Video.StartTime)) { - if (!videoStarted) + videoSprite.Clock = new FramedOffsetClock(Clock) { - videoSprite.FadeIn(500); - videoStarted = true; - } + Offset = -Video.StartTime + }; - // handle seeking before the video starts (break skipping, replay seek) - videoClock.CurrentTime = clock.CurrentTime - Video.StartTime; + videoSprite.FadeIn(500); } - base.Update(); + base.LoadComplete(); } } } From ae7245a51bea2b812a8ce814db2afa325a27a163 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 11 Mar 2020 15:11:54 +0900 Subject: [PATCH 08/61] Refactor + fix fade in too late --- .../Drawables/DrawableStoryboardVideo.cs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs index d3f77e03ae..75135495cc 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs @@ -46,23 +46,17 @@ namespace osu.Game.Storyboards.Drawables FillMode = FillMode.Fill, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Alpha = 0 + Alpha = 0, + Clock = new FramedOffsetClock(Clock) { Offset = -Video.StartTime } }); } protected override void LoadComplete() { - using (videoSprite.BeginAbsoluteSequence(Video.StartTime)) - { - videoSprite.Clock = new FramedOffsetClock(Clock) - { - Offset = -Video.StartTime - }; - - videoSprite.FadeIn(500); - } - base.LoadComplete(); + + using (videoSprite.BeginAbsoluteSequence(0)) + videoSprite.FadeIn(500); } } } From 424f9afbf42203d0f32c97209c72531e4a8b16ec Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 11 Mar 2020 17:55:20 +0900 Subject: [PATCH 09/61] Fix incorrect offset with custom clock --- osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs index 75135495cc..00df388d09 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs @@ -13,7 +13,7 @@ using osu.Game.Beatmaps; namespace osu.Game.Storyboards.Drawables { - public class DrawableStoryboardVideo : Container + public class DrawableStoryboardVideo : CompositeDrawable { public readonly StoryboardVideo Video; private VideoSprite videoSprite; @@ -40,7 +40,7 @@ namespace osu.Game.Storyboards.Drawables if (stream == null) return; - AddInternal(videoSprite = new VideoSprite(stream) + InternalChild = videoSprite = new VideoSprite(stream, false) { RelativeSizeAxes = Axes.Both, FillMode = FillMode.Fill, @@ -48,7 +48,7 @@ namespace osu.Game.Storyboards.Drawables Origin = Anchor.Centre, Alpha = 0, Clock = new FramedOffsetClock(Clock) { Offset = -Video.StartTime } - }); + }; } protected override void LoadComplete() From c27751050be0ac704c76ceb11cfc2b25f0213c7e Mon Sep 17 00:00:00 2001 From: voidedWarranties Date: Thu, 12 Mar 2020 23:29:11 -0700 Subject: [PATCH 10/61] Switch back to strings and update setting labels --- .../Beatmaps/Formats/LegacyStoryboardDecoder.cs | 2 +- osu.Game/Beatmaps/Legacy/LegacyStoryLayer.cs | 2 +- .../Settings/Sections/Graphics/DetailSettings.cs | 2 +- .../Screens/Play/PlayerSettings/VisualSettings.cs | 2 +- osu.Game/Storyboards/Storyboard.cs | 13 +++++-------- 5 files changed, 9 insertions(+), 12 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index 8cb717076a..ba4eb7209b 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -92,7 +92,7 @@ namespace osu.Game.Beatmaps.Formats var offset = Parsing.ParseInt(split[1]); var filename = CleanFilename(split[2]); - storyboard.GetLayer(LegacyStoryLayer.Video).Add(new StoryboardVideo(filename, offset)); + storyboard.GetLayer("Video").Add(new StoryboardVideo(filename, offset)); break; } diff --git a/osu.Game/Beatmaps/Legacy/LegacyStoryLayer.cs b/osu.Game/Beatmaps/Legacy/LegacyStoryLayer.cs index c1329921ec..48e8bdbb76 100644 --- a/osu.Game/Beatmaps/Legacy/LegacyStoryLayer.cs +++ b/osu.Game/Beatmaps/Legacy/LegacyStoryLayer.cs @@ -3,7 +3,7 @@ namespace osu.Game.Beatmaps.Legacy { - public enum LegacyStoryLayer + internal enum LegacyStoryLayer { Background = 0, Fail = 1, diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs index acf33f00b3..3089040f96 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs @@ -18,7 +18,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics { new SettingsCheckbox { - LabelText = "Storyboards", + LabelText = "Storyboard / Video", Bindable = config.GetBindable(OsuSetting.ShowStoryboard) }, new SettingsCheckbox diff --git a/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs b/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs index bfb77e823f..d6c66d0751 100644 --- a/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs @@ -42,7 +42,7 @@ namespace osu.Game.Screens.Play.PlayerSettings { Text = "Toggles:" }, - showStoryboardToggle = new PlayerCheckbox { LabelText = "Storyboards" }, + showStoryboardToggle = new PlayerCheckbox { LabelText = "Storyboard / Video" }, beatmapSkinsToggle = new PlayerCheckbox { LabelText = "Beatmap skins" }, beatmapHitsoundsToggle = new PlayerCheckbox { LabelText = "Beatmap hitsounds" } }; diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index 64ad0208c9..7cfb104576 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -1,11 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.Legacy; -using osu.Game.Storyboards.Drawables; using System.Collections.Generic; using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Storyboards.Drawables; namespace osu.Game.Storyboards { @@ -29,8 +28,6 @@ namespace osu.Game.Storyboards layers.Add("Foreground", new StoryboardLayer("Foreground", 0)); } - public StoryboardLayer GetLayer(LegacyStoryLayer layer) => GetLayer(layer.ToString()); - public StoryboardLayer GetLayer(string name) { if (!layers.TryGetValue(name, out var layer)) @@ -50,17 +47,17 @@ namespace osu.Game.Storyboards if (backgroundPath == null) return false; - if (GetLayer(LegacyStoryLayer.Video).Elements.Any()) + if (GetLayer("Video").Elements.Any()) return true; - return GetLayer(LegacyStoryLayer.Background).Elements.Any(e => e.Path.ToLowerInvariant() == backgroundPath); + return GetLayer("Background").Elements.Any(e => e.Path.ToLowerInvariant() == backgroundPath); } } public DrawableStoryboard CreateDrawable(WorkingBeatmap working = null) { var drawable = new DrawableStoryboard(this); - drawable.Width = drawable.Height * (BeatmapInfo.WidescreenStoryboard || GetLayer(LegacyStoryLayer.Video).Elements.Any() ? 16 / 9f : 4 / 3f); + drawable.Width = drawable.Height * (BeatmapInfo.WidescreenStoryboard || GetLayer("Video").Elements.Any() ? 16 / 9f : 4 / 3f); return drawable; } } From e3a5be71cce01b6e42311a89772cc6fedbbb9c8f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 23 Mar 2020 12:09:30 +0900 Subject: [PATCH 11/61] Implement random mod for taiko --- .../Mods/ManiaModRandom.cs | 9 +------ .../Mods/TaikoModRandom.cs | 27 +++++++++++++++++++ osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 1 + osu.Game/Rulesets/Mods/ModRandom.cs | 17 ++++++++++++ 4 files changed, 46 insertions(+), 8 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs create mode 100644 osu.Game/Rulesets/Mods/ModRandom.cs diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs index 14b36fb765..699c58c373 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs @@ -3,24 +3,17 @@ using System.Linq; using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Graphics.Sprites; using osu.Framework.Utils; using osu.Game.Beatmaps; -using osu.Game.Graphics; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Mania.Mods { - public class ManiaModRandom : Mod, IApplicableToBeatmap + public class ManiaModRandom : ModRandom, IApplicableToBeatmap { - public override string Name => "Random"; - public override string Acronym => "RD"; - public override ModType Type => ModType.Conversion; - public override IconUsage? Icon => OsuIcon.Dice; public override string Description => @"Shuffle around the keys!"; - public override double ScoreMultiplier => 1; public void ApplyToBeatmap(IBeatmap beatmap) { diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs new file mode 100644 index 0000000000..1cf19ac18e --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs @@ -0,0 +1,27 @@ +// 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.Utils; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Taiko.Beatmaps; +using osu.Game.Rulesets.Taiko.Objects; + +namespace osu.Game.Rulesets.Taiko.Mods +{ + public class TaikoModRandom : ModRandom, IApplicableToBeatmap + { + public override string Description => @"Shuffle around the colours!"; + + public void ApplyToBeatmap(IBeatmap beatmap) + { + var taikoBeatmap = (TaikoBeatmap)beatmap; + + foreach (var obj in taikoBeatmap.HitObjects) + { + if (obj is Hit hit) + hit.Type = RNG.Next(2) == 0 ? HitType.Centre : HitType.Rim; + } + } + } +} diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index fc79e59864..4a841bf8c3 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -114,6 +114,7 @@ namespace osu.Game.Rulesets.Taiko case ModType.Conversion: return new Mod[] { + new TaikoModRandom(), new TaikoModDifficultyAdjust(), }; diff --git a/osu.Game/Rulesets/Mods/ModRandom.cs b/osu.Game/Rulesets/Mods/ModRandom.cs new file mode 100644 index 0000000000..da55ab3fbf --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModRandom.cs @@ -0,0 +1,17 @@ +// 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.Graphics.Sprites; +using osu.Game.Graphics; + +namespace osu.Game.Rulesets.Mods +{ + public abstract class ModRandom : Mod + { + public override string Name => "Random"; + public override string Acronym => "RD"; + public override ModType Type => ModType.Conversion; + public override IconUsage? Icon => OsuIcon.Dice; + public override double ScoreMultiplier => 1; + } +} From 997ce397efa6c64e6218fdd342cc0ea738a47b40 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Mar 2020 12:48:05 +0900 Subject: [PATCH 12/61] Disable raw input toggle on all but windows --- .../Settings/Sections/Input/MouseSettings.cs | 39 ++++++++++++------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs index 59d39a1c3c..e7f2f21465 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs @@ -1,6 +1,7 @@ // 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; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Configuration; @@ -56,24 +57,32 @@ namespace osu.Game.Overlays.Settings.Sections.Input }, }; - rawInputToggle.ValueChanged += enabled => + if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows) { - // this is temporary until we support per-handler settings. - const string raw_mouse_handler = @"OsuTKRawMouseHandler"; - const string standard_mouse_handler = @"OsuTKMouseHandler"; - - ignoredInputHandler.Value = enabled.NewValue ? standard_mouse_handler : raw_mouse_handler; - }; - - ignoredInputHandler = config.GetBindable(FrameworkSetting.IgnoredInputHandlers); - ignoredInputHandler.ValueChanged += handler => + rawInputToggle.Disabled = true; + sensitivity.Bindable.Disabled = true; + } + else { - bool raw = !handler.NewValue.Contains("Raw"); - rawInputToggle.Value = raw; - sensitivity.Bindable.Disabled = !raw; - }; + rawInputToggle.ValueChanged += enabled => + { + // this is temporary until we support per-handler settings. + const string raw_mouse_handler = @"OsuTKRawMouseHandler"; + const string standard_mouse_handler = @"OsuTKMouseHandler"; - ignoredInputHandler.TriggerChange(); + ignoredInputHandler.Value = enabled.NewValue ? standard_mouse_handler : raw_mouse_handler; + }; + + ignoredInputHandler = config.GetBindable(FrameworkSetting.IgnoredInputHandlers); + ignoredInputHandler.ValueChanged += handler => + { + bool raw = !handler.NewValue.Contains("Raw"); + rawInputToggle.Value = raw; + sensitivity.Bindable.Disabled = !raw; + }; + + ignoredInputHandler.TriggerChange(); + } } private class SensitivitySetting : SettingsSlider From 47c7673c9e7a855e02dd7d090830a7677657f64b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Mar 2020 17:04:54 +0900 Subject: [PATCH 13/61] Fix crash when holding a key down while entering player --- osu.Game/Screens/Select/FilterControl.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 6a03cfb68e..cad7672c71 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -136,6 +136,8 @@ namespace osu.Game.Screens.Select public void Deactivate() { + searchTextBox.ReadOnly = true; + searchTextBox.HoldFocus = false; if (searchTextBox.HasFocus) GetContainingInputManager().ChangeFocus(searchTextBox); @@ -143,6 +145,7 @@ namespace osu.Game.Screens.Select public void Activate() { + searchTextBox.ReadOnly = false; searchTextBox.HoldFocus = true; } From 232c2559867ceebfb8b61e0a979096c07c67c406 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Mar 2020 17:33:02 +0900 Subject: [PATCH 14/61] Basic test scene setup --- .../ManiaInputTestScene.cs | 2 +- osu.Game.Rulesets.Osu/OsuInputManager.cs | 2 +- .../Gameplay/TestSceneReplayRecording.cs | 105 ++++++++++++++++++ osu.Game/Rulesets/UI/RulesetInputManager.cs | 2 +- 4 files changed, 108 insertions(+), 3 deletions(-) create mode 100644 osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs b/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs index 909d0d45c6..9049bb3a82 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Mania.Tests { } - protected override RulesetKeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) + protected override KeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) => new LocalKeyBindingContainer(ruleset, variant, unique); private class LocalKeyBindingContainer : RulesetKeyBindingContainer diff --git a/osu.Game.Rulesets.Osu/OsuInputManager.cs b/osu.Game.Rulesets.Osu/OsuInputManager.cs index cdea7276f3..c8fe4f41ca 100644 --- a/osu.Game.Rulesets.Osu/OsuInputManager.cs +++ b/osu.Game.Rulesets.Osu/OsuInputManager.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu /// public bool AllowUserCursorMovement { get; set; } = true; - protected override RulesetKeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) + protected override KeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) => new OsuKeyBindingContainer(ruleset, variant, unique); public OsuInputManager(RulesetInfo ruleset) diff --git a/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs b/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs new file mode 100644 index 0000000000..0dc19cc3f2 --- /dev/null +++ b/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs @@ -0,0 +1,105 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; +using osu.Game.Rulesets; +using osu.Game.Rulesets.UI; +using osu.Game.Tests.Visual; +using osu.Game.Tests.Visual.UserInterface; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tests.Gameplay +{ + public class TestSceneReplayRecording : OsuTestScene + { + public TestSceneReplayRecording() + { + Add(new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique) + { + Child = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + Colour = Color4.Brown, + RelativeSizeAxes = Axes.Both, + }, + new TestConsumer() + } + }, + }); + } + + public class TestConsumer : CompositeDrawable, IKeyBindingHandler + { + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; + + private readonly Box box; + + public TestConsumer() + { + Size = new Vector2(30); + + Origin = Anchor.Centre; + + InternalChildren = new Drawable[] + { + box = new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + }, + }; + } + + protected override bool OnMouseMove(MouseMoveEvent e) + { + this.Position = e.MousePosition; + return base.OnMouseMove(e); + } + + public bool OnPressed(TestAction action) + { + box.Colour = Color4.White; + return true; + } + + public void OnReleased(TestAction action) + { + box.Colour = Color4.Black; + } + } + + private class TestRulesetInputManager : RulesetInputManager + { + public TestRulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) + : base(ruleset, variant, unique) + { + } + + protected override KeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) + => new TestKeyBindingContainer(); + + internal class TestKeyBindingContainer : KeyBindingContainer + { + public override IEnumerable DefaultKeyBindings => new[] + { + new KeyBinding(InputKey.MouseLeft, TestAction.Down), + }; + } + } + + public enum TestAction + { + Down, + } + } +} diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 41b2739fc5..7f85c10b56 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -148,7 +148,7 @@ namespace osu.Game.Rulesets.UI #endregion - protected virtual RulesetKeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) + protected virtual KeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) => new RulesetKeyBindingContainer(ruleset, variant, unique); public class RulesetKeyBindingContainer : DatabasedKeyBindingContainer From 467066112f3845393623fa9dc0b2d470a7260935 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Mar 2020 18:50:16 +0900 Subject: [PATCH 15/61] Initial record/playback implementation --- .../Gameplay/TestSceneReplayRecording.cs | 317 ++++++++++++++---- 1 file changed, 247 insertions(+), 70 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs b/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs index 0dc19cc3f2..3ff11bbd4e 100644 --- a/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs +++ b/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs @@ -1,13 +1,20 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Framework.Input.StateChanges; +using osu.Framework.Input.States; +using osu.Framework.Logging; +using osu.Game.Graphics.Sprites; +using osu.Game.Replays; using osu.Game.Rulesets; +using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.UI; using osu.Game.Tests.Visual; using osu.Game.Tests.Visual.UserInterface; @@ -18,88 +25,258 @@ namespace osu.Game.Tests.Gameplay { public class TestSceneReplayRecording : OsuTestScene { + private readonly TestRulesetInputManager playbackManager; + public TestSceneReplayRecording() { - Add(new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique) + Replay replay = new Replay(); + + Add(new GridContainer { - Child = new Container + RelativeSizeAxes = Axes.Both, + Content = new[] { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + new Drawable[] { - new Box + new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique) { - Colour = Color4.Brown, - RelativeSizeAxes = Axes.Both, - }, - new TestConsumer() + RecordTarget = replay, + Child = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + Colour = Color4.Brown, + RelativeSizeAxes = Axes.Both, + }, + new OsuSpriteText + { + Text = "Recording", + Scale = new Vector2(3), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new TestConsumer() + } + }, + } + }, + new Drawable[] + { + playbackManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique) + { + ReplayInputHandler = new TestFramedReplayInputHandler(replay) + { + GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos), + }, + Child = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + Colour = Color4.DarkBlue, + RelativeSizeAxes = Axes.Both, + }, + new OsuSpriteText + { + Text = "Playback", + Scale = new Vector2(3), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new TestConsumer() + } + }, + } } - }, + } }); } - public class TestConsumer : CompositeDrawable, IKeyBindingHandler + protected override void Update() { - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; + base.Update(); - private readonly Box box; - - public TestConsumer() - { - Size = new Vector2(30); - - Origin = Anchor.Centre; - - InternalChildren = new Drawable[] - { - box = new Box - { - Colour = Color4.Black, - RelativeSizeAxes = Axes.Both, - }, - }; - } - - protected override bool OnMouseMove(MouseMoveEvent e) - { - this.Position = e.MousePosition; - return base.OnMouseMove(e); - } - - public bool OnPressed(TestAction action) - { - box.Colour = Color4.White; - return true; - } - - public void OnReleased(TestAction action) - { - box.Colour = Color4.Black; - } - } - - private class TestRulesetInputManager : RulesetInputManager - { - public TestRulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) - : base(ruleset, variant, unique) - { - } - - protected override KeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) - => new TestKeyBindingContainer(); - - internal class TestKeyBindingContainer : KeyBindingContainer - { - public override IEnumerable DefaultKeyBindings => new[] - { - new KeyBinding(InputKey.MouseLeft, TestAction.Down), - }; - } - } - - public enum TestAction - { - Down, + playbackManager.ReplayInputHandler.SetFrameFromTime(Time.Current - 500); } } + + public class TestFramedReplayInputHandler : FramedReplayInputHandler + { + public TestFramedReplayInputHandler(Replay replay) + : base(replay) + { + } + + public override List GetPendingInputs() + { + return new List + { + new MousePositionAbsoluteInput + { + Position = GamefieldToScreenSpace(CurrentFrame?.Position ?? Vector2.Zero) + }, + new ReplayState + { + PressedActions = CurrentFrame?.Actions ?? new List() + } + }; + } + } + + public class TestConsumer : CompositeDrawable, IKeyBindingHandler + { + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parent.ReceivePositionalInputAt(screenSpacePos); + + private readonly Box box; + + public TestConsumer() + { + Size = new Vector2(30); + + Origin = Anchor.Centre; + + InternalChildren = new Drawable[] + { + box = new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + }, + }; + } + + protected override bool OnMouseMove(MouseMoveEvent e) + { + Position = e.MousePosition; + return base.OnMouseMove(e); + } + + public bool OnPressed(TestAction action) + { + box.Colour = Color4.White; + return true; + } + + public void OnReleased(TestAction action) + { + box.Colour = Color4.Black; + } + } + + public class TestRulesetInputManager : RulesetInputManager + { + private ReplayRecorder recorder; + + public Replay RecordTarget + { + set + { + if (recorder != null) + throw new InvalidOperationException("Cannot attach more than one recorder"); + + KeyBindingContainer.Add(recorder = new TestReplayRecorder(value)); + } + } + + public TestRulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) + : base(ruleset, variant, unique) + { + } + + protected override KeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) + => new TestKeyBindingContainer(); + + internal class TestKeyBindingContainer : KeyBindingContainer + { + public override IEnumerable DefaultKeyBindings => new[] + { + new KeyBinding(InputKey.MouseLeft, TestAction.Down), + }; + } + } + + public class TestReplayFrame : ReplayFrame + { + public Vector2 Position; + + public List Actions = new List(); + + public TestReplayFrame() + { + } + + public TestReplayFrame(double time, Vector2 position, params TestAction[] actions) + : base(time) + { + Position = position; + Actions.AddRange(actions); + } + } + + public enum TestAction + { + Down, + } + + internal class TestReplayRecorder : ReplayRecorder + { + public TestReplayRecorder(Replay target) + : base(target) + { + } + + protected override ReplayFrame HandleFrame(InputState state, List pressedActions) => + new TestReplayFrame(Time.Current, ToLocalSpace(state.Mouse.Position), pressedActions.ToArray()); + } + + internal abstract class ReplayRecorder : Component, IKeyBindingHandler + where T : struct + { + private readonly Replay target; + + private readonly List pressedActions = new List(); + + protected ReplayRecorder(Replay target) + { + this.target = target; + + RelativeSizeAxes = Axes.Both; + + Depth = float.MinValue; + } + + protected override bool OnMouseMove(MouseMoveEvent e) + { + recordFrame(); + return base.OnMouseMove(e); + } + + public bool OnPressed(T action) + { + pressedActions.Add(action); + recordFrame(); + return false; + } + + public void OnReleased(T action) + { + pressedActions.Remove(action); + recordFrame(); + } + + private void recordFrame() + { + var frame = HandleFrame(GetContainingInputManager().CurrentState, pressedActions); + + if (frame != null) + target.Frames.Add(frame); + } + + protected abstract ReplayFrame HandleFrame(InputState state, List pressedActions); + } } From d5bc4915e6fd3961ae4a4c6b62059cddc5740817 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Mar 2020 19:02:45 +0900 Subject: [PATCH 16/61] Add "important" frames and record rate options --- .../Gameplay/TestSceneReplayRecording.cs | 37 +++++++++++++------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs b/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs index 3ff11bbd4e..c2b2ebdb98 100644 --- a/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs +++ b/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs @@ -3,14 +3,15 @@ using System; using System.Collections.Generic; +using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Input.StateChanges; using osu.Framework.Input.States; -using osu.Framework.Logging; using osu.Game.Graphics.Sprites; using osu.Game.Replays; using osu.Game.Rulesets; @@ -206,10 +207,6 @@ namespace osu.Game.Tests.Gameplay public List Actions = new List(); - public TestReplayFrame() - { - } - public TestReplayFrame(double time, Vector2 position, params TestAction[] actions) : base(time) { @@ -230,7 +227,7 @@ namespace osu.Game.Tests.Gameplay { } - protected override ReplayFrame HandleFrame(InputState state, List pressedActions) => + protected override ReplayFrame HandleFrame(InputState state, List pressedActions, ReplayFrame previousFrame) => new TestReplayFrame(Time.Current, ToLocalSpace(state.Mouse.Position), pressedActions.ToArray()); } @@ -241,6 +238,10 @@ namespace osu.Game.Tests.Gameplay private readonly List pressedActions = new List(); + private InputManager inputManager; + + public int RecordFrameRate = 60; + protected ReplayRecorder(Replay target) { this.target = target; @@ -250,33 +251,45 @@ namespace osu.Game.Tests.Gameplay Depth = float.MinValue; } + protected override void LoadComplete() + { + base.LoadComplete(); + + inputManager = GetContainingInputManager(); + } + protected override bool OnMouseMove(MouseMoveEvent e) { - recordFrame(); + recordFrame(false); return base.OnMouseMove(e); } public bool OnPressed(T action) { pressedActions.Add(action); - recordFrame(); + recordFrame(true); return false; } public void OnReleased(T action) { pressedActions.Remove(action); - recordFrame(); + recordFrame(true); } - private void recordFrame() + private void recordFrame(bool important) { - var frame = HandleFrame(GetContainingInputManager().CurrentState, pressedActions); + var last = target.Frames.LastOrDefault(); + + if (!important && last != null && Time.Current - last.Time < (1000d / RecordFrameRate)) + return; + + var frame = HandleFrame(inputManager.CurrentState, pressedActions, last); if (frame != null) target.Frames.Add(frame); } - protected abstract ReplayFrame HandleFrame(InputState state, List pressedActions); + protected abstract ReplayFrame HandleFrame(InputState state, List testActions, ReplayFrame previousFrame); } } From 6d480680612b2bf0f875e36fa7d86ddc16464013 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Mar 2020 19:03:42 +0900 Subject: [PATCH 17/61] Move replay recorder to final location --- osu.Game.Rulesets.Osu/UI/OsuReplayRecorder.cs | 23 ++++++ .../Gameplay/TestSceneReplayRecording.cs | 80 +----------------- osu.Game/Rulesets/UI/DrawableRuleset.cs | 2 + osu.Game/Rulesets/UI/ReplayRecorder.cs | 81 +++++++++++++++++++ osu.Game/Rulesets/UI/RulesetInputManager.cs | 16 ++++ 5 files changed, 123 insertions(+), 79 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/UI/OsuReplayRecorder.cs create mode 100644 osu.Game/Rulesets/UI/ReplayRecorder.cs diff --git a/osu.Game.Rulesets.Osu/UI/OsuReplayRecorder.cs b/osu.Game.Rulesets.Osu/UI/OsuReplayRecorder.cs new file mode 100644 index 0000000000..898212ee6b --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/OsuReplayRecorder.cs @@ -0,0 +1,23 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Game.Replays; +using osu.Game.Rulesets.Osu.Replays; +using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.UI; +using osuTK; + +namespace osu.Game.Rulesets.Osu.UI +{ + public class OsuReplayRecorder : ReplayRecorder + { + public OsuReplayRecorder(Replay replay) + : base(replay) + { + } + + protected override ReplayFrame HandleFrame(Vector2 position, List actions, ReplayFrame previousFrame) + => new OsuReplayFrame(Time.Current, position, actions.ToArray()); + } +} diff --git a/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs b/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs index c2b2ebdb98..ab1998a650 100644 --- a/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs +++ b/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs @@ -1,13 +1,10 @@ // Copyright (c) ppy Pty Ltd . 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 osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Input.StateChanges; @@ -41,7 +38,7 @@ namespace osu.Game.Tests.Gameplay { new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique) { - RecordTarget = replay, + Recorder = new TestReplayRecorder(replay), Child = new Container { RelativeSizeAxes = Axes.Both, @@ -171,19 +168,6 @@ namespace osu.Game.Tests.Gameplay public class TestRulesetInputManager : RulesetInputManager { - private ReplayRecorder recorder; - - public Replay RecordTarget - { - set - { - if (recorder != null) - throw new InvalidOperationException("Cannot attach more than one recorder"); - - KeyBindingContainer.Add(recorder = new TestReplayRecorder(value)); - } - } - public TestRulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) : base(ruleset, variant, unique) { @@ -230,66 +214,4 @@ namespace osu.Game.Tests.Gameplay protected override ReplayFrame HandleFrame(InputState state, List pressedActions, ReplayFrame previousFrame) => new TestReplayFrame(Time.Current, ToLocalSpace(state.Mouse.Position), pressedActions.ToArray()); } - - internal abstract class ReplayRecorder : Component, IKeyBindingHandler - where T : struct - { - private readonly Replay target; - - private readonly List pressedActions = new List(); - - private InputManager inputManager; - - public int RecordFrameRate = 60; - - protected ReplayRecorder(Replay target) - { - this.target = target; - - RelativeSizeAxes = Axes.Both; - - Depth = float.MinValue; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - inputManager = GetContainingInputManager(); - } - - protected override bool OnMouseMove(MouseMoveEvent e) - { - recordFrame(false); - return base.OnMouseMove(e); - } - - public bool OnPressed(T action) - { - pressedActions.Add(action); - recordFrame(true); - return false; - } - - public void OnReleased(T action) - { - pressedActions.Remove(action); - recordFrame(true); - } - - private void recordFrame(bool important) - { - var last = target.Frames.LastOrDefault(); - - if (!important && last != null && Time.Current - last.Time < (1000d / RecordFrameRate)) - return; - - var frame = HandleFrame(inputManager.CurrentState, pressedActions, last); - - if (frame != null) - target.Frames.Add(frame); - } - - protected abstract ReplayFrame HandleFrame(InputState state, List testActions, ReplayFrame previousFrame); - } } diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index d0a2722f58..c8af3be980 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -302,6 +302,8 @@ namespace osu.Game.Rulesets.UI protected virtual ReplayInputHandler CreateReplayInputHandler(Replay replay) => null; + protected virtual ReplayRecorder CreateReplayRecorder(Replay replay) => null; + /// /// Creates a Playfield. /// diff --git a/osu.Game/Rulesets/UI/ReplayRecorder.cs b/osu.Game/Rulesets/UI/ReplayRecorder.cs new file mode 100644 index 0000000000..9e2f898206 --- /dev/null +++ b/osu.Game/Rulesets/UI/ReplayRecorder.cs @@ -0,0 +1,81 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Input; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; +using osu.Framework.Input.States; +using osu.Game.Replays; +using osu.Game.Rulesets.Replays; + +namespace osu.Game.Rulesets.UI +{ + public abstract class ReplayRecorder : ReplayRecorder, IKeyBindingHandler + where T : struct + { + private readonly Replay target; + + private readonly List pressedActions = new List(); + + private InputManager inputManager; + + public int RecordFrameRate = 60; + + protected ReplayRecorder(Replay target) + { + this.target = target; + + RelativeSizeAxes = Axes.Both; + + Depth = float.MinValue; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + inputManager = GetContainingInputManager(); + } + + protected override bool OnMouseMove(MouseMoveEvent e) + { + recordFrame(false); + return base.OnMouseMove(e); + } + + public bool OnPressed(T action) + { + pressedActions.Add(action); + recordFrame(true); + return false; + } + + public void OnReleased(T action) + { + pressedActions.Remove(action); + recordFrame(true); + } + + private void recordFrame(bool important) + { + var last = target.Frames.LastOrDefault(); + + if (!important && last != null && Time.Current - last.Time < (1000d / RecordFrameRate)) + return; + + var frame = HandleFrame(inputManager.CurrentState, pressedActions, last); + + if (frame != null) + target.Frames.Add(frame); + } + + protected abstract ReplayFrame HandleFrame(InputState state, List testActions, ReplayFrame previousFrame); + } + + public abstract class ReplayRecorder : Component + { + } +} diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 7f85c10b56..043e0f56cc 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -26,6 +27,21 @@ namespace osu.Game.Rulesets.UI public abstract class RulesetInputManager : PassThroughInputManager, ICanAttachKeyCounter, IHasReplayHandler where T : struct { + private ReplayRecorder recorder; + + public ReplayRecorder Recorder + { + set + { + if (recorder != null) + throw new InvalidOperationException("Cannot attach more than one recorder"); + + recorder = value; + + KeyBindingContainer.Add(recorder); + } + } + protected override InputState CreateInitialState() { var state = base.CreateInitialState(); From 14a85a84bf6001336b82702b7a9e5ba815bab0a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Mar 2020 19:18:56 +0900 Subject: [PATCH 18/61] Add proper screen space - gamefield mapping --- osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs | 2 ++ .../Gameplay/TestSceneReplayRecording.cs | 14 +++++++++----- osu.Game/Rulesets/UI/Playfield.cs | 5 +++++ osu.Game/Rulesets/UI/ReplayRecorder.cs | 10 +++++++--- 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index a37ef8d9a0..b4d51d11c9 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -58,6 +58,8 @@ namespace osu.Game.Rulesets.Osu.UI protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new OsuFramedReplayInputHandler(replay); + protected override ReplayRecorder CreateReplayRecorder(Replay replay) => new OsuReplayRecorder(replay); + public override double GameplayStartTime { get diff --git a/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs b/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs index ab1998a650..fe87ca675b 100644 --- a/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs +++ b/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Input.StateChanges; -using osu.Framework.Input.States; using osu.Game.Graphics.Sprites; using osu.Game.Replays; using osu.Game.Rulesets; @@ -25,6 +24,8 @@ namespace osu.Game.Tests.Gameplay { private readonly TestRulesetInputManager playbackManager; + private readonly TestRulesetInputManager recordingManager; + public TestSceneReplayRecording() { Replay replay = new Replay(); @@ -36,9 +37,12 @@ namespace osu.Game.Tests.Gameplay { new Drawable[] { - new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique) + recordingManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique) { - Recorder = new TestReplayRecorder(replay), + Recorder = new TestReplayRecorder(replay) + { + ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos) + }, Child = new Container { RelativeSizeAxes = Axes.Both, @@ -211,7 +215,7 @@ namespace osu.Game.Tests.Gameplay { } - protected override ReplayFrame HandleFrame(InputState state, List pressedActions, ReplayFrame previousFrame) => - new TestReplayFrame(Time.Current, ToLocalSpace(state.Mouse.Position), pressedActions.ToArray()); + protected override ReplayFrame HandleFrame(Vector2 position, List actions, ReplayFrame previousFrame) => + new TestReplayFrame(Time.Current, position, actions.ToArray()); } } diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 047047ccfd..8141108aef 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -30,6 +30,11 @@ namespace osu.Game.Rulesets.UI /// public Func GamefieldToScreenSpace => HitObjectContainer.ToScreenSpace; + /// + /// A function that converts screen space coordinates to gamefield. + /// + public Func ScreenSpaceToGamefield => HitObjectContainer.ToLocalSpace; + /// /// All the s contained in this and all . /// diff --git a/osu.Game/Rulesets/UI/ReplayRecorder.cs b/osu.Game/Rulesets/UI/ReplayRecorder.cs index 9e2f898206..74e8109d52 100644 --- a/osu.Game/Rulesets/UI/ReplayRecorder.cs +++ b/osu.Game/Rulesets/UI/ReplayRecorder.cs @@ -1,15 +1,16 @@ // Copyright (c) ppy Pty Ltd . 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 osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; -using osu.Framework.Input.States; using osu.Game.Replays; using osu.Game.Rulesets.Replays; +using osuTK; namespace osu.Game.Rulesets.UI { @@ -66,16 +67,19 @@ namespace osu.Game.Rulesets.UI if (!important && last != null && Time.Current - last.Time < (1000d / RecordFrameRate)) return; - var frame = HandleFrame(inputManager.CurrentState, pressedActions, last); + var position = ScreenSpaceToGamefield?.Invoke(inputManager.CurrentState.Mouse.Position) ?? inputManager.CurrentState.Mouse.Position; + + var frame = HandleFrame(position, pressedActions, last); if (frame != null) target.Frames.Add(frame); } - protected abstract ReplayFrame HandleFrame(InputState state, List testActions, ReplayFrame previousFrame); + protected abstract ReplayFrame HandleFrame(Vector2 position, List actions, ReplayFrame previousFrame); } public abstract class ReplayRecorder : Component { + public Func ScreenSpaceToGamefield; } } From 617149fb2702a4686ee9ce69200ea7c84f250d35 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Mar 2020 19:31:43 +0900 Subject: [PATCH 19/61] Implement in player --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 17 +++++++++++++++++ osu.Game/Rulesets/UI/RulesetInputManager.cs | 7 ++++++- osu.Game/Screens/Play/Player.cs | 18 ++++++++++++++++++ osu.Game/Screens/Play/ReplayPlayer.cs | 3 +-- 4 files changed, 42 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index c8af3be980..5c57a92cd1 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -262,6 +262,17 @@ namespace osu.Game.Rulesets.UI Playfield.Add(drawableObject); } + public override void SetRecordTarget(Replay recordingReplay) + { + if (!(KeyBindingInputManager is IHasRecordingHandler recordingInputHandler)) + throw new InvalidOperationException($"A {nameof(KeyBindingInputManager)} which supports recording is not available"); + + var recorder = CreateReplayRecorder(recordingReplay); + recorder.ScreenSpaceToGamefield = Playfield.ScreenSpaceToGamefield; + + recordingInputHandler.Recorder = recorder; + } + public override void SetReplayScore(Score replayScore) { if (!(KeyBindingInputManager is IHasReplayHandler replayInputManager)) @@ -472,6 +483,12 @@ namespace osu.Game.Rulesets.UI /// The replay, null for local input. public abstract void SetReplayScore(Score replayScore); + /// + /// Sets a replay to be used to record gameplay. + /// + /// The target to be recorded to. + public abstract void SetRecordTarget(Replay recordingReplay); + /// /// Invoked when the interactive user requests resuming from a paused state. /// Allows potentially delaying the resume process until an interaction is performed. diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 043e0f56cc..ba30fe28d5 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -24,7 +24,7 @@ using MouseState = osu.Framework.Input.States.MouseState; namespace osu.Game.Rulesets.UI { - public abstract class RulesetInputManager : PassThroughInputManager, ICanAttachKeyCounter, IHasReplayHandler + public abstract class RulesetInputManager : PassThroughInputManager, ICanAttachKeyCounter, IHasReplayHandler, IHasRecordingHandler where T : struct { private ReplayRecorder recorder; @@ -184,6 +184,11 @@ namespace osu.Game.Rulesets.UI ReplayInputHandler ReplayInputHandler { get; set; } } + public interface IHasRecordingHandler + { + public ReplayRecorder Recorder { set; } + } + /// /// Supports attaching a . /// Keys will be populated automatically and a receptor will be injected inside. diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index a120963abd..8fee516f2b 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -19,6 +19,7 @@ using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osu.Game.Online.API; using osu.Game.Overlays; +using osu.Game.Replays; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; @@ -118,6 +119,23 @@ namespace osu.Game.Screens.Play protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + protected override void LoadComplete() + { + base.LoadComplete(); + + PrepareReplay(); + } + + private Replay recordingReplay; + + /// + /// Run any recording / playback setup for replays. + /// + protected virtual void PrepareReplay() + { + DrawableRuleset.SetRecordTarget(recordingReplay = new Replay()); + } + [BackgroundDependencyLoader] private void load(AudioManager audio, OsuConfigManager config) { diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index b040549efc..8708b5f634 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -18,9 +18,8 @@ namespace osu.Game.Screens.Play this.score = score; } - protected override void LoadComplete() + protected override void PrepareReplay() { - base.LoadComplete(); DrawableRuleset?.SetReplayScore(score); } From 96848405fd8df958850d4421c5eb31625416586f Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 23 Mar 2020 10:54:45 -0700 Subject: [PATCH 20/61] Fix song select filter not absorbing input from carousel --- osu.Game/Screens/Select/FilterControl.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 6a03cfb68e..c831e1dcc4 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -16,6 +16,7 @@ using Container = osu.Framework.Graphics.Containers.Container; using osu.Framework.Graphics.Shapes; using osu.Game.Configuration; using osu.Game.Rulesets; +using osu.Framework.Input.Events; namespace osu.Game.Screens.Select { @@ -184,5 +185,7 @@ namespace osu.Game.Screens.Select } private void updateCriteria() => FilterChanged?.Invoke(CreateCriteria()); + + protected override bool OnClick(ClickEvent e) => true; } } From 96d962ab307dfd6c6f7e5213ae5bead1ef241403 Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 23 Mar 2020 11:25:40 -0700 Subject: [PATCH 21/61] Fix autoplay keyboard shortcut not working with keypad enter key --- osu.Game/Screens/Select/PlaySongSelect.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 9e2f5761dd..8b0547376d 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -56,6 +56,7 @@ namespace osu.Game.Screens.Select switch (e.Key) { case Key.Enter: + case Key.KeypadEnter: // this is a special hard-coded case; we can't rely on OnPressed (of SongSelect) as GlobalActionContainer is // matching with exact modifier consideration (so Ctrl+Enter would be ignored). FinaliseSelection(); From 5bc51193891766d39cd564a06d52383bacad3f7a Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 23 Mar 2020 16:03:33 -0700 Subject: [PATCH 22/61] Handle OnHover on song select filter and footer --- osu.Game/Screens/Select/FilterControl.cs | 2 ++ osu.Game/Screens/Select/Footer.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index c831e1dcc4..a4a7ac5c9d 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -187,5 +187,7 @@ namespace osu.Game.Screens.Select private void updateCriteria() => FilterChanged?.Invoke(CreateCriteria()); protected override bool OnClick(ClickEvent e) => true; + + protected override bool OnHover(HoverEvent e) => true; } } diff --git a/osu.Game/Screens/Select/Footer.cs b/osu.Game/Screens/Select/Footer.cs index 1dc7081c1c..689a11166a 100644 --- a/osu.Game/Screens/Select/Footer.cs +++ b/osu.Game/Screens/Select/Footer.cs @@ -107,5 +107,7 @@ namespace osu.Game.Screens.Select protected override bool OnMouseDown(MouseDownEvent e) => true; protected override bool OnClick(ClickEvent e) => true; + + protected override bool OnHover(HoverEvent e) => true; } } From e5f4d8686e04b610ac560b3e62cd60c3f7425445 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Mar 2020 10:38:24 +0900 Subject: [PATCH 23/61] Rename decoder --- ...dLegacyScoreParser.cs => DatabasedLegacyScoreDecoder.cs} | 6 +++--- .../Legacy/{LegacyScoreParser.cs => LegacyScoreDecoder.cs} | 2 +- osu.Game/Scoring/LegacyDatabasedScore.cs | 2 +- osu.Game/Scoring/ScoreManager.cs | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) rename osu.Game/Scoring/Legacy/{DatabasedLegacyScoreParser.cs => DatabasedLegacyScoreDecoder.cs} (74%) rename osu.Game/Scoring/Legacy/{LegacyScoreParser.cs => LegacyScoreDecoder.cs} (99%) diff --git a/osu.Game/Scoring/Legacy/DatabasedLegacyScoreParser.cs b/osu.Game/Scoring/Legacy/DatabasedLegacyScoreDecoder.cs similarity index 74% rename from osu.Game/Scoring/Legacy/DatabasedLegacyScoreParser.cs rename to osu.Game/Scoring/Legacy/DatabasedLegacyScoreDecoder.cs index 2115d784a0..9b590f56dd 100644 --- a/osu.Game/Scoring/Legacy/DatabasedLegacyScoreParser.cs +++ b/osu.Game/Scoring/Legacy/DatabasedLegacyScoreDecoder.cs @@ -7,15 +7,15 @@ using osu.Game.Rulesets; namespace osu.Game.Scoring.Legacy { /// - /// A which retrieves the applicable and + /// A which retrieves the applicable and /// for the score from the database. /// - public class DatabasedLegacyScoreParser : LegacyScoreParser + public class DatabasedLegacyScoreDecoder : LegacyScoreDecoder { private readonly RulesetStore rulesets; private readonly BeatmapManager beatmaps; - public DatabasedLegacyScoreParser(RulesetStore rulesets, BeatmapManager beatmaps) + public DatabasedLegacyScoreDecoder(RulesetStore rulesets, BeatmapManager beatmaps) { this.rulesets = rulesets; this.beatmaps = beatmaps; diff --git a/osu.Game/Scoring/Legacy/LegacyScoreParser.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs similarity index 99% rename from osu.Game/Scoring/Legacy/LegacyScoreParser.cs rename to osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index 19d8410cc2..f29e98b0b4 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreParser.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -19,7 +19,7 @@ using SharpCompress.Compressors.LZMA; namespace osu.Game.Scoring.Legacy { - public abstract class LegacyScoreParser + public abstract class LegacyScoreDecoder { private IBeatmap currentBeatmap; private Ruleset currentRuleset; diff --git a/osu.Game/Scoring/LegacyDatabasedScore.cs b/osu.Game/Scoring/LegacyDatabasedScore.cs index 172e08e2d0..bd673eaa29 100644 --- a/osu.Game/Scoring/LegacyDatabasedScore.cs +++ b/osu.Game/Scoring/LegacyDatabasedScore.cs @@ -19,7 +19,7 @@ namespace osu.Game.Scoring var replayFilename = score.Files.First(f => f.Filename.EndsWith(".osr", StringComparison.InvariantCultureIgnoreCase)).FileInfo.StoragePath; using (var stream = store.GetStream(replayFilename)) - Replay = new DatabasedLegacyScoreParser(rulesets, beatmaps).Parse(stream).Replay; + Replay = new DatabasedLegacyScoreDecoder(rulesets, beatmaps).Parse(stream).Replay; } } } diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 249f0a932b..d5bd486e43 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -46,9 +46,9 @@ namespace osu.Game.Scoring { try { - return new DatabasedLegacyScoreParser(rulesets, beatmaps()).Parse(stream).ScoreInfo; + return new DatabasedLegacyScoreDecoder(rulesets, beatmaps()).Parse(stream).ScoreInfo; } - catch (LegacyScoreParser.BeatmapNotFoundException e) + catch (LegacyScoreDecoder.BeatmapNotFoundException e) { Logger.Log(e.Message, LoggingTarget.Information, LogLevel.Error); return null; From 546772192ce30e0afd20e9830864de65ac11680d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Mar 2020 12:06:24 +0900 Subject: [PATCH 24/61] Add helper method to convert to legacy mods enums --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 2 +- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 55 ++++++++++++++++- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 +- osu.Game/Rulesets/Ruleset.cs | 60 ++++++++++++++++++- osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 2 +- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 35 +++++++++++ .../Tests/Beatmaps/LegacyModConversionTest.cs | 2 +- 8 files changed, 151 insertions(+), 9 deletions(-) create mode 100644 osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index b9d791fdb1..212365caad 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Catch new KeyBinding(InputKey.Shift, CatchAction.Dash), }; - public override IEnumerable ConvertLegacyMods(LegacyMods mods) + public override IEnumerable ConvertFromLegacyMods(LegacyMods mods) { if (mods.HasFlag(LegacyMods.Nightcore)) yield return new CatchModNightcore(); diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index b7b523a94d..9d06bd7c25 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Mania public override ISkin CreateLegacySkinProvider(ISkinSource source) => new ManiaLegacySkinTransformer(source); - public override IEnumerable ConvertLegacyMods(LegacyMods mods) + public override IEnumerable ConvertFromLegacyMods(LegacyMods mods) { if (mods.HasFlag(LegacyMods.Nightcore)) yield return new ManiaModNightcore(); @@ -118,6 +118,59 @@ namespace osu.Game.Rulesets.Mania yield return new ManiaModRandom(); } + public override LegacyMods ConvertToLegacyMods(Mod[] mods) + { + var value = base.ConvertToLegacyMods(mods); + + foreach (var mod in mods) + { + switch (mod) + { + case ManiaModKey1 _: + value |= LegacyMods.Key1; + break; + + case ManiaModKey2 _: + value |= LegacyMods.Key2; + break; + + case ManiaModKey3 _: + value |= LegacyMods.Key3; + break; + + case ManiaModKey4 _: + value |= LegacyMods.Key4; + break; + + case ManiaModKey5 _: + value |= LegacyMods.Key5; + break; + + case ManiaModKey6 _: + value |= LegacyMods.Key6; + break; + + case ManiaModKey7 _: + value |= LegacyMods.Key7; + break; + + case ManiaModKey8 _: + value |= LegacyMods.Key8; + break; + + case ManiaModKey9 _: + value |= LegacyMods.Key9; + break; + + case ManiaModFadeIn _: + value |= LegacyMods.FadeIn; + break; + } + } + + return value; + } + public override IEnumerable GetModsFor(ModType type) { switch (type) diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 148869f5e8..a2c0e051d0 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Osu new KeyBinding(InputKey.MouseRight, OsuAction.RightButton), }; - public override IEnumerable ConvertLegacyMods(LegacyMods mods) + public override IEnumerable ConvertFromLegacyMods(LegacyMods mods) { if (mods.HasFlag(LegacyMods.Nightcore)) yield return new OsuModNightcore(); diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index fc79e59864..dfcc886940 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Taiko new KeyBinding(InputKey.K, TaikoAction.RightRim), }; - public override IEnumerable ConvertLegacyMods(LegacyMods mods) + public override IEnumerable ConvertFromLegacyMods(LegacyMods mods) { if (mods.HasFlag(LegacyMods.Nightcore)) yield return new TaikoModNightcore(); diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index c38a5c6af7..58f598a203 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -42,9 +42,63 @@ namespace osu.Game.Rulesets /// /// Converts mods from legacy enum values. Do not override if you're not a legacy ruleset. /// - /// The legacy enum which will be converted - /// An enumerable of constructed s - public virtual IEnumerable ConvertLegacyMods(LegacyMods mods) => Array.Empty(); + /// The legacy enum which will be converted. + /// An enumerable of constructed s. + public virtual IEnumerable ConvertFromLegacyMods(LegacyMods mods) => Array.Empty(); + + /// + /// Converts mods to legacy enum values. Do not override if you're not a legacy ruleset. + /// + /// The mods which will be converted. + /// A single bitwise enumerable value representing (to the best of our ability) the mods. + public virtual LegacyMods ConvertToLegacyMods(Mod[] mods) + { + var value = LegacyMods.None; + + foreach (var mod in mods) + { + switch (mod) + { + case ModNoFail _: + value |= LegacyMods.NoFail; + break; + + case ModEasy _: + value |= LegacyMods.Easy; + break; + + case ModHidden _: + value |= LegacyMods.Hidden; + break; + + case ModHardRock _: + value |= LegacyMods.HardRock; + break; + + case ModSuddenDeath _: + value |= LegacyMods.SuddenDeath; + break; + + case ModDoubleTime _: + value |= LegacyMods.DoubleTime; + break; + + case ModRelax _: + value |= LegacyMods.Relax; + break; + + case ModHalfTime _: + value |= LegacyMods.HalfTime; + break; + + case ModFlashlight _: + value |= LegacyMods.Flashlight; + break; + } + } + + return value; + } public ModAutoplay GetAutoplayMod() => GetAllMods().OfType().First(); diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index f29e98b0b4..495d8c8cc0 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -66,7 +66,7 @@ namespace osu.Game.Scoring.Legacy /* score.Perfect = */ sr.ReadBoolean(); - scoreInfo.Mods = currentRuleset.ConvertLegacyMods((LegacyMods)sr.ReadInt32()).ToArray(); + scoreInfo.Mods = currentRuleset.ConvertFromLegacyMods((LegacyMods)sr.ReadInt32()).ToArray(); /* score.HpGraphString = */ sr.ReadString(); diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs new file mode 100644 index 0000000000..927ab3fe07 --- /dev/null +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -0,0 +1,35 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.IO; + +namespace osu.Game.Scoring.Legacy +{ + public class LegacyScoreEncoder + { + public const int LATEST_VERSION = 128; + + private readonly Score score; + + public LegacyScoreEncoder(Score score) + { + this.score = score; + + if (score.ScoreInfo.Beatmap.RulesetID < 0 || score.ScoreInfo.Beatmap.RulesetID > 3) + throw new ArgumentException("Only scores in the osu, taiko, catch, or mania rulesets can be encoded to the legacy score format.", nameof(score)); + } + + public void Encode(TextWriter writer) + { + writer.WriteLine($"osu file format v{LATEST_VERSION}"); + + writer.WriteLine(); + handleGeneral(writer); + } + + private void handleGeneral(TextWriter writer) + { + } + } +} diff --git a/osu.Game/Tests/Beatmaps/LegacyModConversionTest.cs b/osu.Game/Tests/Beatmaps/LegacyModConversionTest.cs index e9251f8011..e93bf916c7 100644 --- a/osu.Game/Tests/Beatmaps/LegacyModConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/LegacyModConversionTest.cs @@ -23,7 +23,7 @@ namespace osu.Game.Tests.Beatmaps protected void Test(LegacyMods legacyMods, Type[] expectedMods) { var ruleset = CreateRuleset(); - var mods = ruleset.ConvertLegacyMods(legacyMods).ToList(); + var mods = ruleset.ConvertFromLegacyMods(legacyMods).ToList(); Assert.AreEqual(expectedMods.Length, mods.Count); foreach (var modType in expectedMods) From 68ebe98fdee98368a276e07275c0d27fd10bc51c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Mar 2020 14:08:25 +0900 Subject: [PATCH 25/61] Remove unused GetUnderlyingStream method --- osu.Game.Tests/Scores/IO/ImportScoreTest.cs | 2 -- osu.Game/IO/Archives/ArchiveReader.cs | 2 -- osu.Game/IO/Archives/LegacyDirectoryArchiveReader.cs | 2 -- osu.Game/IO/Archives/LegacyFileArchiveReader.cs | 2 -- osu.Game/IO/Archives/ZipArchiveReader.cs | 2 -- 5 files changed, 10 deletions(-) diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs index a139c3a8c2..90bf419644 100644 --- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -236,8 +236,6 @@ namespace osu.Game.Tests.Scores.IO } public override IEnumerable Filenames => new[] { "test_file.osr" }; - - public override Stream GetUnderlyingStream() => new MemoryStream(); } } } diff --git a/osu.Game/IO/Archives/ArchiveReader.cs b/osu.Game/IO/Archives/ArchiveReader.cs index 4ee7a19ebc..a30f961daf 100644 --- a/osu.Game/IO/Archives/ArchiveReader.cs +++ b/osu.Game/IO/Archives/ArchiveReader.cs @@ -45,7 +45,5 @@ namespace osu.Game.IO.Archives return buffer; } } - - public abstract Stream GetUnderlyingStream(); } } diff --git a/osu.Game/IO/Archives/LegacyDirectoryArchiveReader.cs b/osu.Game/IO/Archives/LegacyDirectoryArchiveReader.cs index eff02ae7a5..dfae58aed7 100644 --- a/osu.Game/IO/Archives/LegacyDirectoryArchiveReader.cs +++ b/osu.Game/IO/Archives/LegacyDirectoryArchiveReader.cs @@ -28,7 +28,5 @@ namespace osu.Game.IO.Archives } public override IEnumerable Filenames => Directory.GetFiles(path, "*", SearchOption.AllDirectories).Select(f => f.Replace(path, string.Empty).Trim(Path.DirectorySeparatorChar)).ToArray(); - - public override Stream GetUnderlyingStream() => null; } } diff --git a/osu.Game/IO/Archives/LegacyFileArchiveReader.cs b/osu.Game/IO/Archives/LegacyFileArchiveReader.cs index bd5f9cbd07..72e5a21079 100644 --- a/osu.Game/IO/Archives/LegacyFileArchiveReader.cs +++ b/osu.Game/IO/Archives/LegacyFileArchiveReader.cs @@ -28,7 +28,5 @@ namespace osu.Game.IO.Archives } public override IEnumerable Filenames => new[] { Name }; - - public override Stream GetUnderlyingStream() => null; } } diff --git a/osu.Game/IO/Archives/ZipArchiveReader.cs b/osu.Game/IO/Archives/ZipArchiveReader.cs index 35f38ea7e8..80dfa104f3 100644 --- a/osu.Game/IO/Archives/ZipArchiveReader.cs +++ b/osu.Game/IO/Archives/ZipArchiveReader.cs @@ -45,7 +45,5 @@ namespace osu.Game.IO.Archives } public override IEnumerable Filenames => archive.Entries.Select(e => e.Key).ExcludeSystemFileNames(); - - public override Stream GetUnderlyingStream() => archiveStream; } } From 4bb15a8b93e1290c6a1052005bf3c2bd47623f6c Mon Sep 17 00:00:00 2001 From: voidedWarranties Date: Mon, 23 Mar 2020 22:51:37 -0700 Subject: [PATCH 26/61] Allow individual storyboard layers to disable masking --- osu.Game/Screens/Play/DimmableStoryboard.cs | 1 - osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs | 1 + osu.Game/Storyboards/StoryboardLayer.cs | 4 +++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/DimmableStoryboard.cs b/osu.Game/Screens/Play/DimmableStoryboard.cs index 0fe315fbab..eabdee95fb 100644 --- a/osu.Game/Screens/Play/DimmableStoryboard.cs +++ b/osu.Game/Screens/Play/DimmableStoryboard.cs @@ -44,7 +44,6 @@ namespace osu.Game.Screens.Play return; drawableStoryboard = storyboard.CreateDrawable(); - drawableStoryboard.Masking = true; if (async) LoadComponentAsync(drawableStoryboard, Add); diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs index 39f5418902..69219fb038 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs @@ -22,6 +22,7 @@ namespace osu.Game.Storyboards.Drawables Anchor = Anchor.Centre; Origin = Anchor.Centre; Enabled = layer.EnabledWhenPassing; + Masking = layer.Masking; } [BackgroundDependencyLoader] diff --git a/osu.Game/Storyboards/StoryboardLayer.cs b/osu.Game/Storyboards/StoryboardLayer.cs index d15f771534..e3b74ca609 100644 --- a/osu.Game/Storyboards/StoryboardLayer.cs +++ b/osu.Game/Storyboards/StoryboardLayer.cs @@ -10,15 +10,17 @@ namespace osu.Game.Storyboards { public string Name; public int Depth; + public bool Masking; public bool EnabledWhenPassing = true; public bool EnabledWhenFailing = true; public List Elements = new List(); - public StoryboardLayer(string name, int depth) + public StoryboardLayer(string name, int depth, bool masking = true) { Name = name; Depth = depth; + Masking = masking; } public void Add(IStoryboardElement element) From 022465f546ba5cfb93f21f9792365cf6010d3a2f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Mar 2020 14:13:46 +0900 Subject: [PATCH 27/61] Add encoding and import support --- .../Replays/CatchReplayFrame.cs | 9 ++ .../Replays/ManiaReplayFrame.cs | 37 ++++++++ .../Replays/OsuReplayFrame.cs | 12 +++ .../Replays/TaikoReplayFrame.cs | 12 +++ osu.Game/IO/Archives/LegacyByteArrayReader.cs | 30 ++++++ .../Replays/Types/IConvertibleReplayFrame.cs | 6 ++ osu.Game/Rulesets/UI/DrawableRuleset.cs | 4 + osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 91 +++++++++++++++++-- osu.Game/Screens/Play/Player.cs | 37 +++++--- 9 files changed, 220 insertions(+), 18 deletions(-) create mode 100644 osu.Game/IO/Archives/LegacyByteArrayReader.cs diff --git a/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs b/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs index b41a5e0612..bc60f16ae8 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs @@ -56,5 +56,14 @@ namespace osu.Game.Rulesets.Catch.Replays Actions.Add(CatchAction.MoveLeft); } } + + public LegacyReplayFrame ConvertTo(IBeatmap beatmap) + { + ReplayButtonState state = ReplayButtonState.None; + + if (Actions.Contains(CatchAction.Dash)) state |= ReplayButtonState.Left1; + + return new LegacyReplayFrame(Time, Position * CatchPlayfield.BASE_WIDTH, null, state); + } } } diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs index 877a9ee410..4987aa8e4c 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs @@ -56,5 +56,42 @@ namespace osu.Game.Rulesets.Mania.Replays activeColumns >>= 1; } } + + public LegacyReplayFrame ConvertTo(IBeatmap beatmap) + { + int keys = 0; + + var converter = new ManiaBeatmapConverter(beatmap, new ManiaRuleset()); + + var stage = new StageDefinition { Columns = converter.TargetColumns }; + + var specialColumns = new List(); + + for (int i = 0; i < converter.TargetColumns; i++) + { + if (stage.IsSpecialColumn(i)) + specialColumns.Add(i); + } + + foreach (var action in Actions) + { + switch (action) + { + case ManiaAction.Special1: + keys |= 1 << specialColumns[0]; + break; + + case ManiaAction.Special2: + keys |= 1 << specialColumns[1]; + break; + + default: + keys |= 1 << (action - ManiaAction.Key1); + break; + } + } + + return new LegacyReplayFrame(Time, keys, null, ReplayButtonState.None); + } } } diff --git a/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs b/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs index e6c6db5e61..93cf4db5b1 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs @@ -32,5 +32,17 @@ namespace osu.Game.Rulesets.Osu.Replays if (currentFrame.MouseLeft) Actions.Add(OsuAction.LeftButton); if (currentFrame.MouseRight) Actions.Add(OsuAction.RightButton); } + + public LegacyReplayFrame ConvertTo(IBeatmap beatmap) + { + ReplayButtonState state = ReplayButtonState.None; + + if (Actions.Contains(OsuAction.LeftButton)) + state |= ReplayButtonState.Left1; + if (Actions.Contains(OsuAction.RightButton)) + state |= ReplayButtonState.Right1; + + return new LegacyReplayFrame(Time, Position.X, Position.Y, state); + } } } diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs index c5ebefc397..cb4ca35c2b 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs @@ -30,5 +30,17 @@ namespace osu.Game.Rulesets.Taiko.Replays if (currentFrame.MouseLeft1) Actions.Add(TaikoAction.LeftCentre); if (currentFrame.MouseLeft2) Actions.Add(TaikoAction.RightCentre); } + + public LegacyReplayFrame ConvertTo(IBeatmap beatmap) + { + ReplayButtonState state = ReplayButtonState.None; + + if (Actions.Contains(TaikoAction.LeftRim)) state |= ReplayButtonState.Right1; + if (Actions.Contains(TaikoAction.RightRim)) state |= ReplayButtonState.Right2; + if (Actions.Contains(TaikoAction.LeftCentre)) state |= ReplayButtonState.Left1; + if (Actions.Contains(TaikoAction.RightCentre)) state |= ReplayButtonState.Left2; + + return new LegacyReplayFrame(Time, null, null, state); + } } } diff --git a/osu.Game/IO/Archives/LegacyByteArrayReader.cs b/osu.Game/IO/Archives/LegacyByteArrayReader.cs new file mode 100644 index 0000000000..0c3620403f --- /dev/null +++ b/osu.Game/IO/Archives/LegacyByteArrayReader.cs @@ -0,0 +1,30 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.IO; + +namespace osu.Game.IO.Archives +{ + /// + /// Allows reading a single file from the provided stream. + /// + public class LegacyByteArrayReader : ArchiveReader + { + private readonly byte[] content; + + public LegacyByteArrayReader(byte[] content, string filename) + : base(filename) + { + this.content = content; + } + + public override Stream GetStream(string name) => new MemoryStream(content); + + public override void Dispose() + { + } + + public override IEnumerable Filenames => new[] { Name }; + } +} diff --git a/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs b/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs index c2947c0aca..a240e7aa0e 100644 --- a/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs +++ b/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs @@ -18,5 +18,11 @@ namespace osu.Game.Rulesets.Replays.Types /// The beatmap. /// The last post-conversion , used to fill in missing delta information. May be null. void ConvertFrom(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null); + + /// + /// Populates this using values from a . + /// + /// The beatmap. + LegacyReplayFrame ConvertTo(IBeatmap beatmap); } } diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 5c57a92cd1..e4e2f5d569 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -268,6 +268,10 @@ namespace osu.Game.Rulesets.UI throw new InvalidOperationException($"A {nameof(KeyBindingInputManager)} which supports recording is not available"); var recorder = CreateReplayRecorder(recordingReplay); + + if (recorder == null) + return; + recorder.ScreenSpaceToGamefield = Playfield.ScreenSpaceToGamefield; recordingInputHandler.Recorder = recorder; diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index 927ab3fe07..3e3120d99f 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -3,6 +3,14 @@ using System; using System.IO; +using System.Linq; +using System.Text; +using osu.Framework.Extensions; +using osu.Game.Beatmaps; +using osu.Game.IO.Legacy; +using osu.Game.Replays.Legacy; +using osu.Game.Rulesets.Replays.Types; +using SharpCompress.Compressors.LZMA; namespace osu.Game.Scoring.Legacy { @@ -11,25 +19,96 @@ namespace osu.Game.Scoring.Legacy public const int LATEST_VERSION = 128; private readonly Score score; + private readonly IBeatmap beatmap; - public LegacyScoreEncoder(Score score) + public LegacyScoreEncoder(Score score, IBeatmap beatmap) { this.score = score; + this.beatmap = beatmap; if (score.ScoreInfo.Beatmap.RulesetID < 0 || score.ScoreInfo.Beatmap.RulesetID > 3) throw new ArgumentException("Only scores in the osu, taiko, catch, or mania rulesets can be encoded to the legacy score format.", nameof(score)); } - public void Encode(TextWriter writer) + public void Encode(Stream stream) { - writer.WriteLine($"osu file format v{LATEST_VERSION}"); + using (SerializationWriter sw = new SerializationWriter(stream)) + { + sw.Write((byte)score.ScoreInfo.RulesetID); + sw.Write(LATEST_VERSION); + sw.Write(score.ScoreInfo.Beatmap.MD5Hash); + sw.Write(score.ScoreInfo.UserString); + sw.Write($"lazer-{score.ScoreInfo.UserString}-{score.ScoreInfo.Date}".ComputeMD5Hash()); + sw.Write((ushort)(score.ScoreInfo.GetCount300() ?? 0)); + sw.Write((ushort)(score.ScoreInfo.GetCount100() ?? 0)); + sw.Write((ushort)(score.ScoreInfo.GetCount50() ?? 0)); + sw.Write((ushort)(score.ScoreInfo.GetCountGeki() ?? 0)); + sw.Write((ushort)(score.ScoreInfo.GetCountKatu() ?? 0)); + sw.Write((ushort)(score.ScoreInfo.GetCountMiss() ?? 0)); + sw.Write((int)(score.ScoreInfo.TotalScore)); + sw.Write((ushort)score.ScoreInfo.MaxCombo); + sw.Write(score.ScoreInfo.Combo == score.ScoreInfo.MaxCombo); + sw.Write((int)score.ScoreInfo.Ruleset.CreateInstance().ConvertToLegacyMods(score.ScoreInfo.Mods)); - writer.WriteLine(); - handleGeneral(writer); + sw.Write(getHpGraphFormatted()); + sw.Write(score.ScoreInfo.Date.DateTime); + sw.WriteByteArray(createReplayData()); + sw.Write((long)0); + writeModSpecificData(score.ScoreInfo, sw); + } } - private void handleGeneral(TextWriter writer) + private void writeModSpecificData(ScoreInfo score, SerializationWriter sw) { } + + private byte[] createReplayData() + { + var content = new ASCIIEncoding().GetBytes(replayStringContent); + + using (var outStream = new MemoryStream()) + { + using (var lzma = new LzmaStream(new LzmaEncoderProperties(false, 1 << 21, 255), false, outStream)) + { + outStream.Write(lzma.Properties); + + long fileSize = content.Length; + for (int i = 0; i < 8; i++) + outStream.WriteByte((byte)(fileSize >> (8 * i))); + + lzma.Write(content); + } + + return outStream.ToArray(); + } + } + + private string replayStringContent + { + get + { + StringBuilder replayData = new StringBuilder(); + + if (score.Replay != null) + { + LegacyReplayFrame lastF = new LegacyReplayFrame(0, 0, 0, ReplayButtonState.None); + + foreach (var f in score.Replay.Frames.OfType().Select(f => f.ConvertTo(beatmap))) + { + replayData.Append(FormattableString.Invariant($"{f.Time - lastF.Time}|{f.MouseX}|{f.MouseY}|{(int)f.ButtonState},")); + lastF = f; + } + } + + replayData.AppendFormat(@"{0}|{1}|{2}|{3},", -12345, 0, 0, 0); + return replayData.ToString(); + } + } + + private string getHpGraphFormatted() + { + // todo: implement, maybe? + return string.Empty; + } } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 8fee516f2b..f0f36db490 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -17,6 +18,7 @@ using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics.Containers; +using osu.Game.IO.Archives; using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Replays; @@ -25,6 +27,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Scoring; +using osu.Game.Scoring.Legacy; using osu.Game.Screens.Ranking; using osu.Game.Skinning; using osu.Game.Users; @@ -643,19 +646,29 @@ namespace osu.Game.Screens.Play completionProgressDelegate?.Cancel(); completionProgressDelegate = Schedule(delegate { - var score = CreateScore(); - - if (DrawableRuleset.ReplayScore == null) - { - scoreManager.Import(score).ContinueWith(_ => Schedule(() => - { - // screen may be in the exiting transition phase. - if (this.IsCurrentScreen()) - this.Push(CreateResults(score)); - })); - } + if (DrawableRuleset.ReplayScore != null) + this.Push(CreateResults(DrawableRuleset.ReplayScore.ScoreInfo)); else - this.Push(CreateResults(score)); + { + var score = new Score + { + ScoreInfo = CreateScore(), + Replay = recordingReplay + }; + + using (var stream = new MemoryStream()) + { + new LegacyScoreEncoder(score, gameplayBeatmap).Encode(stream); + + scoreManager.Import(score.ScoreInfo, new LegacyByteArrayReader(stream.ToArray(), "replay.osr")) + .ContinueWith(imported => Schedule(() => + { + // screen may be in the exiting transition phase. + if (this.IsCurrentScreen()) + this.Push(CreateResults(imported.Result)); + })); + } + } }); } From 96a849f897375c906d3ab8b672e8522aa93d3da4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Mar 2020 14:55:49 +0900 Subject: [PATCH 28/61] Add remaining replay recorders --- .../UI/CatchReplayRecorder.cs | 23 +++++++++++++++++++ .../UI/DrawableCatchRuleset.cs | 2 ++ .../UI/DrawableManiaRuleset.cs | 2 ++ .../UI/ManiaReplayRecorder.cs | 23 +++++++++++++++++++ .../UI/DrawableTaikoRuleset.cs | 2 ++ .../UI/TaikoReplayRecorder.cs | 23 +++++++++++++++++++ 6 files changed, 75 insertions(+) create mode 100644 osu.Game.Rulesets.Catch/UI/CatchReplayRecorder.cs create mode 100644 osu.Game.Rulesets.Mania/UI/ManiaReplayRecorder.cs create mode 100644 osu.Game.Rulesets.Taiko/UI/TaikoReplayRecorder.cs diff --git a/osu.Game.Rulesets.Catch/UI/CatchReplayRecorder.cs b/osu.Game.Rulesets.Catch/UI/CatchReplayRecorder.cs new file mode 100644 index 0000000000..8bede32c59 --- /dev/null +++ b/osu.Game.Rulesets.Catch/UI/CatchReplayRecorder.cs @@ -0,0 +1,23 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Game.Replays; +using osu.Game.Rulesets.Catch.Replays; +using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.UI; +using osuTK; + +namespace osu.Game.Rulesets.Catch.UI +{ + public class CatchReplayRecorder : ReplayRecorder + { + public CatchReplayRecorder(Replay target) + : base(target) + { + } + + protected override ReplayFrame HandleFrame(Vector2 position, List actions, ReplayFrame previousFrame) + => new CatchReplayFrame(Time.Current, position.X, actions.Contains(CatchAction.Dash), previousFrame as CatchReplayFrame); + } +} diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs index fd8a1d175d..594c7a57c7 100644 --- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs @@ -32,6 +32,8 @@ namespace osu.Game.Rulesets.Catch.UI protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay); + protected override ReplayRecorder CreateReplayRecorder(Replay replay) => new CatchReplayRecorder(replay); + protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty, CreateDrawableRepresentation); public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new CatchPlayfieldAdjustmentContainer(); diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 2c497541a8..e5ec054fa7 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -85,5 +85,7 @@ namespace osu.Game.Rulesets.Mania.UI } protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay); + + protected override ReplayRecorder CreateReplayRecorder(Replay replay) => new ManiaReplayRecorder(replay); } } diff --git a/osu.Game.Rulesets.Mania/UI/ManiaReplayRecorder.cs b/osu.Game.Rulesets.Mania/UI/ManiaReplayRecorder.cs new file mode 100644 index 0000000000..57cbc1ff17 --- /dev/null +++ b/osu.Game.Rulesets.Mania/UI/ManiaReplayRecorder.cs @@ -0,0 +1,23 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Game.Replays; +using osu.Game.Rulesets.Mania.Replays; +using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.UI; +using osuTK; + +namespace osu.Game.Rulesets.Mania.UI +{ + public class ManiaReplayRecorder : ReplayRecorder + { + public ManiaReplayRecorder(Replay replay) + : base(replay) + { + } + + protected override ReplayFrame HandleFrame(Vector2 position, List actions, ReplayFrame previousFrame) + => new ManiaReplayFrame(Time.Current, actions.ToArray()); + } +} diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index 9196bbf13e..e4a4b555a7 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -65,5 +65,7 @@ namespace osu.Game.Rulesets.Taiko.UI } protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new TaikoFramedReplayInputHandler(replay); + + protected override ReplayRecorder CreateReplayRecorder(Replay replay) => new TaikoReplayRecorder(replay); } } diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoReplayRecorder.cs b/osu.Game.Rulesets.Taiko/UI/TaikoReplayRecorder.cs new file mode 100644 index 0000000000..4330ae6464 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/UI/TaikoReplayRecorder.cs @@ -0,0 +1,23 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Game.Replays; +using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.Taiko.Replays; +using osu.Game.Rulesets.UI; +using osuTK; + +namespace osu.Game.Rulesets.Taiko.UI +{ + public class TaikoReplayRecorder : ReplayRecorder + { + public TaikoReplayRecorder(Replay replay) + : base(replay) + { + } + + protected override ReplayFrame HandleFrame(Vector2 position, List actions, ReplayFrame previousFrame) => + new TaikoReplayFrame(Time.Current, actions.ToArray()); + } +} From 388cf5c83a40ed2650cb947cda677c681f51a481 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Mar 2020 15:38:54 +0900 Subject: [PATCH 29/61] Fix catch positional data being incorrectly recorded --- osu.Game.Rulesets.Catch/UI/CatchReplayRecorder.cs | 9 ++++++--- osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs | 2 +- osu.Game.Rulesets.Mania/UI/ManiaReplayRecorder.cs | 2 +- osu.Game.Rulesets.Osu/UI/OsuReplayRecorder.cs | 4 ++-- osu.Game.Rulesets.Taiko/UI/TaikoReplayRecorder.cs | 2 +- osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs | 4 ++-- osu.Game/Rulesets/UI/ReplayRecorder.cs | 2 +- 7 files changed, 14 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchReplayRecorder.cs b/osu.Game.Rulesets.Catch/UI/CatchReplayRecorder.cs index 8bede32c59..9a4d1f9585 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchReplayRecorder.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchReplayRecorder.cs @@ -12,12 +12,15 @@ namespace osu.Game.Rulesets.Catch.UI { public class CatchReplayRecorder : ReplayRecorder { - public CatchReplayRecorder(Replay target) + private readonly CatchPlayfield playfield; + + public CatchReplayRecorder(Replay target, CatchPlayfield playfield) : base(target) { + this.playfield = playfield; } - protected override ReplayFrame HandleFrame(Vector2 position, List actions, ReplayFrame previousFrame) - => new CatchReplayFrame(Time.Current, position.X, actions.Contains(CatchAction.Dash), previousFrame as CatchReplayFrame); + protected override ReplayFrame HandleFrame(Vector2 mousePosition, List actions, ReplayFrame previousFrame) + => new CatchReplayFrame(Time.Current, playfield.CatcherArea.MovableCatcher.X, actions.Contains(CatchAction.Dash), previousFrame as CatchReplayFrame); } } diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs index 594c7a57c7..ebe45aa3ab 100644 --- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Catch.UI protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay); - protected override ReplayRecorder CreateReplayRecorder(Replay replay) => new CatchReplayRecorder(replay); + protected override ReplayRecorder CreateReplayRecorder(Replay replay) => new CatchReplayRecorder(replay, (CatchPlayfield)Playfield); protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty, CreateDrawableRepresentation); diff --git a/osu.Game.Rulesets.Mania/UI/ManiaReplayRecorder.cs b/osu.Game.Rulesets.Mania/UI/ManiaReplayRecorder.cs index 57cbc1ff17..18275000a2 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaReplayRecorder.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaReplayRecorder.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Mania.UI { } - protected override ReplayFrame HandleFrame(Vector2 position, List actions, ReplayFrame previousFrame) + protected override ReplayFrame HandleFrame(Vector2 mousePosition, List actions, ReplayFrame previousFrame) => new ManiaReplayFrame(Time.Current, actions.ToArray()); } } diff --git a/osu.Game.Rulesets.Osu/UI/OsuReplayRecorder.cs b/osu.Game.Rulesets.Osu/UI/OsuReplayRecorder.cs index 898212ee6b..b68ea136d5 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuReplayRecorder.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuReplayRecorder.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.UI { } - protected override ReplayFrame HandleFrame(Vector2 position, List actions, ReplayFrame previousFrame) - => new OsuReplayFrame(Time.Current, position, actions.ToArray()); + protected override ReplayFrame HandleFrame(Vector2 mousePosition, List actions, ReplayFrame previousFrame) + => new OsuReplayFrame(Time.Current, mousePosition, actions.ToArray()); } } diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoReplayRecorder.cs b/osu.Game.Rulesets.Taiko/UI/TaikoReplayRecorder.cs index 4330ae6464..1859dabf03 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoReplayRecorder.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoReplayRecorder.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Taiko.UI { } - protected override ReplayFrame HandleFrame(Vector2 position, List actions, ReplayFrame previousFrame) => + protected override ReplayFrame HandleFrame(Vector2 mousePosition, List actions, ReplayFrame previousFrame) => new TaikoReplayFrame(Time.Current, actions.ToArray()); } } diff --git a/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs b/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs index fe87ca675b..057d026132 100644 --- a/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs +++ b/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs @@ -215,7 +215,7 @@ namespace osu.Game.Tests.Gameplay { } - protected override ReplayFrame HandleFrame(Vector2 position, List actions, ReplayFrame previousFrame) => - new TestReplayFrame(Time.Current, position, actions.ToArray()); + protected override ReplayFrame HandleFrame(Vector2 mousePosition, List actions, ReplayFrame previousFrame) => + new TestReplayFrame(Time.Current, mousePosition, actions.ToArray()); } } diff --git a/osu.Game/Rulesets/UI/ReplayRecorder.cs b/osu.Game/Rulesets/UI/ReplayRecorder.cs index 74e8109d52..c977639584 100644 --- a/osu.Game/Rulesets/UI/ReplayRecorder.cs +++ b/osu.Game/Rulesets/UI/ReplayRecorder.cs @@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.UI target.Frames.Add(frame); } - protected abstract ReplayFrame HandleFrame(Vector2 position, List actions, ReplayFrame previousFrame); + protected abstract ReplayFrame HandleFrame(Vector2 mousePosition, List actions, ReplayFrame previousFrame); } public abstract class ReplayRecorder : Component From 448961b330f3ad087b558ff87cded8ddb7e57540 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Mar 2020 15:39:01 +0900 Subject: [PATCH 30/61] Rename incorrect variable --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index e4e2f5d569..27993ff173 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -264,7 +264,7 @@ namespace osu.Game.Rulesets.UI public override void SetRecordTarget(Replay recordingReplay) { - if (!(KeyBindingInputManager is IHasRecordingHandler recordingInputHandler)) + if (!(KeyBindingInputManager is IHasRecordingHandler recordingInputManager)) throw new InvalidOperationException($"A {nameof(KeyBindingInputManager)} which supports recording is not available"); var recorder = CreateReplayRecorder(recordingReplay); @@ -274,7 +274,7 @@ namespace osu.Game.Rulesets.UI recorder.ScreenSpaceToGamefield = Playfield.ScreenSpaceToGamefield; - recordingInputHandler.Recorder = recorder; + recordingInputManager.Recorder = recorder; } public override void SetReplayScore(Score replayScore) From 02a3c7c025866795894a7eae5757ee41f6034d15 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Mar 2020 15:43:22 +0900 Subject: [PATCH 31/61] Fix incorrect ruleset being recorded to file --- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index 3e3120d99f..0ba595b1c5 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -34,7 +34,7 @@ namespace osu.Game.Scoring.Legacy { using (SerializationWriter sw = new SerializationWriter(stream)) { - sw.Write((byte)score.ScoreInfo.RulesetID); + sw.Write((byte)(score.ScoreInfo.Ruleset.ID ?? 0)); sw.Write(LATEST_VERSION); sw.Write(score.ScoreInfo.Beatmap.MD5Hash); sw.Write(score.ScoreInfo.UserString); From 2feb66d4233b58772b219e9ee29ad96775af1742 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Mar 2020 15:43:34 +0900 Subject: [PATCH 32/61] Correctly handle missing positional data --- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index 0ba595b1c5..515cdc8864 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -95,7 +95,7 @@ namespace osu.Game.Scoring.Legacy foreach (var f in score.Replay.Frames.OfType().Select(f => f.ConvertTo(beatmap))) { - replayData.Append(FormattableString.Invariant($"{f.Time - lastF.Time}|{f.MouseX}|{f.MouseY}|{(int)f.ButtonState},")); + replayData.Append(FormattableString.Invariant($"{f.Time - lastF.Time}|{f.MouseX ?? 0}|{f.MouseY ?? 0}|{(int)f.ButtonState},")); lastF = f; } } From a7bfaad60fdb18115549b2c031c01b0a538f355b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Mar 2020 15:44:39 +0900 Subject: [PATCH 33/61] More correctly handle rulesets which don't support replay recording --- osu.Game/Screens/Play/Player.cs | 34 ++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index f0f36db490..7723a84637 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -650,24 +650,28 @@ namespace osu.Game.Screens.Play this.Push(CreateResults(DrawableRuleset.ReplayScore.ScoreInfo)); else { - var score = new Score - { - ScoreInfo = CreateScore(), - Replay = recordingReplay - }; + var score = new Score { ScoreInfo = CreateScore() }; - using (var stream = new MemoryStream()) - { - new LegacyScoreEncoder(score, gameplayBeatmap).Encode(stream); + LegacyByteArrayReader replayReader = null; - scoreManager.Import(score.ScoreInfo, new LegacyByteArrayReader(stream.ToArray(), "replay.osr")) - .ContinueWith(imported => Schedule(() => - { - // screen may be in the exiting transition phase. - if (this.IsCurrentScreen()) - this.Push(CreateResults(imported.Result)); - })); + if (recordingReplay?.Frames.Count > 0) + { + score.Replay = recordingReplay; + + using (var stream = new MemoryStream()) + { + new LegacyScoreEncoder(score, gameplayBeatmap).Encode(stream); + replayReader = new LegacyByteArrayReader(stream.ToArray(), "replay.osr"); + } } + + scoreManager.Import(score.ScoreInfo, replayReader) + .ContinueWith(imported => Schedule(() => + { + // screen may be in the exiting transition phase. + if (this.IsCurrentScreen()) + this.Push(CreateResults(imported.Result)); + })); } }); } From 2735a2250c1236e92cdc9be5a323e570a717d57b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Mar 2020 19:03:42 +0900 Subject: [PATCH 34/61] Move replay recorder to final location --- .../Gameplay/TestSceneReplayRecording.cs | 80 +----------------- osu.Game/Rulesets/UI/DrawableRuleset.cs | 2 + osu.Game/Rulesets/UI/ReplayRecorder.cs | 81 +++++++++++++++++++ osu.Game/Rulesets/UI/RulesetInputManager.cs | 16 ++++ 4 files changed, 100 insertions(+), 79 deletions(-) create mode 100644 osu.Game/Rulesets/UI/ReplayRecorder.cs diff --git a/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs b/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs index c2b2ebdb98..ab1998a650 100644 --- a/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs +++ b/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs @@ -1,13 +1,10 @@ // Copyright (c) ppy Pty Ltd . 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 osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Input.StateChanges; @@ -41,7 +38,7 @@ namespace osu.Game.Tests.Gameplay { new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique) { - RecordTarget = replay, + Recorder = new TestReplayRecorder(replay), Child = new Container { RelativeSizeAxes = Axes.Both, @@ -171,19 +168,6 @@ namespace osu.Game.Tests.Gameplay public class TestRulesetInputManager : RulesetInputManager { - private ReplayRecorder recorder; - - public Replay RecordTarget - { - set - { - if (recorder != null) - throw new InvalidOperationException("Cannot attach more than one recorder"); - - KeyBindingContainer.Add(recorder = new TestReplayRecorder(value)); - } - } - public TestRulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) : base(ruleset, variant, unique) { @@ -230,66 +214,4 @@ namespace osu.Game.Tests.Gameplay protected override ReplayFrame HandleFrame(InputState state, List pressedActions, ReplayFrame previousFrame) => new TestReplayFrame(Time.Current, ToLocalSpace(state.Mouse.Position), pressedActions.ToArray()); } - - internal abstract class ReplayRecorder : Component, IKeyBindingHandler - where T : struct - { - private readonly Replay target; - - private readonly List pressedActions = new List(); - - private InputManager inputManager; - - public int RecordFrameRate = 60; - - protected ReplayRecorder(Replay target) - { - this.target = target; - - RelativeSizeAxes = Axes.Both; - - Depth = float.MinValue; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - inputManager = GetContainingInputManager(); - } - - protected override bool OnMouseMove(MouseMoveEvent e) - { - recordFrame(false); - return base.OnMouseMove(e); - } - - public bool OnPressed(T action) - { - pressedActions.Add(action); - recordFrame(true); - return false; - } - - public void OnReleased(T action) - { - pressedActions.Remove(action); - recordFrame(true); - } - - private void recordFrame(bool important) - { - var last = target.Frames.LastOrDefault(); - - if (!important && last != null && Time.Current - last.Time < (1000d / RecordFrameRate)) - return; - - var frame = HandleFrame(inputManager.CurrentState, pressedActions, last); - - if (frame != null) - target.Frames.Add(frame); - } - - protected abstract ReplayFrame HandleFrame(InputState state, List testActions, ReplayFrame previousFrame); - } } diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index d0a2722f58..c8af3be980 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -302,6 +302,8 @@ namespace osu.Game.Rulesets.UI protected virtual ReplayInputHandler CreateReplayInputHandler(Replay replay) => null; + protected virtual ReplayRecorder CreateReplayRecorder(Replay replay) => null; + /// /// Creates a Playfield. /// diff --git a/osu.Game/Rulesets/UI/ReplayRecorder.cs b/osu.Game/Rulesets/UI/ReplayRecorder.cs new file mode 100644 index 0000000000..9e2f898206 --- /dev/null +++ b/osu.Game/Rulesets/UI/ReplayRecorder.cs @@ -0,0 +1,81 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Input; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; +using osu.Framework.Input.States; +using osu.Game.Replays; +using osu.Game.Rulesets.Replays; + +namespace osu.Game.Rulesets.UI +{ + public abstract class ReplayRecorder : ReplayRecorder, IKeyBindingHandler + where T : struct + { + private readonly Replay target; + + private readonly List pressedActions = new List(); + + private InputManager inputManager; + + public int RecordFrameRate = 60; + + protected ReplayRecorder(Replay target) + { + this.target = target; + + RelativeSizeAxes = Axes.Both; + + Depth = float.MinValue; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + inputManager = GetContainingInputManager(); + } + + protected override bool OnMouseMove(MouseMoveEvent e) + { + recordFrame(false); + return base.OnMouseMove(e); + } + + public bool OnPressed(T action) + { + pressedActions.Add(action); + recordFrame(true); + return false; + } + + public void OnReleased(T action) + { + pressedActions.Remove(action); + recordFrame(true); + } + + private void recordFrame(bool important) + { + var last = target.Frames.LastOrDefault(); + + if (!important && last != null && Time.Current - last.Time < (1000d / RecordFrameRate)) + return; + + var frame = HandleFrame(inputManager.CurrentState, pressedActions, last); + + if (frame != null) + target.Frames.Add(frame); + } + + protected abstract ReplayFrame HandleFrame(InputState state, List testActions, ReplayFrame previousFrame); + } + + public abstract class ReplayRecorder : Component + { + } +} diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 7f85c10b56..043e0f56cc 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -26,6 +27,21 @@ namespace osu.Game.Rulesets.UI public abstract class RulesetInputManager : PassThroughInputManager, ICanAttachKeyCounter, IHasReplayHandler where T : struct { + private ReplayRecorder recorder; + + public ReplayRecorder Recorder + { + set + { + if (recorder != null) + throw new InvalidOperationException("Cannot attach more than one recorder"); + + recorder = value; + + KeyBindingContainer.Add(recorder); + } + } + protected override InputState CreateInitialState() { var state = base.CreateInitialState(); From 8484d201d16488e83462bfb5ed722c307d616d0b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Mar 2020 15:54:04 +0900 Subject: [PATCH 35/61] Nest and rename test classes --- .../Gameplay/TestSceneReplayRecording.cs | 188 +++++++++--------- 1 file changed, 94 insertions(+), 94 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs b/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs index ab1998a650..cd9486a70a 100644 --- a/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs +++ b/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs @@ -56,7 +56,7 @@ namespace osu.Game.Tests.Gameplay Anchor = Anchor.Centre, Origin = Anchor.Centre, }, - new TestConsumer() + new TestInputConsumer() } }, } @@ -86,7 +86,7 @@ namespace osu.Game.Tests.Gameplay Anchor = Anchor.Centre, Origin = Anchor.Centre, }, - new TestConsumer() + new TestInputConsumer() } }, } @@ -101,117 +101,117 @@ namespace osu.Game.Tests.Gameplay playbackManager.ReplayInputHandler.SetFrameFromTime(Time.Current - 500); } - } - public class TestFramedReplayInputHandler : FramedReplayInputHandler - { - public TestFramedReplayInputHandler(Replay replay) - : base(replay) + public class TestFramedReplayInputHandler : FramedReplayInputHandler { - } - - public override List GetPendingInputs() - { - return new List + public TestFramedReplayInputHandler(Replay replay) + : base(replay) { - new MousePositionAbsoluteInput - { - Position = GamefieldToScreenSpace(CurrentFrame?.Position ?? Vector2.Zero) - }, - new ReplayState - { - PressedActions = CurrentFrame?.Actions ?? new List() - } - }; - } - } + } - public class TestConsumer : CompositeDrawable, IKeyBindingHandler - { - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parent.ReceivePositionalInputAt(screenSpacePos); - - private readonly Box box; - - public TestConsumer() - { - Size = new Vector2(30); - - Origin = Anchor.Centre; - - InternalChildren = new Drawable[] + public override List GetPendingInputs() { - box = new Box + return new List { - Colour = Color4.Black, - RelativeSizeAxes = Axes.Both, - }, - }; + new MousePositionAbsoluteInput + { + Position = GamefieldToScreenSpace(CurrentFrame?.Position ?? Vector2.Zero) + }, + new ReplayState + { + PressedActions = CurrentFrame?.Actions ?? new List() + } + }; + } } - protected override bool OnMouseMove(MouseMoveEvent e) + public class TestInputConsumer : CompositeDrawable, IKeyBindingHandler { - Position = e.MousePosition; - return base.OnMouseMove(e); - } + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parent.ReceivePositionalInputAt(screenSpacePos); - public bool OnPressed(TestAction action) - { - box.Colour = Color4.White; - return true; - } + private readonly Box box; - public void OnReleased(TestAction action) - { - box.Colour = Color4.Black; - } - } - - public class TestRulesetInputManager : RulesetInputManager - { - public TestRulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) - : base(ruleset, variant, unique) - { - } - - protected override KeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) - => new TestKeyBindingContainer(); - - internal class TestKeyBindingContainer : KeyBindingContainer - { - public override IEnumerable DefaultKeyBindings => new[] + public TestInputConsumer() { - new KeyBinding(InputKey.MouseLeft, TestAction.Down), - }; + Size = new Vector2(30); + + Origin = Anchor.Centre; + + InternalChildren = new Drawable[] + { + box = new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + }, + }; + } + + protected override bool OnMouseMove(MouseMoveEvent e) + { + Position = e.MousePosition; + return base.OnMouseMove(e); + } + + public bool OnPressed(TestAction action) + { + box.Colour = Color4.White; + return true; + } + + public void OnReleased(TestAction action) + { + box.Colour = Color4.Black; + } } - } - public class TestReplayFrame : ReplayFrame - { - public Vector2 Position; - - public List Actions = new List(); - - public TestReplayFrame(double time, Vector2 position, params TestAction[] actions) - : base(time) + public class TestRulesetInputManager : RulesetInputManager { - Position = position; - Actions.AddRange(actions); + public TestRulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) + : base(ruleset, variant, unique) + { + } + + protected override KeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) + => new TestKeyBindingContainer(); + + internal class TestKeyBindingContainer : KeyBindingContainer + { + public override IEnumerable DefaultKeyBindings => new[] + { + new KeyBinding(InputKey.MouseLeft, TestAction.Down), + }; + } } - } - public enum TestAction - { - Down, - } - - internal class TestReplayRecorder : ReplayRecorder - { - public TestReplayRecorder(Replay target) - : base(target) + public class TestReplayFrame : ReplayFrame { + public Vector2 Position; + + public List Actions = new List(); + + public TestReplayFrame(double time, Vector2 position, params TestAction[] actions) + : base(time) + { + Position = position; + Actions.AddRange(actions); + } } - protected override ReplayFrame HandleFrame(InputState state, List pressedActions, ReplayFrame previousFrame) => - new TestReplayFrame(Time.Current, ToLocalSpace(state.Mouse.Position), pressedActions.ToArray()); + public enum TestAction + { + Down, + } + + internal class TestReplayRecorder : ReplayRecorder + { + public TestReplayRecorder(Replay target) + : base(target) + { + } + + protected override ReplayFrame HandleFrame(InputState state, List pressedActions, ReplayFrame previousFrame) => + new TestReplayFrame(Time.Current, ToLocalSpace(state.Mouse.Position), pressedActions.ToArray()); + } } } From 417ff837ac95434809dcf5333ec7246b6dd925fb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Mar 2020 16:22:54 +0900 Subject: [PATCH 36/61] Add basic tests --- ...ecording.cs => TestSceneReplayRecorder.cs} | 79 +++++++++++++++++-- 1 file changed, 71 insertions(+), 8 deletions(-) rename osu.Game.Tests/Gameplay/{TestSceneReplayRecording.cs => TestSceneReplayRecorder.cs} (68%) diff --git a/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs b/osu.Game.Tests/Gameplay/TestSceneReplayRecorder.cs similarity index 68% rename from osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs rename to osu.Game.Tests/Gameplay/TestSceneReplayRecorder.cs index cd9486a70a..e99c399b89 100644 --- a/osu.Game.Tests/Gameplay/TestSceneReplayRecording.cs +++ b/osu.Game.Tests/Gameplay/TestSceneReplayRecorder.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -9,6 +11,8 @@ using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Input.StateChanges; using osu.Framework.Input.States; +using osu.Framework.Testing; +using osu.Framework.Threading; using osu.Game.Graphics.Sprites; using osu.Game.Replays; using osu.Game.Rulesets; @@ -18,16 +22,23 @@ using osu.Game.Tests.Visual; using osu.Game.Tests.Visual.UserInterface; using osuTK; using osuTK.Graphics; +using osuTK.Input; namespace osu.Game.Tests.Gameplay { - public class TestSceneReplayRecording : OsuTestScene + public class TestSceneReplayRecorder : OsuManualInputManagerTestScene { - private readonly TestRulesetInputManager playbackManager; + private TestRulesetInputManager playbackManager; + private TestRulesetInputManager recordingManager; - public TestSceneReplayRecording() + private Replay replay; + + private TestReplayRecorder recorder; + + [SetUp] + public void SetUp() => Schedule(() => { - Replay replay = new Replay(); + replay = new Replay(); Add(new GridContainer { @@ -36,9 +47,9 @@ namespace osu.Game.Tests.Gameplay { new Drawable[] { - new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique) + recordingManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique) { - Recorder = new TestReplayRecorder(replay), + Recorder = recorder = new TestReplayRecorder(replay), Child = new Container { RelativeSizeAxes = Axes.Both, @@ -93,13 +104,65 @@ namespace osu.Game.Tests.Gameplay } } }); + }); + + [Test] + public void TestBasic() + { + AddStep("move to center", () => InputManager.MoveMouseTo(recordingManager.ScreenSpaceDrawQuad.Centre)); + AddUntilStep("one frame recorded", () => replay.Frames.Count == 1); + AddAssert("position matches", () => playbackManager.ChildrenOfType().First().Position == recordingManager.ChildrenOfType().First().Position); + } + + [Test] + public void TestHighFrameRate() + { + ScheduledDelegate moveFunction = null; + + AddStep("move to center", () => InputManager.MoveMouseTo(recordingManager.ScreenSpaceDrawQuad.Centre)); + AddStep("much move", () => moveFunction = Scheduler.AddDelayed(() => + InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(-1, 0)), 10, true)); + AddWaitStep("move", 10); + AddStep("stop move", () => moveFunction.Cancel()); + AddAssert("at least 60 frames recorded", () => replay.Frames.Count > 60); + } + + [Test] + public void TestLimitedFrameRate() + { + ScheduledDelegate moveFunction = null; + + AddStep("lower rate", () => recorder.RecordFrameRate = 2); + AddStep("move to center", () => InputManager.MoveMouseTo(recordingManager.ScreenSpaceDrawQuad.Centre)); + AddStep("much move", () => moveFunction = Scheduler.AddDelayed(() => + InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(-1, 0)), 10, true)); + AddWaitStep("move", 10); + AddStep("stop move", () => moveFunction.Cancel()); + AddAssert("less than 10 frames recorded", () => replay.Frames.Count < 10); + } + + [Test] + public void TestLimitedFrameRateWithImportantFrames() + { + ScheduledDelegate moveFunction = null; + + AddStep("lower rate", () => recorder.RecordFrameRate = 2); + AddStep("move to center", () => InputManager.MoveMouseTo(recordingManager.ScreenSpaceDrawQuad.Centre)); + AddStep("much move with press", () => moveFunction = Scheduler.AddDelayed(() => + { + InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(-1, 0)); + InputManager.PressButton(MouseButton.Left); + InputManager.ReleaseButton(MouseButton.Left); + }, 10, true)); + AddWaitStep("move", 10); + AddStep("stop move", () => moveFunction.Cancel()); + AddAssert("at least 60 frames recorded", () => replay.Frames.Count > 60); } protected override void Update() { base.Update(); - - playbackManager.ReplayInputHandler.SetFrameFromTime(Time.Current - 500); + playbackManager?.ReplayInputHandler.SetFrameFromTime(Time.Current - 100); } public class TestFramedReplayInputHandler : FramedReplayInputHandler From 368bf585217409695bb4b336b97907b23834c560 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Mar 2020 11:08:08 +0900 Subject: [PATCH 37/61] Rename and make fields readonly --- .../Formats/LegacyStoryboardDecoderTest.cs | 16 ++++++++-------- .../Storyboards/Drawables/DrawableStoryboard.cs | 2 +- .../Drawables/DrawableStoryboardLayer.cs | 2 +- osu.Game/Storyboards/Storyboard.cs | 4 ++-- osu.Game/Storyboards/StoryboardLayer.cs | 14 +++++++++----- 5 files changed, 21 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs index 76b76aa357..a6945dca90 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs @@ -31,29 +31,29 @@ namespace osu.Game.Tests.Beatmaps.Formats StoryboardLayer background = storyboard.Layers.FirstOrDefault(l => l.Depth == 3); Assert.IsNotNull(background); Assert.AreEqual(16, background.Elements.Count); - Assert.IsTrue(background.EnabledWhenFailing); - Assert.IsTrue(background.EnabledWhenPassing); + Assert.IsTrue(background.VisibleWhenFailing); + Assert.IsTrue(background.VisibleWhenPassing); Assert.AreEqual("Background", background.Name); StoryboardLayer fail = storyboard.Layers.FirstOrDefault(l => l.Depth == 2); Assert.IsNotNull(fail); Assert.AreEqual(0, fail.Elements.Count); - Assert.IsTrue(fail.EnabledWhenFailing); - Assert.IsFalse(fail.EnabledWhenPassing); + Assert.IsTrue(fail.VisibleWhenFailing); + Assert.IsFalse(fail.VisibleWhenPassing); Assert.AreEqual("Fail", fail.Name); StoryboardLayer pass = storyboard.Layers.FirstOrDefault(l => l.Depth == 1); Assert.IsNotNull(pass); Assert.AreEqual(0, pass.Elements.Count); - Assert.IsFalse(pass.EnabledWhenFailing); - Assert.IsTrue(pass.EnabledWhenPassing); + Assert.IsFalse(pass.VisibleWhenFailing); + Assert.IsTrue(pass.VisibleWhenPassing); Assert.AreEqual("Pass", pass.Name); StoryboardLayer foreground = storyboard.Layers.FirstOrDefault(l => l.Depth == 0); Assert.IsNotNull(foreground); Assert.AreEqual(151, foreground.Elements.Count); - Assert.IsTrue(foreground.EnabledWhenFailing); - Assert.IsTrue(foreground.EnabledWhenPassing); + Assert.IsTrue(foreground.VisibleWhenFailing); + Assert.IsTrue(foreground.VisibleWhenPassing); Assert.AreEqual("Foreground", foreground.Name); int spriteCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardSprite)); diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index 94d7395ecf..bc6e01a729 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs @@ -75,7 +75,7 @@ namespace osu.Game.Storyboards.Drawables private void updateLayerVisibility() { foreach (var layer in Children) - layer.Enabled = passing ? layer.Layer.EnabledWhenPassing : layer.Layer.EnabledWhenFailing; + layer.Enabled = passing ? layer.Layer.VisibleWhenPassing : layer.Layer.VisibleWhenFailing; } } } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs index 69219fb038..def4eed2ca 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs @@ -21,7 +21,7 @@ namespace osu.Game.Storyboards.Drawables RelativeSizeAxes = Axes.Both; Anchor = Anchor.Centre; Origin = Anchor.Centre; - Enabled = layer.EnabledWhenPassing; + Enabled = layer.VisibleWhenPassing; Masking = layer.Masking; } diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index 35bfe8c229..2ba8563ba8 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -22,8 +22,8 @@ namespace osu.Game.Storyboards public Storyboard() { layers.Add("Background", new StoryboardLayer("Background", 3)); - layers.Add("Fail", new StoryboardLayer("Fail", 2) { EnabledWhenPassing = false, }); - layers.Add("Pass", new StoryboardLayer("Pass", 1) { EnabledWhenFailing = false, }); + layers.Add("Fail", new StoryboardLayer("Fail", 2) { VisibleWhenPassing = false, }); + layers.Add("Pass", new StoryboardLayer("Pass", 1) { VisibleWhenFailing = false, }); layers.Add("Foreground", new StoryboardLayer("Foreground", 0)); } diff --git a/osu.Game/Storyboards/StoryboardLayer.cs b/osu.Game/Storyboards/StoryboardLayer.cs index e3b74ca609..142bc60deb 100644 --- a/osu.Game/Storyboards/StoryboardLayer.cs +++ b/osu.Game/Storyboards/StoryboardLayer.cs @@ -8,11 +8,15 @@ namespace osu.Game.Storyboards { public class StoryboardLayer { - public string Name; - public int Depth; - public bool Masking; - public bool EnabledWhenPassing = true; - public bool EnabledWhenFailing = true; + public readonly string Name; + + public readonly int Depth; + + public readonly bool Masking; + + public bool VisibleWhenPassing = true; + + public bool VisibleWhenFailing = true; public List Elements = new List(); From f2e0fba1648ee4668b63e3b30d7cb92ad137fe48 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Mar 2020 11:59:59 +0900 Subject: [PATCH 38/61] Remove VideoFile from BeatmapMetadata Leaving in database because it's a pain to drop columns. --- osu.Game/Beatmaps/BeatmapMetadata.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index 9267527d79..001f319307 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -52,7 +52,6 @@ namespace osu.Game.Beatmaps public int PreviewTime { get; set; } public string AudioFile { get; set; } public string BackgroundFile { get; set; } - public string VideoFile { get; set; } public override string ToString() => $"{Artist} - {Title} ({Author})"; @@ -82,8 +81,7 @@ namespace osu.Game.Beatmaps && Tags == other.Tags && PreviewTime == other.PreviewTime && AudioFile == other.AudioFile - && BackgroundFile == other.BackgroundFile - && VideoFile == other.VideoFile; + && BackgroundFile == other.BackgroundFile; } } } From b8f20831a18e8cb6bf1dddc7d08ee3b75d325e35 Mon Sep 17 00:00:00 2001 From: voidedWarranties Date: Tue, 24 Mar 2020 20:04:09 -0700 Subject: [PATCH 39/61] Video no longer modifies storyboard resolution --- osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs | 4 ++-- osu.Game/Storyboards/Storyboard.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index ba4eb7209b..269449ef80 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -90,9 +90,9 @@ namespace osu.Game.Beatmaps.Formats case LegacyEventType.Video: { var offset = Parsing.ParseInt(split[1]); - var filename = CleanFilename(split[2]); + var path = CleanFilename(split[2]); - storyboard.GetLayer("Video").Add(new StoryboardVideo(filename, offset)); + storyboard.GetLayer("Video").Add(new StoryboardVideo(path, offset)); break; } diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index dba8b4e176..a1ddafbacf 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -21,7 +21,7 @@ namespace osu.Game.Storyboards public Storyboard() { - layers.Add("Video", new StoryboardLayer("Video", 4)); + layers.Add("Video", new StoryboardLayer("Video", 4, false)); layers.Add("Background", new StoryboardLayer("Background", 3)); layers.Add("Fail", new StoryboardLayer("Fail", 2) { VisibleWhenPassing = false, }); layers.Add("Pass", new StoryboardLayer("Pass", 1) { VisibleWhenFailing = false, }); @@ -57,7 +57,7 @@ namespace osu.Game.Storyboards public DrawableStoryboard CreateDrawable(WorkingBeatmap working = null) { var drawable = new DrawableStoryboard(this); - drawable.Width = drawable.Height * (BeatmapInfo.WidescreenStoryboard || GetLayer("Video").Elements.Any() ? 16 / 9f : 4 / 3f); + drawable.Width = drawable.Height * (BeatmapInfo.WidescreenStoryboard ? 16 / 9f : 4 / 3f); return drawable; } } From 87db1ba48703d07371f12f822d4d2eb765c1adfb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Mar 2020 14:58:49 +0900 Subject: [PATCH 40/61] Remove unused text transform helpers --- osu.Game/Graphics/Sprites/OsuSpriteText.cs | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/osu.Game/Graphics/Sprites/OsuSpriteText.cs b/osu.Game/Graphics/Sprites/OsuSpriteText.cs index cd988c347b..76e46513ba 100644 --- a/osu.Game/Graphics/Sprites/OsuSpriteText.cs +++ b/osu.Game/Graphics/Sprites/OsuSpriteText.cs @@ -1,9 +1,7 @@ // 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.Graphics; using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Transforms; namespace osu.Game.Graphics.Sprites { @@ -15,23 +13,4 @@ namespace osu.Game.Graphics.Sprites Font = OsuFont.Default; } } - - public static class OsuSpriteTextTransformExtensions - { - /// - /// Sets Text to a new value after a duration. - /// - /// A to which further transforms can be added. - public static TransformSequence TransformTextTo(this T spriteText, string newText, double duration = 0, Easing easing = Easing.None) - where T : OsuSpriteText - => spriteText.TransformTo(nameof(OsuSpriteText.Text), newText, duration, easing); - - /// - /// Sets Text to a new value after a duration. - /// - /// A to which further transforms can be added. - public static TransformSequence TransformTextTo(this TransformSequence t, string newText, double duration = 0, Easing easing = Easing.None) - where T : OsuSpriteText - => t.Append(o => o.TransformTextTo(newText, duration, easing)); - } } From 880d138a47276f82323d3187e54375abfc85d252 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Mar 2020 15:12:19 +0900 Subject: [PATCH 41/61] Fix intro tests not asserting pass or working at all --- osu.Game.Tests/Visual/Menus/IntroTestScene.cs | 2 ++ osu.Game/Screens/Menu/MainMenu.cs | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/IntroTestScene.cs b/osu.Game.Tests/Visual/Menus/IntroTestScene.cs index 5870ef9813..1ad4d9dca9 100644 --- a/osu.Game.Tests/Visual/Menus/IntroTestScene.cs +++ b/osu.Game.Tests/Visual/Menus/IntroTestScene.cs @@ -64,6 +64,8 @@ namespace osu.Game.Tests.Visual.Menus introStack.Push(CreateScreen()); }); + + AddUntilStep("wait for menu", () => introStack.CurrentScreen is MainMenu); } protected abstract IScreen CreateScreen(); diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 127270f521..dcee5e83b7 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -140,7 +140,7 @@ namespace osu.Game.Screens.Menu preloadSongSelect(); } - [Resolved] + [Resolved(canBeNull: true)] private OsuGame game { get; set; } private void confirmAndExit() @@ -148,7 +148,7 @@ namespace osu.Game.Screens.Menu if (exitConfirmed) return; exitConfirmed = true; - game.PerformFromScreen(menu => menu.Exit()); + game?.PerformFromScreen(menu => menu.Exit()); } private void preloadSongSelect() From b1d4261402088dd44aee59130863d15be2f90add Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Mar 2020 15:45:33 +0900 Subject: [PATCH 42/61] Fix track looping state not being reset when entering editor from song select Closes #8432. --- osu.Game/Screens/Select/PlaySongSelect.cs | 2 -- osu.Game/Screens/Select/SongSelect.cs | 3 +++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 8b0547376d..179aab54a3 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -85,8 +85,6 @@ namespace osu.Game.Screens.Select } } - Beatmap.Value.Track.Looping = false; - SampleConfirm?.Play(); this.Push(player = new PlayerLoader(() => new Player())); diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index b6ec40ab88..895a8ad0c9 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -572,6 +572,9 @@ namespace osu.Game.Screens.Select BeatmapOptions.Hide(); + if (Beatmap.Value.Track != null) + Beatmap.Value.Track.Looping = false; + this.ScaleTo(1.1f, 250, Easing.InSine); this.FadeOut(250); From 8a2aac5f8377a27ceb33e22eeb5fa8dbb4432c9d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Mar 2020 20:21:34 +0900 Subject: [PATCH 43/61] Rename conversion methods for clarity --- osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs | 4 ++-- osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs | 4 ++-- osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs | 4 ++-- osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs | 4 ++-- osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs | 4 ++-- osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 2 +- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 2 +- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs b/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs index bc60f16ae8..9dab3ed630 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Catch.Replays } } - public void ConvertFrom(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null) + public void FromLegacy(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null) { Position = currentFrame.Position.X / CatchPlayfield.BASE_WIDTH; Dashing = currentFrame.ButtonState == ReplayButtonState.Left1; @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Catch.Replays } } - public LegacyReplayFrame ConvertTo(IBeatmap beatmap) + public LegacyReplayFrame ToLegacy(IBeatmap beatmap) { ReplayButtonState state = ReplayButtonState.None; diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs index 4987aa8e4c..b93e372027 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Mania.Replays Actions.AddRange(actions); } - public void ConvertFrom(LegacyReplayFrame legacyFrame, IBeatmap beatmap, ReplayFrame lastFrame = null) + public void FromLegacy(LegacyReplayFrame legacyFrame, IBeatmap beatmap, ReplayFrame lastFrame = null) { // We don't need to fully convert, just create the converter var converter = new ManiaBeatmapConverter(beatmap, new ManiaRuleset()); @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Mania.Replays } } - public LegacyReplayFrame ConvertTo(IBeatmap beatmap) + public LegacyReplayFrame ToLegacy(IBeatmap beatmap) { int keys = 0; diff --git a/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs b/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs index 93cf4db5b1..3db81d70da 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs @@ -26,14 +26,14 @@ namespace osu.Game.Rulesets.Osu.Replays Actions.AddRange(actions); } - public void ConvertFrom(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null) + public void FromLegacy(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null) { Position = currentFrame.Position; if (currentFrame.MouseLeft) Actions.Add(OsuAction.LeftButton); if (currentFrame.MouseRight) Actions.Add(OsuAction.RightButton); } - public LegacyReplayFrame ConvertTo(IBeatmap beatmap) + public LegacyReplayFrame ToLegacy(IBeatmap beatmap) { ReplayButtonState state = ReplayButtonState.None; diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs index cb4ca35c2b..d2a7329a28 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko.Replays Actions.AddRange(actions); } - public void ConvertFrom(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null) + public void FromLegacy(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null) { if (currentFrame.MouseRight1) Actions.Add(TaikoAction.LeftRim); if (currentFrame.MouseRight2) Actions.Add(TaikoAction.RightRim); @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Taiko.Replays if (currentFrame.MouseLeft2) Actions.Add(TaikoAction.RightCentre); } - public LegacyReplayFrame ConvertTo(IBeatmap beatmap) + public LegacyReplayFrame ToLegacy(IBeatmap beatmap) { ReplayButtonState state = ReplayButtonState.None; diff --git a/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs b/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs index a240e7aa0e..d9aa615c6e 100644 --- a/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs +++ b/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs @@ -17,12 +17,12 @@ namespace osu.Game.Rulesets.Replays.Types /// The to extract values from. /// The beatmap. /// The last post-conversion , used to fill in missing delta information. May be null. - void ConvertFrom(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null); + void FromLegacy(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null); /// /// Populates this using values from a . /// /// The beatmap. - LegacyReplayFrame ConvertTo(IBeatmap beatmap); + LegacyReplayFrame ToLegacy(IBeatmap beatmap); } } diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index 495d8c8cc0..58b64e1b8f 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -264,7 +264,7 @@ namespace osu.Game.Scoring.Legacy if (convertible == null) throw new InvalidOperationException($"Legacy replay cannot be converted for the ruleset: {currentRuleset.Description}"); - convertible.ConvertFrom(currentFrame, currentBeatmap, lastFrame); + convertible.FromLegacy(currentFrame, currentBeatmap, lastFrame); var frame = (ReplayFrame)convertible; frame.Time = currentFrame.Time; diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index 515cdc8864..db7e51e833 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -93,7 +93,7 @@ namespace osu.Game.Scoring.Legacy { LegacyReplayFrame lastF = new LegacyReplayFrame(0, 0, 0, ReplayButtonState.None); - foreach (var f in score.Replay.Frames.OfType().Select(f => f.ConvertTo(beatmap))) + foreach (var f in score.Replay.Frames.OfType().Select(f => f.ToLegacy(beatmap))) { replayData.Append(FormattableString.Invariant($"{f.Time - lastF.Time}|{f.MouseX ?? 0}|{f.MouseY ?? 0}|{(int)f.ButtonState},")); lastF = f; From 1e025b7c3187a6dc31d363aeb89ead355f85135d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 25 Mar 2020 20:58:51 +0300 Subject: [PATCH 44/61] Add tests to cover the issue --- .../Visual/Online/TestSceneUserPanel.cs | 32 +++++++++++++++++-- osu.Game/Users/UserPanel.cs | 7 ++-- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs index ccae778745..a38f045e7f 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs @@ -28,7 +28,7 @@ namespace osu.Game.Tests.Visual.Online private readonly Bindable status = new Bindable(); private UserGridPanel peppy; - private UserListPanel evast; + private TestUserListPanel evast; [Resolved] private RulesetStore rulesetStore { get; set; } @@ -38,6 +38,9 @@ namespace osu.Game.Tests.Visual.Online { UserGridPanel flyte; + activity.Value = null; + status.Value = null; + Child = new FillFlowContainer { Anchor = Anchor.Centre, @@ -63,7 +66,7 @@ namespace osu.Game.Tests.Visual.Online IsSupporter = true, SupportLevel = 3, }) { Width = 300 }, - evast = new UserListPanel(new User + evast = new TestUserListPanel(new User { Username = @"Evast", Id = 8195163, @@ -96,7 +99,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestUserActivity() { - AddStep("set online status", () => peppy.Status.Value = evast.Status.Value = new UserStatusOnline()); + AddStep("set online status", () => status.Value = new UserStatusOnline()); AddStep("idle", () => activity.Value = null); AddStep("spectating", () => activity.Value = new UserActivity.Spectating()); @@ -109,6 +112,29 @@ namespace osu.Game.Tests.Visual.Online AddStep("modding", () => activity.Value = new UserActivity.Modding()); } + [Test] + public void TestUserActivityChange() + { + AddAssert("visit message is visible", () => evast.LastVisitMessage.IsPresent); + AddStep("set online status", () => status.Value = new UserStatusOnline()); + AddAssert("visit message is not visible", () => !evast.LastVisitMessage.IsPresent); + AddStep("set choosing activity", () => activity.Value = new UserActivity.ChoosingBeatmap()); + AddStep("set offline status", () => status.Value = new UserStatusOffline()); + AddAssert("visit message is visible", () => evast.LastVisitMessage.IsPresent); + AddStep("set online status", () => status.Value = new UserStatusOnline()); + AddAssert("visit message is not visible", () => !evast.LastVisitMessage.IsPresent); + } + private UserActivity soloGameStatusForRuleset(int rulesetId) => new UserActivity.SoloGame(null, rulesetStore.GetRuleset(rulesetId)); + + private class TestUserListPanel : UserListPanel + { + public TestUserListPanel(User user) + : base(user) + { + } + + public new TextFlowContainer LastVisitMessage => base.LastVisitMessage; + } } } diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index d5e6d5f13e..2f3986b4c0 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -36,9 +36,10 @@ namespace osu.Game.Users protected DelayedLoadUnloadWrapper Background { get; private set; } + protected TextFlowContainer LastVisitMessage { get; private set; } + private SpriteIcon statusIcon; private OsuSpriteText statusMessage; - private TextFlowContainer lastVisitMessage; protected UserPanel(User user) { @@ -153,7 +154,7 @@ namespace osu.Game.Users var alignment = rightAlignedChildren ? Anchor.CentreRight : Anchor.CentreLeft; - statusContainer.Add(lastVisitMessage = new TextFlowContainer(t => t.Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold)).With(text => + statusContainer.Add(LastVisitMessage = new TextFlowContainer(t => t.Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold)).With(text => { text.Anchor = alignment; text.Origin = alignment; @@ -193,7 +194,7 @@ namespace osu.Game.Users } // Otherwise use only status - lastVisitMessage.FadeTo(status is UserStatusOffline && User.LastVisit.HasValue ? 1 : 0); + LastVisitMessage.FadeTo(status is UserStatusOffline && User.LastVisit.HasValue ? 1 : 0); statusMessage.Text = status.Message; statusIcon.FadeColour(status.GetAppropriateColour(colours), 500, Easing.OutQuint); From 454e402e882b23f3188543f3d7b1de1ce1bdfb65 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 25 Mar 2020 21:02:45 +0300 Subject: [PATCH 45/61] Fix last seen message has been visible when it shouldn't --- osu.Game/Users/UserPanel.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index 2f3986b4c0..6f59f9e443 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -185,6 +185,8 @@ namespace osu.Game.Users { if (status != null) { + LastVisitMessage.FadeTo(status is UserStatusOffline && User.LastVisit.HasValue ? 1 : 0); + // Set status message based on activity (if we have one) and status is not offline if (activity != null && !(status is UserStatusOffline)) { @@ -194,7 +196,6 @@ namespace osu.Game.Users } // Otherwise use only status - LastVisitMessage.FadeTo(status is UserStatusOffline && User.LastVisit.HasValue ? 1 : 0); statusMessage.Text = status.Message; statusIcon.FadeColour(status.GetAppropriateColour(colours), 500, Easing.OutQuint); From 2b1245f63a279e2eee4521f689ed692ed839d376 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Mar 2020 12:50:00 +0900 Subject: [PATCH 46/61] Improve xmldoc in a couple of places --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 2 +- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 27993ff173..ff6ed5bf17 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -406,7 +406,7 @@ namespace osu.Game.Rulesets.UI public abstract Playfield Playfield { get; } /// - /// Place to put drawables above hit objects but below UI. + /// Content to be placed above hitobjects. Will be affected by frame stability. /// public abstract Container Overlays { get; } diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index e569bb8459..3ba28aad45 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.UI { /// /// A container which consumes a parent gameplay clock and standardises frame counts for children. - /// Will ensure a minimum of 40 frames per clock second is maintained, regardless of any system lag or seeks. + /// Will ensure a minimum of 50 frames per clock second is maintained, regardless of any system lag or seeks. /// public class FrameStabilityContainer : Container, IHasReplayHandler { From d372ddaadd65696972f778ddfd2cb3c3df750ac4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Mar 2020 12:50:18 +0900 Subject: [PATCH 47/61] Move break overlay to a location it is not affected by gameplay scale --- osu.Game/Screens/Play/Player.cs | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index dc5bac9fd1..3ff47b868c 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -293,19 +293,26 @@ namespace osu.Game.Screens.Play performImmediateExit(); }, }, - failAnimation = new FailAnimation(DrawableRuleset) { OnComplete = onFailComplete, } + failAnimation = new FailAnimation(DrawableRuleset) { OnComplete = onFailComplete, }, + new Container + { + Name = "Frame-stable elements", + Clock = DrawableRuleset.FrameStableClock, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + ScoreProcessor, + HealthProcessor, + BreakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, DrawableRuleset.GameplayStartTime, ScoreProcessor) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Breaks = working.Beatmap.Breaks + }, + } + }, }); - DrawableRuleset.Overlays.Add(BreakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, DrawableRuleset.GameplayStartTime, ScoreProcessor) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Breaks = working.Beatmap.Breaks - }); - - DrawableRuleset.Overlays.Add(ScoreProcessor); - DrawableRuleset.Overlays.Add(HealthProcessor); - HealthProcessor.IsBreakTime.BindTo(BreakOverlay.IsBreakTime); } From 07462120e47d46e0a8639f2868538eb822800d86 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Mar 2020 15:28:56 +0900 Subject: [PATCH 48/61] Split break tracking into its own component --- .../Visual/Gameplay/TestSceneAutoplay.cs | 3 +- ...eakOverlay.cs => TestSceneBreakTracker.cs} | 53 ++++++++---- .../Graphics/Containers/UserDimContainer.cs | 2 +- osu.Game/Rulesets/UI/DrawableRuleset.cs | 12 ++- osu.Game/Screens/Play/BreakOverlay.cs | 80 ++---------------- osu.Game/Screens/Play/BreakTracker.cs | 82 +++++++++++++++++++ osu.Game/Screens/Play/Player.cs | 43 +++++----- 7 files changed, 160 insertions(+), 115 deletions(-) rename osu.Game.Tests/Visual/Gameplay/{TestSceneBreakOverlay.cs => TestSceneBreakTracker.cs} (80%) create mode 100644 osu.Game/Screens/Play/BreakTracker.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs index afeda5fb7c..8108ce0864 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs @@ -3,6 +3,7 @@ using System.ComponentModel; using System.Linq; +using osu.Framework.Testing; using osu.Game.Rulesets; using osu.Game.Screens.Play; @@ -23,7 +24,7 @@ namespace osu.Game.Tests.Visual.Gameplay { AddUntilStep("score above zero", () => Player.ScoreProcessor.TotalScore.Value > 0); AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 2)); - AddStep("seek to break time", () => Player.GameplayClockContainer.Seek(Player.BreakOverlay.Breaks.First().StartTime)); + AddStep("seek to break time", () => Player.GameplayClockContainer.Seek(Player.ChildrenOfType().First().Breaks.First().StartTime)); AddUntilStep("wait for seek to complete", () => Player.HUDOverlay.Progress.ReferenceClock.CurrentTime >= Player.BreakOverlay.Breaks.First().StartTime); AddAssert("test keys not counting", () => !Player.HUDOverlay.KeyCounter.IsCounting); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs similarity index 80% rename from osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs rename to osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs index 19dce303ea..d46b4ea289 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Graphics; using osu.Framework.Timing; using osu.Game.Beatmaps.Timing; using osu.Game.Screens.Play; @@ -12,14 +13,16 @@ using osu.Game.Screens.Play; namespace osu.Game.Tests.Visual.Gameplay { [TestFixture] - public class TestSceneBreakOverlay : OsuTestScene + public class TestSceneBreakTracker : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { typeof(BreakOverlay), }; - private readonly TestBreakOverlay breakOverlay; + private readonly BreakOverlay breakOverlay; + + private readonly TestBreakTracker breakTracker; private readonly IReadOnlyList testBreaks = new List { @@ -35,9 +38,23 @@ namespace osu.Game.Tests.Visual.Gameplay }, }; - public TestSceneBreakOverlay() + public TestSceneBreakTracker() { - Add(breakOverlay = new TestBreakOverlay(true)); + AddRange(new Drawable[] + { + breakTracker = new TestBreakTracker(), + breakOverlay = new BreakOverlay(true) + { + ProcessCustomClock = false, + } + }); + } + + protected override void Update() + { + base.Update(); + + breakOverlay.Clock = breakTracker.Clock; } [Test] @@ -88,7 +105,7 @@ namespace osu.Game.Tests.Visual.Gameplay loadBreaksStep("multiple breaks", testBreaks); seekAndAssertBreak("seek to break start", testBreaks[1].StartTime, true); - AddAssert("is skipped to break #2", () => breakOverlay.CurrentBreakIndex == 1); + AddAssert("is skipped to break #2", () => breakTracker.CurrentBreakIndex == 1); seekAndAssertBreak("seek to break middle", testBreaks[1].StartTime + testBreaks[1].Duration / 2, true); seekAndAssertBreak("seek to break end", testBreaks[1].EndTime, false); @@ -110,7 +127,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void addShowBreakStep(double seconds) { - AddStep($"show '{seconds}s' break", () => breakOverlay.Breaks = new List + AddStep($"show '{seconds}s' break", () => breakOverlay.Breaks = breakTracker.Breaks = new List { new BreakPeriod { @@ -122,12 +139,12 @@ namespace osu.Game.Tests.Visual.Gameplay private void setClock(bool useManual) { - AddStep($"set {(useManual ? "manual" : "realtime")} clock", () => breakOverlay.SwitchClock(useManual)); + AddStep($"set {(useManual ? "manual" : "realtime")} clock", () => breakTracker.SwitchClock(useManual)); } private void loadBreaksStep(string breakDescription, IReadOnlyList breaks) { - AddStep($"load {breakDescription}", () => breakOverlay.Breaks = breaks); + AddStep($"load {breakDescription}", () => breakOverlay.Breaks = breakTracker.Breaks = breaks); seekAndAssertBreak("seek back to 0", 0, false); } @@ -151,17 +168,18 @@ namespace osu.Game.Tests.Visual.Gameplay private void seekAndAssertBreak(string seekStepDescription, double time, bool shouldBeBreak) { - AddStep(seekStepDescription, () => breakOverlay.ManualClockTime = time); + AddStep(seekStepDescription, () => breakTracker.ManualClockTime = time); AddAssert($"is{(!shouldBeBreak ? " not" : string.Empty)} break time", () => { - breakOverlay.ProgressTime(); - return breakOverlay.IsBreakTime.Value == shouldBeBreak; + breakTracker.ProgressTime(); + return breakTracker.IsBreakTime.Value == shouldBeBreak; }); } - private class TestBreakOverlay : BreakOverlay + private class TestBreakTracker : BreakTracker { - private readonly FramedClock framedManualClock; + public readonly FramedClock FramedManualClock; + private readonly ManualClock manualClock; private IFrameBasedClock originalClock; @@ -173,20 +191,19 @@ namespace osu.Game.Tests.Visual.Gameplay set => manualClock.CurrentTime = value; } - public TestBreakOverlay(bool letterboxing) - : base(letterboxing) + public TestBreakTracker() { - framedManualClock = new FramedClock(manualClock = new ManualClock()); + FramedManualClock = new FramedClock(manualClock = new ManualClock()); ProcessCustomClock = false; } public void ProgressTime() { - framedManualClock.ProcessFrame(); + FramedManualClock.ProcessFrame(); Update(); } - public void SwitchClock(bool setManual) => Clock = setManual ? framedManualClock : originalClock; + public void SwitchClock(bool setManual) => Clock = setManual ? FramedManualClock : originalClock; protected override void LoadComplete() { diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index 4485ce3447..39c1fdad52 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -40,7 +40,7 @@ namespace osu.Game.Graphics.Containers /// /// Whether player is in break time. - /// Must be bound to to allow for dim adjustments in gameplay. + /// Must be bound to to allow for dim adjustments in gameplay. /// public readonly IBindable IsBreakTime = new Bindable(); diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index ff6ed5bf17..5062c92afe 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -72,9 +72,9 @@ namespace osu.Game.Rulesets.UI /// public override Playfield Playfield => playfield.Value; - private Container overlays; + public override Container Overlays { get; } = new Container { RelativeSizeAxes = Axes.Both }; - public override Container Overlays => overlays; + public override Container FrameStableComponents { get; } = new Container { RelativeSizeAxes = Axes.Both }; public override GameplayClock FrameStableClock => frameStabilityContainer.GameplayClock; @@ -187,11 +187,12 @@ namespace osu.Game.Rulesets.UI FrameStablePlayback = FrameStablePlayback, Children = new Drawable[] { + FrameStableComponents, KeyBindingInputManager .WithChild(CreatePlayfieldAdjustmentContainer() .WithChild(Playfield) ), - overlays = new Container { RelativeSizeAxes = Axes.Both } + Overlays, } }, }; @@ -410,6 +411,11 @@ namespace osu.Game.Rulesets.UI /// public abstract Container Overlays { get; } + /// + /// Components to be run potentially multiple times in line with frame-stable gameplay. + /// + public abstract Container FrameStableComponents { get; } + /// /// The frame-stable clock which is being used for playfield display. /// diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index ee8be87352..89f51315f2 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -2,8 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -16,8 +14,6 @@ namespace osu.Game.Screens.Play { public class BreakOverlay : Container { - private readonly ScoreProcessor scoreProcessor; - /// /// The duration of the break overlay fading. /// @@ -37,10 +33,6 @@ namespace osu.Game.Screens.Play { breaks = value; - // reset index in case the new breaks list is smaller than last one - isBreakTime.Value = false; - CurrentBreakIndex = 0; - if (IsLoaded) initializeBreaks(); } @@ -48,27 +40,17 @@ namespace osu.Game.Screens.Play public override bool RemoveCompletedTransforms => false; - /// - /// Whether the gameplay is currently in a break. - /// - public IBindable IsBreakTime => isBreakTime; - - protected int CurrentBreakIndex; - - private readonly BindableBool isBreakTime = new BindableBool(); - private readonly Container remainingTimeAdjustmentBox; private readonly Container remainingTimeBox; private readonly RemainingTimeCounter remainingTimeCounter; - private readonly BreakInfo info; private readonly BreakArrows breakArrows; - private readonly double gameplayStartTime; - public BreakOverlay(bool letterboxing, double gameplayStartTime = 0, ScoreProcessor scoreProcessor = null) + public BreakOverlay(bool letterboxing, ScoreProcessor scoreProcessor = null) { - this.gameplayStartTime = gameplayStartTime; - this.scoreProcessor = scoreProcessor; RelativeSizeAxes = Axes.Both; + + BreakInfo info; + Child = fadeContainer = new Container { Alpha = 0, @@ -119,13 +101,11 @@ namespace osu.Game.Screens.Play } }; - if (scoreProcessor != null) bindProcessor(scoreProcessor); - } - - [BackgroundDependencyLoader(true)] - private void load(GameplayClock clock) - { - if (clock != null) Clock = clock; + if (scoreProcessor != null) + { + info.AccuracyDisplay.Current.BindTo(scoreProcessor.Accuracy); + info.GradeDisplay.Current.BindTo(scoreProcessor.Rank); + } } protected override void LoadComplete() @@ -134,42 +114,6 @@ namespace osu.Game.Screens.Play initializeBreaks(); } - protected override void Update() - { - base.Update(); - updateBreakTimeBindable(); - } - - private void updateBreakTimeBindable() => - isBreakTime.Value = getCurrentBreak()?.HasEffect == true - || Clock.CurrentTime < gameplayStartTime - || scoreProcessor?.HasCompleted == true; - - private BreakPeriod getCurrentBreak() - { - if (breaks?.Count > 0) - { - var time = Clock.CurrentTime; - - if (time > breaks[CurrentBreakIndex].EndTime) - { - while (time > breaks[CurrentBreakIndex].EndTime && CurrentBreakIndex < breaks.Count - 1) - CurrentBreakIndex++; - } - else - { - while (time < breaks[CurrentBreakIndex].StartTime && CurrentBreakIndex > 0) - CurrentBreakIndex--; - } - - var closest = breaks[CurrentBreakIndex]; - - return closest.Contains(time) ? closest : null; - } - - return null; - } - private void initializeBreaks() { FinishTransforms(true); @@ -207,11 +151,5 @@ namespace osu.Game.Screens.Play } } } - - private void bindProcessor(ScoreProcessor processor) - { - info.AccuracyDisplay.Current.BindTo(processor.Accuracy); - info.GradeDisplay.Current.BindTo(processor.Rank); - } } } diff --git a/osu.Game/Screens/Play/BreakTracker.cs b/osu.Game/Screens/Play/BreakTracker.cs new file mode 100644 index 0000000000..64262d52b5 --- /dev/null +++ b/osu.Game/Screens/Play/BreakTracker.cs @@ -0,0 +1,82 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Beatmaps.Timing; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Screens.Play +{ + public class BreakTracker : Component + { + private readonly ScoreProcessor scoreProcessor; + + private readonly double gameplayStartTime; + + /// + /// Whether the gameplay is currently in a break. + /// + public IBindable IsBreakTime => isBreakTime; + + protected int CurrentBreakIndex; + + private readonly BindableBool isBreakTime = new BindableBool(); + + private IReadOnlyList breaks; + + public IReadOnlyList Breaks + { + get => breaks; + set + { + breaks = value; + + // reset index in case the new breaks list is smaller than last one + isBreakTime.Value = false; + CurrentBreakIndex = 0; + } + } + + public BreakTracker(double gameplayStartTime = 0, ScoreProcessor scoreProcessor = null) + { + this.gameplayStartTime = gameplayStartTime; + this.scoreProcessor = scoreProcessor; + } + + protected override void Update() + { + base.Update(); + + isBreakTime.Value = getCurrentBreak()?.HasEffect == true + || Clock.CurrentTime < gameplayStartTime + || scoreProcessor?.HasCompleted == true; + } + + private BreakPeriod getCurrentBreak() + { + if (breaks?.Count > 0) + { + var time = Clock.CurrentTime; + + if (time > breaks[CurrentBreakIndex].EndTime) + { + while (time > breaks[CurrentBreakIndex].EndTime && CurrentBreakIndex < breaks.Count - 1) + CurrentBreakIndex++; + } + else + { + while (time < breaks[CurrentBreakIndex].StartTime && CurrentBreakIndex > 0) + CurrentBreakIndex--; + } + + var closest = breaks[CurrentBreakIndex]; + + return closest.Contains(time) ? closest : null; + } + + return null; + } + } +} diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 3ff47b868c..9ad500039e 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -76,6 +76,8 @@ namespace osu.Game.Screens.Play public BreakOverlay BreakOverlay; + private BreakTracker breakTracker; + protected ScoreProcessor ScoreProcessor { get; private set; } protected HealthProcessor HealthProcessor { get; private set; } @@ -204,7 +206,7 @@ namespace osu.Game.Screens.Play foreach (var mod in Mods.Value.OfType()) mod.ApplyToHealthProcessor(HealthProcessor); - BreakOverlay.IsBreakTime.BindValueChanged(onBreakTimeChanged, true); + breakTracker.IsBreakTime.BindValueChanged(onBreakTimeChanged, true); } private void addUnderlayComponents(Container target) @@ -231,6 +233,18 @@ namespace osu.Game.Screens.Play DrawableRuleset, new ComboEffects(ScoreProcessor) }); + + DrawableRuleset.FrameStableComponents.AddRange(new Drawable[] + { + ScoreProcessor, + HealthProcessor, + breakTracker = new BreakTracker(DrawableRuleset.GameplayStartTime, ScoreProcessor) + { + Breaks = working.Beatmap.Breaks + } + }); + + HealthProcessor.IsBreakTime.BindTo(breakTracker.IsBreakTime); } private void addOverlayComponents(Container target, WorkingBeatmap working) @@ -294,26 +308,13 @@ namespace osu.Game.Screens.Play }, }, failAnimation = new FailAnimation(DrawableRuleset) { OnComplete = onFailComplete, }, - new Container + BreakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks) { - Name = "Frame-stable elements", Clock = DrawableRuleset.FrameStableClock, - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - ScoreProcessor, - HealthProcessor, - BreakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, DrawableRuleset.GameplayStartTime, ScoreProcessor) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Breaks = working.Beatmap.Breaks - }, - } + ProcessCustomClock = false, + Breaks = working.Beatmap.Breaks }, }); - - HealthProcessor.IsBreakTime.BindTo(BreakOverlay.IsBreakTime); } private void onBreakTimeChanged(ValueChangedEvent isBreakTime) @@ -325,7 +326,7 @@ namespace osu.Game.Screens.Play private void updatePauseOnFocusLostState() => HUDOverlay.HoldToQuit.PauseOnFocusLost = PauseOnFocusLost && !DrawableRuleset.HasReplayLoaded.Value - && !BreakOverlay.IsBreakTime.Value; + && !breakTracker.IsBreakTime.Value; private IBeatmap loadPlayableBeatmap() { @@ -547,7 +548,7 @@ namespace osu.Game.Screens.Play PauseOverlay.Hide(); // breaks and time-based conditions may allow instant resume. - if (BreakOverlay.IsBreakTime.Value) + if (breakTracker.IsBreakTime.Value) completeResume(); else DrawableRuleset.RequestResume(completeResume); @@ -581,8 +582,8 @@ namespace osu.Game.Screens.Play Background.BlurAmount.Value = 0; // bind component bindables. - Background.IsBreakTime.BindTo(BreakOverlay.IsBreakTime); - DimmableStoryboard.IsBreakTime.BindTo(BreakOverlay.IsBreakTime); + Background.IsBreakTime.BindTo(breakTracker.IsBreakTime); + DimmableStoryboard.IsBreakTime.BindTo(breakTracker.IsBreakTime); Background.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground); DimmableStoryboard.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground); From 2949e8dc27d66ec99df393b12543fecd8241b471 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Mar 2020 16:58:23 +0900 Subject: [PATCH 49/61] Reduce spread of stacked fruit --- osu.Game.Rulesets.Catch/UI/Catcher.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index e361b29a9d..8fa9c61b6f 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -141,14 +141,14 @@ namespace osu.Game.Rulesets.Catch.UI var ourRadius = fruit.DisplayRadius; float theirRadius = 0; - const float allowance = 6; + const float allowance = 10; while (caughtFruit.Any(f => f.LifetimeEnd == double.MaxValue && Vector2Extensions.Distance(f.Position, fruit.Position) < (ourRadius + (theirRadius = f.DrawSize.X / 2 * f.Scale.X)) / (allowance / 2))) { var diff = (ourRadius + theirRadius) / allowance; - fruit.X += (RNG.NextSingle() - 0.5f) * 2 * diff; + fruit.X += (RNG.NextSingle() - 0.5f) * diff * 2; fruit.Y -= RNG.NextSingle() * diff; } From 8e4896fbbecce162e327d1c4af60dce652985c6b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Mar 2020 17:13:53 +0900 Subject: [PATCH 50/61] Make slider judgements count towards base score / accuracy --- osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs | 2 -- osu.Game.Rulesets.Osu/Objects/SliderTick.cs | 2 -- 2 files changed, 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs index a8fd3764c5..ac6c6905e4 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs @@ -34,8 +34,6 @@ namespace osu.Game.Rulesets.Osu.Objects public class SliderRepeatJudgement : OsuJudgement { - public override bool IsBonus => true; - protected override int NumericResultFor(HitResult result) => result == MaxResult ? 30 : 0; } } diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs index 212a84c04a..22f3f559db 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs @@ -36,8 +36,6 @@ namespace osu.Game.Rulesets.Osu.Objects public class SliderTickJudgement : OsuJudgement { - public override bool IsBonus => true; - protected override int NumericResultFor(HitResult result) => result == MaxResult ? 10 : 0; } } From 6555ab6ede959af102069e1c29ae4d29924d8242 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Mar 2020 17:18:27 +0900 Subject: [PATCH 51/61] Only play slider end sounds if tracking --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 2d5b9d874c..35d58b7111 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -11,6 +11,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Skinning; +using osu.Game.Rulesets.Scoring; using osuTK.Graphics; using osu.Game.Skinning; @@ -193,7 +194,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (userTriggered || Time.Current < slider.EndTime) return; - ApplyResult(r => r.Type = r.Judgement.MaxResult); + ApplyResult(r => r.Type = Ball.Tracking ? r.Judgement.MaxResult : HitResult.Miss); } protected override void UpdateStateTransforms(ArmedState state) From f80efd10c22aefe8678374ce3b14075d619462b8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Mar 2020 19:51:02 +0900 Subject: [PATCH 52/61] Avoid using a miss judgement --- .../Objects/Drawables/DrawableSlider.cs | 10 +++++++++- .../Rulesets/Objects/Drawables/DrawableHitObject.cs | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 35d58b7111..5c7f4a42b3 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -194,7 +194,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (userTriggered || Time.Current < slider.EndTime) return; - ApplyResult(r => r.Type = Ball.Tracking ? r.Judgement.MaxResult : HitResult.Miss); + ApplyResult(r => r.Type = r.Judgement.MaxResult); + } + + public override void PlaySamples() + { + // rather than doing it this way, we should probably attach the sample to the tail circle. + // this can only be done after we stop using LegacyLastTick. + if (TailCircle.Result.Type != HitResult.Miss) + base.PlaySamples(); } protected override void UpdateStateTransforms(ArmedState state) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index aa29e42fac..5b5802fa9d 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -344,7 +344,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// Plays all the hit sounds for this . /// This is invoked automatically when this is hit. /// - public void PlaySamples() => Samples?.Play(); + public virtual void PlaySamples() => Samples?.Play(); protected override void Update() { From 01c9112f82136510ae96dbd918e698ee9623ae81 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Thu, 26 Mar 2020 17:09:22 +0100 Subject: [PATCH 53/61] Add a null check to prevent NRE when playing the "no video" version of a beatmap. --- osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs index 00df388d09..d4dbdf1ea8 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs @@ -55,6 +55,8 @@ namespace osu.Game.Storyboards.Drawables { base.LoadComplete(); + if (videoSprite == null) return; + using (videoSprite.BeginAbsoluteSequence(0)) videoSprite.FadeIn(500); } From f75c0826018a72977b3edca3ae469e93f6a28dee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 Mar 2020 15:50:11 +0900 Subject: [PATCH 54/61] Fix osu!mania replays recording incorrectly when key mod applied --- .../Replays/ManiaReplayFrame.cs | 21 +++++++------------ osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 6 +++--- osu.Game/Screens/Play/Player.cs | 2 +- 3 files changed, 11 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs index b93e372027..8c73c36e99 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Linq; using osu.Game.Beatmaps; using osu.Game.Replays.Legacy; using osu.Game.Rulesets.Mania.Beatmaps; @@ -26,13 +27,7 @@ namespace osu.Game.Rulesets.Mania.Replays public void FromLegacy(LegacyReplayFrame legacyFrame, IBeatmap beatmap, ReplayFrame lastFrame = null) { - // We don't need to fully convert, just create the converter - var converter = new ManiaBeatmapConverter(beatmap, new ManiaRuleset()); - - // NB: Via co-op mod, osu-stable can have two stages with floor(col/2) and ceil(col/2) columns. This will need special handling - // elsewhere in the game if we do choose to support the old co-op mod anyway. For now, assume that there is only one stage. - - var stage = new StageDefinition { Columns = converter.TargetColumns }; + var maniaBeatmap = (ManiaBeatmap)beatmap; var normalAction = ManiaAction.Key1; var specialAction = ManiaAction.Special1; @@ -42,7 +37,7 @@ namespace osu.Game.Rulesets.Mania.Replays while (activeColumns > 0) { - var isSpecial = stage.IsSpecialColumn(counter); + var isSpecial = maniaBeatmap.Stages.First().IsSpecialColumn(counter); if ((activeColumns & 1) > 0) Actions.Add(isSpecial ? specialAction : normalAction); @@ -59,17 +54,15 @@ namespace osu.Game.Rulesets.Mania.Replays public LegacyReplayFrame ToLegacy(IBeatmap beatmap) { + var maniaBeatmap = (ManiaBeatmap)beatmap; + int keys = 0; - var converter = new ManiaBeatmapConverter(beatmap, new ManiaRuleset()); - - var stage = new StageDefinition { Columns = converter.TargetColumns }; - var specialColumns = new List(); - for (int i = 0; i < converter.TargetColumns; i++) + for (int i = 0; i < maniaBeatmap.TotalColumns; i++) { - if (stage.IsSpecialColumn(i)) + if (maniaBeatmap.Stages.First().IsSpecialColumn(i)) specialColumns.Add(i); } diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index 58b64e1b8f..c356dd246d 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -45,9 +45,6 @@ namespace osu.Game.Scoring.Legacy if (workingBeatmap is DummyWorkingBeatmap) throw new BeatmapNotFoundException(); - currentBeatmap = workingBeatmap.Beatmap; - scoreInfo.Beatmap = currentBeatmap.BeatmapInfo; - scoreInfo.User = new User { Username = sr.ReadString() }; // MD5Hash @@ -68,6 +65,9 @@ namespace osu.Game.Scoring.Legacy scoreInfo.Mods = currentRuleset.ConvertFromLegacyMods((LegacyMods)sr.ReadInt32()).ToArray(); + currentBeatmap = workingBeatmap.GetPlayableBeatmap(currentRuleset.RulesetInfo, scoreInfo.Mods); + scoreInfo.Beatmap = currentBeatmap.BeatmapInfo; + /* score.HpGraphString = */ sr.ReadString(); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index dc5bac9fd1..c570f4bf4f 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -657,7 +657,7 @@ namespace osu.Game.Screens.Play using (var stream = new MemoryStream()) { - new LegacyScoreEncoder(score, gameplayBeatmap).Encode(stream); + new LegacyScoreEncoder(score, gameplayBeatmap.PlayableBeatmap).Encode(stream); replayReader = new LegacyByteArrayReader(stream.ToArray(), "replay.osr"); } } From 6788b7f9cd9222c4dffa9fe46792b4a179e053c4 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Fri, 27 Mar 2020 09:43:51 +0100 Subject: [PATCH 55/61] Add test for loading storyboards with missing video file. --- .../Resources/storyboard_no_video.osu | 31 ++++++++++++++++ .../Visual/Gameplay/TestSceneStoryboard.cs | 37 +++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 osu.Game.Tests/Resources/storyboard_no_video.osu diff --git a/osu.Game.Tests/Resources/storyboard_no_video.osu b/osu.Game.Tests/Resources/storyboard_no_video.osu new file mode 100644 index 0000000000..25f1ff6361 --- /dev/null +++ b/osu.Game.Tests/Resources/storyboard_no_video.osu @@ -0,0 +1,31 @@ +osu file format v14 + +[Events] +//Background and Video events +0,0,"BG.jpg",0,0 +Video,0,"video.avi" +//Break Periods +//Storyboard Layer 0 (Background) +//Storyboard Layer 1 (Fail) +//Storyboard Layer 2 (Pass) +//Storyboard Layer 3 (Foreground) +//Storyboard Layer 4 (Overlay) +//Storyboard Sound Samples + +[TimingPoints] +1674,333.333333333333,4,2,1,70,1,0 +1674,-100,4,2,1,70,0,0 +3340,-100,4,2,1,70,0,0 +3507,-100,4,2,1,70,0,0 +3673,-100,4,2,1,70,0,0 + +[Colours] +Combo1 : 240,80,80 +Combo2 : 171,252,203 +Combo3 : 128,128,255 +Combo4 : 249,254,186 + +[HitObjects] +148,303,1674,5,6,3:2:0:0: +378,252,1840,1,0,0:0:0:0: +389,270,2340,5,2,0:1:0:0: diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs index ff8437311e..9f1492a25f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs @@ -9,8 +9,12 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Timing; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Formats; +using osu.Game.IO; using osu.Game.Overlays; +using osu.Game.Storyboards; using osu.Game.Storyboards.Drawables; +using osu.Game.Tests.Resources; using osuTK.Graphics; namespace osu.Game.Tests.Visual.Gameplay @@ -54,7 +58,11 @@ namespace osu.Game.Tests.Visual.Gameplay State = { Value = Visibility.Visible }, } }); + } + [Test] + public void TestStoryboard() + { AddStep("Restart", restart); AddToggleStep("Passing", passing => { @@ -62,6 +70,12 @@ namespace osu.Game.Tests.Visual.Gameplay }); } + [Test] + public void TestStoryboardMissingVideo() + { + AddStep("Load storyboard with missing video", loadStoryboardNoVideo); + } + [BackgroundDependencyLoader] private void load() { @@ -94,5 +108,28 @@ namespace osu.Game.Tests.Visual.Gameplay storyboardContainer.Add(storyboard); decoupledClock.ChangeSource(working.Track); } + + private void loadStoryboardNoVideo() + { + if (storyboard != null) + storyboardContainer.Remove(storyboard); + + var decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = true }; + storyboardContainer.Clock = decoupledClock; + + Storyboard sb; + + using (var str = TestResources.OpenResource("storyboard_no_video.osu")) + using (var bfr = new LineBufferedReader(str)) + { + var decoder = new LegacyStoryboardDecoder(); + sb = decoder.Decode(bfr); + } + + storyboard = sb.CreateDrawable(Beatmap.Value); + + storyboardContainer.Add(storyboard); + decoupledClock.ChangeSource(Beatmap.Value.Track); + } } } From 4106700771f8581ca07846d291aa343c121f0884 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 Mar 2020 20:51:44 +0900 Subject: [PATCH 56/61] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 7e17f9da16..b147fdd05b 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 3894c06994..781c566b5f 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -23,7 +23,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 9cc9792ecf..a2c6106931 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + @@ -79,7 +79,7 @@ - + From 46af4bce32eb176c459514b04aac08a95cf44e35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 27 Mar 2020 19:42:45 +0100 Subject: [PATCH 57/61] Cover regression in autoplay test --- osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs index 8108ce0864..5ee17aeea2 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs @@ -6,6 +6,7 @@ using System.Linq; using osu.Framework.Testing; using osu.Game.Rulesets; using osu.Game.Screens.Play; +using osu.Game.Screens.Play.Break; namespace osu.Game.Tests.Visual.Gameplay { @@ -27,7 +28,8 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("seek to break time", () => Player.GameplayClockContainer.Seek(Player.ChildrenOfType().First().Breaks.First().StartTime)); AddUntilStep("wait for seek to complete", () => Player.HUDOverlay.Progress.ReferenceClock.CurrentTime >= Player.BreakOverlay.Breaks.First().StartTime); - AddAssert("test keys not counting", () => !Player.HUDOverlay.KeyCounter.IsCounting); + AddAssert("keys not counting", () => !Player.HUDOverlay.KeyCounter.IsCounting); + AddAssert("overlay displays 100% accuracy", () => Player.BreakOverlay.ChildrenOfType().Single().AccuracyDisplay.Current.Value == 1); AddStep("rewind", () => Player.GameplayClockContainer.Seek(-80000)); AddUntilStep("key counter reset", () => Player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0)); } From adc759771ff73e3b3b023b624348cf9655c9f017 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 27 Mar 2020 19:47:42 +0100 Subject: [PATCH 58/61] Hook up score processor in player --- osu.Game/Screens/Play/Player.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 118cea324c..8693035103 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -308,7 +308,7 @@ namespace osu.Game.Screens.Play }, }, failAnimation = new FailAnimation(DrawableRuleset) { OnComplete = onFailComplete, }, - BreakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks) + BreakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor) { Clock = DrawableRuleset.FrameStableClock, ProcessCustomClock = false, From 3a3bfe9a5ea14477da9fdc67d42b9f6fe16598e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 27 Mar 2020 21:19:49 +0100 Subject: [PATCH 59/61] Reorder children to fix pause overlay z-order --- osu.Game/Screens/Play/Player.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 8693035103..63ec3b0d2d 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -251,6 +251,12 @@ namespace osu.Game.Screens.Play { target.AddRange(new[] { + BreakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor) + { + Clock = DrawableRuleset.FrameStableClock, + ProcessCustomClock = false, + Breaks = working.Beatmap.Breaks + }, // display the cursor above some HUD elements. DrawableRuleset.Cursor?.CreateProxy() ?? new Container(), DrawableRuleset.ResumeOverlay?.CreateProxy() ?? new Container(), @@ -308,12 +314,6 @@ namespace osu.Game.Screens.Play }, }, failAnimation = new FailAnimation(DrawableRuleset) { OnComplete = onFailComplete, }, - BreakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor) - { - Clock = DrawableRuleset.FrameStableClock, - ProcessCustomClock = false, - Breaks = working.Beatmap.Breaks - }, }); } From 15fb1a099e4d96e725a6c46072fdf6b782bfd529 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 28 Mar 2020 00:42:51 +0100 Subject: [PATCH 60/61] Modify assert to avoid false failures In headless tests it was possible for TestInstantLoad() to erroneously fail. There were two scenarios in which LoadingSpinner could be null: 1. If the test runner was quick enough, the assert could end up running even before Loader.OnEntering() had even had a chance to, meaning that the spinner was never even actually assigned to or instantiated at that point in time. 2. Even if Loader.OnEntering() had managed to run, there was also a possibility that the spinner itself wasn't loaded at the point of checking the assertion. As the spinner is accessed through ChildrenOfType(), which only checks InternalChildren and ignores all currently-loading drawables, it would therefore return null. As null != 0, both of these cases would actually fail the test (this is best seen running headless, preferably with a [Repeat] attribute attached). To resolve, allow the spinner to be null at the point of asserting and duplicate the assertion step at the end. This weakens the test, as case (1) should probably be waited for and case (2) could be solved with exposition as protected in the base, but when attempting to wait for the loader itself to be loaded there were also cases where the appropriate until step would take so much time that the spinner would actually become visible in line with the delayed display logic, so this is a best-effort attempt to address both points without radical changes. --- osu.Game.Tests/Visual/Menus/TestSceneLoader.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneLoader.cs b/osu.Game.Tests/Visual/Menus/TestSceneLoader.cs index b3064ba9be..c44363d9ea 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneLoader.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneLoader.cs @@ -36,8 +36,6 @@ namespace osu.Game.Tests.Visual.Menus [Test] public void TestInstantLoad() { - // visual only, very impossible to test this using asserts. - AddStep("load immediately", () => { loader = new TestLoader(); @@ -46,12 +44,17 @@ namespace osu.Game.Tests.Visual.Menus LoadScreen(loader); }); - AddAssert("spinner did not display", () => loader.LoadingSpinner?.Alpha == 0); + spinnerNotPresentOrHidden(); AddUntilStep("loaded", () => loader.ScreenLoaded); AddUntilStep("not current", () => !loader.IsCurrentScreen()); + + spinnerNotPresentOrHidden(); } + private void spinnerNotPresentOrHidden() => + AddAssert("spinner did not display", () => loader.LoadingSpinner == null || loader.LoadingSpinner.Alpha == 0); + [Test] public void TestDelayedLoad() { From a317ef65b8b5d301689e526bec8efd6b453743bb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 28 Mar 2020 12:18:28 +0900 Subject: [PATCH 61/61] Remove default for argument --- osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs | 2 +- osu.Game/Screens/Play/BreakOverlay.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs index d46b4ea289..ff25e609c1 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakTracker.cs @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddRange(new Drawable[] { breakTracker = new TestBreakTracker(), - breakOverlay = new BreakOverlay(true) + breakOverlay = new BreakOverlay(true, null) { ProcessCustomClock = false, } diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index 89f51315f2..c978f4e96d 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -45,7 +45,7 @@ namespace osu.Game.Screens.Play private readonly RemainingTimeCounter remainingTimeCounter; private readonly BreakArrows breakArrows; - public BreakOverlay(bool letterboxing, ScoreProcessor scoreProcessor = null) + public BreakOverlay(bool letterboxing, ScoreProcessor scoreProcessor) { RelativeSizeAxes = Axes.Both;