- Closes https://github.com/ppy/osu/issues/37884
- Closes https://github.com/ppy/osu/pull/37890
Due to lack of population of `Storyboard.Beatmap` and
`Storyboard.BeatmapInfo` post-decoding, `LegacyBeatmapExporter` would
completely drop background specifications on exported beatmap packages.
This affects both direct legacy export to file (`.osz`) as well as
beatmap submission.
I will not pretend that the API here is optimal but I do not see very
easy opportunities to curtail misuse. Storyboards can be treated as
either parts of a beatmap or standalone entities, and if a requirement
is added to forcibly provide a beatmap and its info when encoding out a
storyboard, I also foresee a requirement to bypass this later when
design mode is implemented, which would be a return to square one.
There is likely room for cleanup around `Storyboard` to maybe make this
nicer (remove passing of both `Beatmap` and `BeatmapInfo` and just pass
`Beatmap` instead, maybe shuffle some properties from `Beatmap` to
`Storyboard` to remove the requirement of having to bolt the beatmap on
to begin with). I leave voicing opinions on that, and how soon that
should be done, to reviewers. My primary intent at this time is to
hotfix a major issue in a released build.
The external editing feature is not involved in this bug and any
attempts to claim so are misdirections.
- Closes https://github.com/ppy/osu/issues/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
(a66dc406f498e35d4e0c8f2a462e946a9a1aeccc). 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`, b60698a95c4de1bfeb36fbb159fd5a6028920832 adds the
required storage to be able to tell which storyboard element lives
where, so that it can be decoded properly later.
- In c9d3e04a4135886b5b0943c85f3cc6f4fe99c84c, 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, dd1c4e43dc51154cd67860f096712f8b4f229661 removes
`Beatmap.UnhandledEventLines` as no longer required.
- 26ac417ed98a8937c42e5f52c4e15ef065a48902 adds tests. They are mostly
handwritten to ensure basic encode-decode roundtripping. Using existing
storyboards is difficult, see "Known issues" section as to why.
- 5cc542366db7caac38eb0729260d884905a2c0d5 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.
The compatibility export was creating .osu files with CRLF line endings
on Windows and LF line endings on Linux/macOS, because StreamWriter
defaults to Environment.NewLine.
This caused the server to detect spurious file changes when a mapper
alternated between platforms, leading to unnecessary wipes of local
scores and noise in the beatmap update history.
Fix by explicitly setting NewLine = "\r\n" on the StreamWriter, ensuring
CRLF is always used regardless of platform.
Closes#36846
---------
Co-authored-by: Dean Herbert <pe@ppy.sh>
A few facts of life:
- Guest difficulties are at this point a staple of mapping.
- People are very much used to flinging `.osu`s around (because there's
no better alternative).
- Currently there are two ways to get an `.osu` out of lazer. You can:
- Export the beatmap as "compatibility" to an `.osz`, then
transmogrify the `.osz` to a `.zip`, then extract the `.zip`, then
pluck out the `.osu`. This is the "correct" way to make sure stable
works, but is also stupidly arcane.
- Use "edit externally" to mount the beatmap files to disk, then
copy-paste out the `.osu`. This is the *wrong* way to make sure
stable works, because the mounting process exposes the raw "for
editing" format with features stable doesn't support, but it the
actual easy one.
- Reports about guest difficulties exported from lazer "working wrong on
stable" are prevalent. Probably mostly because of the preceding point.
What this PR does is introduce a *third* method to export an `.osu`,
which is designed to be both the easiest one yet *and* correct. I am
hoping this will curb the complaints until support for direct submission
of guest difficulties is added - which I still hope to see, but it will
be a significant effort *client-side* (the server side has been ready
for years now).
And yes, you will notice that much of the code added in
`LegacyBeatmapExporter` related to manipulation of the path is
copy-pasted from `LegacyExporter`. I don't care enough to invent
protected / abstract / whatever else OOP faff for something that may not
survive review and is mostly a weird semi-temporary wart.
Done for two reasons:
- During review it was requested for the logic to be moved out of
`BezierConverter` as `BezierConverter` was intended to produce
"lazer style" sliders with per-control-point curve types,
as a future usability / code layering concern.
- It is also relevant for encode-decode stability. With how the logic
was structured between the Bezier converter and the legacy beatmap
encoder, the encoder would leave behind per-control-point Bezier curve
specs that stable ignored, but subsequent encodes and decodes in lazer
would end up multiplying the doubled-up control points ad nauseam.
Instead, it is sufficient to only specify the curve type for the
head control point as Bezier, not specify any further curve types
later on, and instead just keep the double-up-control-point for new
implicit segment logic which is enough to make stable cooperate
(and also as close to outputting the slider exactly as stable would
have produced it as we've ever been)