Skip to content

Merge 4 upstream ppy/osu commits into fork - #344

Merged
winnerspiros merged 7 commits into
masterfrom
copilot/merge-4-prs-prioritize-our-fork
May 21, 2026
Merged

Merge 4 upstream ppy/osu commits into fork#344
winnerspiros merged 7 commits into
masterfrom
copilot/merge-4-prs-prioritize-our-fork

Conversation

Copilot AI commented May 21, 2026

Copy link
Copy Markdown
  • Identified CI error: LegacyStoryboardEncoderTest.cs lines 13-14 use using osuTK; and using osuTK.Graphics; which no longer exist
  • Fix: replaced using osuTK; with using System.Numerics;, removed using osuTK.Graphics;, renamed Color4.White/AquamarineColour4.White/Aquamarine

LiquidPL and others added 5 commits May 20, 2026 04:24
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>
@gitar-bot

gitar-bot Bot commented May 21, 2026

Copy link
Copy Markdown

Gitar is working

Gitar

Copilot AI requested a review from winnerspiros May 21, 2026 05:42
…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>
Copilot AI requested a review from winnerspiros May 21, 2026 06:02
@winnerspiros
winnerspiros marked this pull request as ready for review May 21, 2026 06:45
Copilot AI review requested due to automatic review settings May 21, 2026 06:45
@winnerspiros
winnerspiros merged commit 4f913fd into master May 21, 2026
14 of 22 checks passed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants