Merge 4 upstream ppy/osu commits into fork - #344
Merged
Conversation
Requires ppy/osu-resources#424. Replaces the editor toolbox icons for taiko, mania, and catch rulesets with new designs, courtesy of [Adarin](https://osu.ppy.sh/users/118360). | taiko | catch | mania | |--------|--------|--------| | <img width="369" height="401" alt="image" src="https://github.com/user-attachments/assets/25dc6259-02b4-4a23-9a80-529a366c270e" /> | <img width="369" height="401" alt="image" src="https://github.com/user-attachments/assets/4bf67bf9-8a45-4075-8d33-f0501cdb08cb" /> | <img width="369" height="317" alt="image" src="https://github.com/user-attachments/assets/d130a97e-c082-4b01-a88a-d3209c05e33b" /> | --------- Co-authored-by: Dean Herbert <pe@ppy.sh>
- Closes ppy#37757 Commit-by-commit reading is recommended. Commits will be split to PRs on request but I consider this to be the minimal viable functional increment. ## Done - This adds a first version of a full storyboard encoder (a66dc40). I expect there to be hiccups due to weird corners of the `.osb` format; this is only intended to be somewhat correct as a start to build upon. Storyboarders are asked to file issues as necessary. - Due to the fact that storyboard definitions can reside both in the `.osu` and the `.osb`, b60698a adds the required storage to be able to tell which storyboard element lives where, so that it can be decoded properly later. - In c9d3e04, the storyboard decoder is weaved into the beatmap decoder to handle the `.osu` part of the storyboard, via the `LegacyStoryboardEncoder.Encode{General,Events}ToBeatmap()` methods. For `.osb`s, `LegacyStoryboardEncoder.EncodeStandaloneStoryboard()` is intended, but for now is not used outside tests. - Because of the above, dd1c4e4 removes `Beatmap.UnhandledEventLines` as no longer required. - 26ac417 adds tests. They are mostly handwritten to ensure basic encode-decode roundtripping. Using existing storyboards is difficult, see "Known issues" section as to why. - 5cc5423 fixes a bug in the storyboard decoder where the trigger group number was not properly negated on decode (see inline comment reference to relevant stable code). ## Known issues - Any and all variables in the `[Variables]` section are inlined into their usages by `LegacyStoryboardDecoder`, and as such `LegacyStoryboardEncoder` will end up inlining them and discarding the `[Variables]` section. As far as I can tell stable will also do this. - `LegacyStoryboardDecoder` splits all `M` (move) commands into `MX`/`MY` commands. Therefore, `LegacyStoryboardEncoder` will write out things in the same split way. I did not put in effort to attempt to reconcile this, for reasons of part laziness, part not wanting to bloat this already-large diff. - Ordering of storyboard samples on decode may not match the order on decode. I'm crossing fingers this doesn't matter.
Custom rulesets may have different conditions for replacement, and may not even use the `IHasColumn` interface.
…k changes Co-authored-by: winnerspiros <1675249+winnerspiros@users.noreply.github.com>
Copilot created this pull request from a session on behalf of
winnerspiros
May 21, 2026 05:35
View session
Agent-Logs-Url: https://github.com/winnerspiros/osu/sessions/bf366aca-3380-4f61-ba73-8b02958ae0ce Co-authored-by: winnerspiros <1675249+winnerspiros@users.noreply.github.com>
winnerspiros
approved these changes
May 21, 2026
…toryboardEncoderTest Agent-Logs-Url: https://github.com/winnerspiros/osu/sessions/52588c74-d9fd-4401-b050-cee29465a0ed Co-authored-by: winnerspiros <1675249+winnerspiros@users.noreply.github.com>
winnerspiros
approved these changes
May 21, 2026
There was a problem hiding this comment.
Pull request overview
This PR merges upstream changes that expand legacy beatmap encoding/decoding to properly handle storyboards (including distinguishing elements sourced from the .osu vs .osb), and threads storyboard data through editor save/export flows. It also refactors placement replacement logic to be ruleset-extensible and updates some composition tool display names.
Changes:
- Add storyboard source tracking (
StoryboardElementSource) and update storyboard element/command APIs accordingly. - Implement legacy storyboard encoding/decoding and integrate it into legacy beatmap encode/export and editor save flows.
- Make placement replacement behaviour overridable per-ruleset, and align editor toolbox labels with new display strings.
Reviewed changes
Copilot reviewed 59 out of 59 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| osu.Game/Tests/Visual/LegacyReplayPlaybackTestScene.cs | Update LegacyBeatmapEncoder ctor usage to include storyboard param. |
| osu.Game/Tests/Visual/EditorTestScene.cs | Update BeatmapManager.Save() override signature to include storyboard. |
| osu.Game/Tests/Beatmaps/TestBeatmap.cs | Remove UnhandledEventLines propagation. |
| osu.Game/Storyboards/StoryboardVideo.cs | Add Source plumbing via new ctor signature. |
| osu.Game/Storyboards/StoryboardSprite.cs | Add Source, expose looping/trigger groups, update ctor signature. |
| osu.Game/Storyboards/StoryboardSample.cs | Add Source and update ctor signature. |
| osu.Game/Storyboards/StoryboardAnimation.cs | Add Source plumbing via new ctor signature. |
| osu.Game/Storyboards/IStoryboardElement.cs | Add Source contract + StoryboardElementSource enum. |
| osu.Game/Storyboards/Commands/StoryboardLoopingGroup.cs | Mark looping commands to allow unwrapping during encoding. |
| osu.Game/Storyboards/Commands/IStoryboardCommand.cs | Add Easing to interface; introduce IStoryboardLoopingCommand. |
| osu.Game/Skinning/LegacySkinDecoder.cs | Update decoder signature to include isPrimaryStream. |
| osu.Game/Skinning/LegacyManiaSkinDecoder.cs | Update decoder signature to include isPrimaryStream. |
| osu.Game/Screens/Edit/Setup/ResourcesSection.cs | Pass storyboard through BeatmapManager.Save(). |
| osu.Game/Screens/Edit/Setup/MetadataSection.cs | Pass storyboard through BeatmapManager.Save(). |
| osu.Game/Screens/Edit/EditorBeatmap.cs | Store storyboard on editor beatmap and remove UnhandledEventLines. |
| osu.Game/Screens/Edit/Editor.cs | Thread storyboard into EditorBeatmap creation and save. |
| osu.Game/Screens/Edit/BeatmapEditorChangeHandler.cs | Include storyboard when encoding editor state for undo/redo. |
| osu.Game/Rulesets/Edit/HitObjectPlacementBlueprint.cs | Replace static replacement method with virtual instance method. |
| osu.Game/Rulesets/Edit/HitObjectComposer.cs | Use placement blueprint’s replacement logic during placement commit. |
| osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs | Remove UnhandledEventLines passthrough from wrapper beatmap. |
| osu.Game/Database/LegacyBeatmapExporter.cs | Decode storyboard and pass it into legacy beatmap encoding on export. |
| osu.Game/Beatmaps/IBeatmap.cs | Remove UnhandledEventLines from public beatmap contract. |
| osu.Game/Beatmaps/Formats/LegacyStoryboardEncoder.cs | New encoder for writing legacy storyboard events/commands. |
| osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs | Track element source based on primary vs secondary stream. |
| osu.Game/Beatmaps/Formats/LegacyDecoder.cs | Add isPrimaryStream flow into parsing pipeline. |
| osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | Accept storyboard and emit storyboard-related general/events output. |
| osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | Remove unhandled event line collection; update parse signatures. |
| osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs | Update decoder signature to include isPrimaryStream. |
| osu.Game/Beatmaps/Formats/Decoder.cs | Plumb isPrimaryStream flag into decoding pipeline. |
| osu.Game/Beatmaps/BeatmapManager.cs | Extend Save() signature to include storyboard and pass to encoder. |
| osu.Game/Beatmaps/BeatmapConverter.cs | Remove UnhandledEventLines transfer. |
| osu.Game/Beatmaps/Beatmap.cs | Remove UnhandledEventLines storage from Beatmap implementation. |
| osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs | Update storyboard element construction to include Source. |
| osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs | Update storyboard element construction to include Source. |
| osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithIntro.cs | Update storyboard element construction to include Source. |
| osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs | Update storyboard sample construction to include Source. |
| osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardCommands.cs | Update storyboard element construction to include Source. |
| osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs | Update storyboard element construction to include Source. |
| osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs | Update storyboard sprite/video construction to include Source. |
| osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs | Update toolbox label string matching (“Hit circle”). |
| osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs | Implement new Source property for test storyboard element. |
| osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs | Update storyboard sample construction to include Source. |
| osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs | Update LegacyBeatmapEncoder ctor usage to include storyboard param. |
| osu.Game.Tests/Editing/Checks/CheckVideoUsageTest.cs | Update storyboard video construction to include Source. |
| osu.Game.Tests/Editing/Checks/CheckVideoResolutionTest.cs | Update storyboard video construction to include Source. |
| osu.Game.Tests/Editing/Checks/CheckUnusedAudioAtEndTest.cs | Update storyboard element/video construction to include Source. |
| osu.Game.Tests/Editing/Checks/CheckInconsistentSettingsTest.cs | Update storyboard sprite construction to include Source. |
| osu.Game.Tests/Editing/Checks/CheckAudioInVideoTest.cs | Update storyboard video construction to include Source. |
| osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardEncoderTest.cs | New tests for legacy storyboard encode/decode behaviour. |
| osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs | Update tests to thread storyboard and validate rough layer stability. |
| osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs | Update tool display name string. |
| osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayerScroller.cs | Update storyboard sprite construction to include Source. |
| osu.Game.Rulesets.Osu/Edit/HitCircleCompositionTool.cs | Update tool display name string. |
| osu.Game.Rulesets.Osu.Tests/TestSceneLegacyHitPolicy.cs | Update LegacyBeatmapEncoder ctor usage to include storyboard param. |
| osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderDrawing.cs | Update toolbox label string matching (“Hit circle”). |
| osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs | Override replacement logic to include column matching. |
| osu.Game.Rulesets.Mania.Tests/ManiaLegacyBeatmapEncoderTest.cs | Update test record usage to new BeatmapComponents shape. |
| osu.Game.Rulesets.Catch/Edit/JuiceStreamCompositionTool.cs | Update tool display name string. |
| osu.Game.Rulesets.Catch/Edit/BananaShowerCompositionTool.cs | Update tool display name string. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| newBeatmap.BeatmapInfo.BeatmapSet = targetBeatmapSet; | ||
|
|
||
| save(newBeatmap.BeatmapInfo, newBeatmap, beatmapSkin, transferCollections: false); | ||
| save(newBeatmap.BeatmapInfo, newBeatmap, beatmapSkin, new Storyboard(), transferCollections: false); |
Comment on lines
+70
to
+76
| using var storyboardStream = base.GetFileContents(model, file); | ||
|
|
||
| if (storyboardStream == null) | ||
| return null; | ||
|
|
||
| using var storyboardStreamReader = new LineBufferedReader(storyboardStream); | ||
| var beatmapStoryboard = new LegacyStoryboardDecoder().Decode(storyboardStreamReader); |
Comment on lines
+54
to
+61
| if (target == StoryboardElementSource.Beatmap) | ||
| { | ||
| // https://github.com/peppy/osu-stable-reference/blob/c34a74fb61c17c5667486a12548485d1f03baa2e/osu!/GameplayElements/HitObjectManager_LoadSave.cs#L1499 | ||
| // TODO: handle nonzero background offset (https://github.com/ppy/osu/issues/14238) | ||
| writer.WriteLine(string.Format(CultureInfo.InvariantCulture, | ||
| @"{0},{1},""{2}"",{3},{4}", | ||
| (int)LegacyEventType.Background, 0, storyboard.BeatmapInfo.Metadata.BackgroundFile, 0, 0)); | ||
| } |
Comment on lines
+232
to
+240
| case StoryboardRotationCommand rotation: | ||
| typeAcronym = @"R"; | ||
| details = rotation.StartValue == rotation.EndValue | ||
| ? rotation.StartValue.ToString(CultureInfo.InvariantCulture) | ||
| : string.Format(CultureInfo.InvariantCulture, | ||
| @"{0},{1}", | ||
| float.DegreesToRadians(rotation.StartValue), | ||
| float.DegreesToRadians(rotation.EndValue)); | ||
| break; |
Comment on lines
+38
to
+41
| /// <param name="storyboard"> | ||
| /// The combined storyboard, loaded from both the <c>.osu</c> and the <c>.osz</c>. | ||
| /// Only elements from the <c>.osu</c> (marked via <see cref="StoryboardElementSource.Beatmap"/>) will be encoded to the beatmap. | ||
| /// </param> |
Comment on lines
163
to
180
| private void handleEvents(TextWriter writer) | ||
| { | ||
| writer.WriteLine("[Events]"); | ||
|
|
||
| if (!string.IsNullOrEmpty(beatmap.BeatmapInfo.Metadata.BackgroundFile)) | ||
| writer.WriteLine(FormattableString.Invariant($"{(int)LegacyEventType.Background},0,\"{beatmap.BeatmapInfo.Metadata.BackgroundFile}\",0,0")); | ||
| if (storyboardEncoder != null) | ||
| { | ||
| storyboardEncoder.EncodeEventsToBeatmap(writer); | ||
| } | ||
| else | ||
| { | ||
| if (!string.IsNullOrEmpty(beatmap.BeatmapInfo.Metadata.BackgroundFile)) | ||
| writer.WriteLine(FormattableString.Invariant($"{(int)LegacyEventType.Background},0,\"{beatmap.BeatmapInfo.Metadata.BackgroundFile}\",0,0")); | ||
| } | ||
|
|
||
| writer.WriteLine("// Break Periods"); | ||
| foreach (var b in beatmap.Breaks) | ||
| writer.WriteLine(FormattableString.Invariant($"{(int)LegacyEventType.Break},{b.StartTime},{b.EndTime}")); | ||
|
|
||
| foreach (string l in beatmap.UnhandledEventLines) | ||
| writer.WriteLine(l); | ||
| } |
Comment on lines
+296
to
+306
| public static BeatmapComponents DecodeFromLegacy(Stream stream, IResourceStore<byte[]> beatmapsResourceStore, string name, int version = LegacyDecoder<Beatmap>.LATEST_VERSION) | ||
| { | ||
| using (var reader = new LineBufferedReader(stream)) | ||
| { | ||
| var beatmap = new LegacyBeatmapDecoder(version) { ApplyOffsets = false }.Decode(reader); | ||
| var beatmapSkin = new TestLegacySkin(beatmapsResourceStore, name); | ||
| stream.Seek(0, SeekOrigin.Begin); | ||
| beatmapSkin.Configuration = new LegacySkinDecoder().Decode(reader); | ||
| return (convert(beatmap), beatmapSkin); | ||
| stream.Seek(0, SeekOrigin.Begin); | ||
| var storyboard = new LegacyStoryboardDecoder().Decode(reader); | ||
| return new BeatmapComponents(convert(beatmap), beatmapSkin, storyboard); |
Comment on lines
+369
to
+374
| using var beatmapReader = new LineBufferedReader(encoded.Beatmap, leaveOpen: true); | ||
| var beatmap = new LegacyBeatmapDecoder().Decode(beatmapReader); | ||
|
|
||
| encoded.Beatmap.Position = 0; | ||
| using var storyboardReader = new LineBufferedReader(encoded.Storyboard, leaveOpen: true); | ||
| var storyboard = new LegacyStoryboardDecoder().Decode(beatmapReader, storyboardReader); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
LegacyStoryboardEncoderTest.cslines 13-14 useusing osuTK;andusing osuTK.Graphics;which no longer existusing osuTK;withusing System.Numerics;, removedusing osuTK.Graphics;, renamedColor4.White/Aquamarine→Colour4.White/Aquamarine