1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-15 01:52:55 +08:00

Merge branch 'master' into tournament-score-diff

This commit is contained in:
Dean Herbert 2023-07-23 17:08:39 +09:00 committed by GitHub
commit 15012b6a25
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 158 additions and 11 deletions

View File

@ -29,7 +29,7 @@ namespace osu.Game.Beatmaps
/// </summary> /// </summary>
public class BeatmapImporter : RealmArchiveModelImporter<BeatmapSetInfo> public class BeatmapImporter : RealmArchiveModelImporter<BeatmapSetInfo>
{ {
public override IEnumerable<string> HandledExtensions => new[] { ".osz" }; public override IEnumerable<string> HandledExtensions => new[] { ".osz", ".olz" };
protected override string[] HashableFileTypes => new[] { ".osu" }; protected override string[] HashableFileTypes => new[] { ".osu" };
@ -145,7 +145,7 @@ namespace osu.Game.Beatmaps
} }
} }
protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path).ToLowerInvariant() == ".osz"; protected override bool ShouldDeleteArchive(string path) => HandledExtensions.Contains(Path.GetExtension(path).ToLowerInvariant());
protected override void Populate(BeatmapSetInfo beatmapSet, ArchiveReader? archive, Realm realm, CancellationToken cancellationToken = default) protected override void Populate(BeatmapSetInfo beatmapSet, ArchiveReader? archive, Realm realm, CancellationToken cancellationToken = default)
{ {

View File

@ -40,7 +40,9 @@ namespace osu.Game.Beatmaps
private readonly WorkingBeatmapCache workingBeatmapCache; private readonly WorkingBeatmapCache workingBeatmapCache;
private readonly LegacyBeatmapExporter beatmapExporter; private readonly BeatmapExporter beatmapExporter;
private readonly LegacyBeatmapExporter legacyBeatmapExporter;
public ProcessBeatmapDelegate? ProcessBeatmap { private get; set; } public ProcessBeatmapDelegate? ProcessBeatmap { private get; set; }
@ -77,7 +79,12 @@ namespace osu.Game.Beatmaps
workingBeatmapCache = CreateWorkingBeatmapCache(audioManager, gameResources, userResources, defaultBeatmap, host); workingBeatmapCache = CreateWorkingBeatmapCache(audioManager, gameResources, userResources, defaultBeatmap, host);
beatmapExporter = new LegacyBeatmapExporter(storage) beatmapExporter = new BeatmapExporter(storage)
{
PostNotification = obj => PostNotification?.Invoke(obj)
};
legacyBeatmapExporter = new LegacyBeatmapExporter(storage)
{ {
PostNotification = obj => PostNotification?.Invoke(obj) PostNotification = obj => PostNotification?.Invoke(obj)
}; };
@ -402,6 +409,8 @@ namespace osu.Game.Beatmaps
public Task Export(BeatmapSetInfo beatmap) => beatmapExporter.ExportAsync(beatmap.ToLive(Realm)); public Task Export(BeatmapSetInfo beatmap) => beatmapExporter.ExportAsync(beatmap.ToLive(Realm));
public Task ExportLegacy(BeatmapSetInfo beatmap) => legacyBeatmapExporter.ExportAsync(beatmap.ToLive(Realm));
private void updateHashAndMarkDirty(BeatmapSetInfo setInfo) private void updateHashAndMarkDirty(BeatmapSetInfo setInfo)
{ {
setInfo.Hash = beatmapImporter.ComputeHash(setInfo); setInfo.Hash = beatmapImporter.ComputeHash(setInfo);

View File

@ -0,0 +1,22 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Platform;
using osu.Game.Beatmaps;
namespace osu.Game.Database
{
/// <summary>
/// Exporter for beatmap archives.
/// This is not for legacy purposes and works for lazer only.
/// </summary>
public class BeatmapExporter : LegacyArchiveExporter<BeatmapSetInfo>
{
public BeatmapExporter(Storage storage)
: base(storage)
{
}
protected override string FileExtension => @".olz";
}
}

View File

@ -39,7 +39,7 @@ namespace osu.Game.Database
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
using (var stream = UserFileStorage.GetStream(file.File.GetStoragePath())) using (var stream = GetFileContents(model, file))
{ {
if (stream == null) if (stream == null)
{ {
@ -65,5 +65,7 @@ namespace osu.Game.Database
} }
} }
} }
protected virtual Stream? GetFileContents(TModel model, INamedFileUsage file) => UserFileStorage.GetStream(file.File.GetStoragePath());
} }
} }

View File

@ -1,11 +1,25 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.IO;
using System.Linq;
using System.Text;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Formats;
using osu.Game.IO;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Skinning;
using osuTK;
namespace osu.Game.Database namespace osu.Game.Database
{ {
/// <summary>
/// Exporter for osu!stable legacy beatmap archives.
/// Converts all beatmaps in the set to legacy format and exports it as a legacy package.
/// </summary>
public class LegacyBeatmapExporter : LegacyArchiveExporter<BeatmapSetInfo> public class LegacyBeatmapExporter : LegacyArchiveExporter<BeatmapSetInfo>
{ {
public LegacyBeatmapExporter(Storage storage) public LegacyBeatmapExporter(Storage storage)
@ -13,6 +27,72 @@ namespace osu.Game.Database
{ {
} }
protected override Stream? GetFileContents(BeatmapSetInfo model, INamedFileUsage file)
{
bool isBeatmap = model.Beatmaps.Any(o => o.Hash == file.File.Hash);
if (!isBeatmap)
return base.GetFileContents(model, file);
// Read the beatmap contents and skin
using var contentStream = base.GetFileContents(model, file);
if (contentStream == null)
return null;
using var contentStreamReader = new LineBufferedReader(contentStream);
var beatmapContent = new LegacyBeatmapDecoder().Decode(contentStreamReader);
using var skinStream = base.GetFileContents(model, file);
if (skinStream == null)
return null;
using var skinStreamReader = new LineBufferedReader(skinStream);
var beatmapSkin = new LegacySkin(new SkinInfo(), null!)
{
Configuration = new LegacySkinDecoder().Decode(skinStreamReader)
};
// Convert beatmap elements to be compatible with legacy format
// So we truncate time and position values to integers, and convert paths with multiple segments to bezier curves
foreach (var controlPoint in beatmapContent.ControlPointInfo.AllControlPoints)
controlPoint.Time = Math.Floor(controlPoint.Time);
foreach (var hitObject in beatmapContent.HitObjects)
{
// Truncate end time before truncating start time because end time is dependent on start time
if (hitObject is IHasDuration hasDuration && hitObject is not IHasPath)
hasDuration.Duration = Math.Floor(hasDuration.EndTime) - Math.Floor(hitObject.StartTime);
hitObject.StartTime = Math.Floor(hitObject.StartTime);
if (hitObject is not IHasPath hasPath || BezierConverter.CountSegments(hasPath.Path.ControlPoints) <= 1) continue;
var newControlPoints = BezierConverter.ConvertToModernBezier(hasPath.Path.ControlPoints);
// Truncate control points to integer positions
foreach (var pathControlPoint in newControlPoints)
{
pathControlPoint.Position = new Vector2(
(float)Math.Floor(pathControlPoint.Position.X),
(float)Math.Floor(pathControlPoint.Position.Y));
}
hasPath.Path.ControlPoints.Clear();
hasPath.Path.ControlPoints.AddRange(newControlPoints);
}
// Encode to legacy format
var stream = new MemoryStream();
using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true))
new LegacyBeatmapEncoder(beatmapContent, beatmapSkin).Encode(sw);
stream.Seek(0, SeekOrigin.Begin);
return stream;
}
protected override string FileExtension => @".osz"; protected override string FileExtension => @".osz";
} }
} }

View File

@ -39,6 +39,11 @@ namespace osu.Game.Localisation
/// </summary> /// </summary>
public static LocalisableString Default => new TranslatableString(getKey(@"default"), @"Default"); public static LocalisableString Default => new TranslatableString(getKey(@"default"), @"Default");
/// <summary>
/// "Export"
/// </summary>
public static LocalisableString Export => new TranslatableString(getKey(@"export"), @"Export");
/// <summary> /// <summary>
/// "Width" /// "Width"
/// </summary> /// </summary>

View File

@ -35,9 +35,14 @@ namespace osu.Game.Localisation
public static LocalisableString SetPreviewPointToCurrent => new TranslatableString(getKey(@"set_preview_point_to_current"), @"Set preview point to current time"); public static LocalisableString SetPreviewPointToCurrent => new TranslatableString(getKey(@"set_preview_point_to_current"), @"Set preview point to current time");
/// <summary> /// <summary>
/// "Export package" /// "For editing (.olz)"
/// </summary> /// </summary>
public static LocalisableString ExportPackage => new TranslatableString(getKey(@"export_package"), @"Export package"); public static LocalisableString ExportForEditing => new TranslatableString(getKey(@"export_for_editing"), @"For editing (.olz)");
/// <summary>
/// "For compatibility (.osz)"
/// </summary>
public static LocalisableString ExportForCompatibility => new TranslatableString(getKey(@"export_for_compatibility"), @"For compatibility (.osz)");
/// <summary> /// <summary>
/// "Create new difficulty" /// "Create new difficulty"

View File

@ -425,7 +425,7 @@ namespace osu.Game.Online.Leaderboards
if (Score.Files.Count > 0) if (Score.Files.Count > 0)
{ {
items.Add(new OsuMenuItem("Export", MenuItemType.Standard, () => scoreManager.Export(Score))); items.Add(new OsuMenuItem(Localisation.CommonStrings.Export, MenuItemType.Standard, () => scoreManager.Export(Score)));
items.Add(new OsuMenuItem(CommonStrings.ButtonsDelete, MenuItemType.Destructive, () => dialogOverlay?.Push(new LocalScoreDeleteDialog(Score)))); items.Add(new OsuMenuItem(CommonStrings.ButtonsDelete, MenuItemType.Destructive, () => dialogOverlay?.Push(new LocalScoreDeleteDialog(Score))));
} }

View File

@ -39,6 +39,13 @@ namespace osu.Game.Rulesets.Objects
new[] { new Vector2d(1, 0), new Vector2d(1, 1.2447058f), new Vector2d(-0.8526471f, 2.118367f), new Vector2d(-2.6211002f, 7.854936e-06f), new Vector2d(-0.8526448f, -2.118357f), new Vector2d(1, -1.2447058f), new Vector2d(1, 0) }) new[] { new Vector2d(1, 0), new Vector2d(1, 1.2447058f), new Vector2d(-0.8526471f, 2.118367f), new Vector2d(-2.6211002f, 7.854936e-06f), new Vector2d(-0.8526448f, -2.118357f), new Vector2d(1, -1.2447058f), new Vector2d(1, 0) })
}; };
/// <summary>
/// Counts the number of segments in a slider path.
/// </summary>
/// <param name="controlPoints">The control points of the path.</param>
/// <returns>The number of segments in a slider path.</returns>
public static int CountSegments(IList<PathControlPoint> controlPoints) => controlPoints.Where((t, i) => t.Type != null && i < controlPoints.Count - 1).Count();
/// <summary> /// <summary>
/// Converts a slider path to bezier control point positions compatible with the legacy osu! client. /// Converts a slider path to bezier control point positions compatible with the legacy osu! client.
/// </summary> /// </summary>

View File

@ -997,23 +997,40 @@ namespace osu.Game.Screens.Edit
private List<MenuItem> createFileMenuItems() => new List<MenuItem> private List<MenuItem> createFileMenuItems() => new List<MenuItem>
{ {
new EditorMenuItem(WebCommonStrings.ButtonsSave, MenuItemType.Standard, () => Save()),
new EditorMenuItem(EditorStrings.ExportPackage, MenuItemType.Standard, exportBeatmap) { Action = { Disabled = !RuntimeInfo.IsDesktop } },
new EditorMenuItemSpacer(),
createDifficultyCreationMenu(), createDifficultyCreationMenu(),
createDifficultySwitchMenu(), createDifficultySwitchMenu(),
new EditorMenuItemSpacer(), new EditorMenuItemSpacer(),
new EditorMenuItem(EditorStrings.DeleteDifficulty, MenuItemType.Standard, deleteDifficulty) { Action = { Disabled = Beatmap.Value.BeatmapSetInfo.Beatmaps.Count < 2 } }, new EditorMenuItem(EditorStrings.DeleteDifficulty, MenuItemType.Standard, deleteDifficulty) { Action = { Disabled = Beatmap.Value.BeatmapSetInfo.Beatmaps.Count < 2 } },
new EditorMenuItemSpacer(), new EditorMenuItemSpacer(),
new EditorMenuItem(WebCommonStrings.ButtonsSave, MenuItemType.Standard, () => Save()),
createExportMenu(),
new EditorMenuItemSpacer(),
new EditorMenuItem(CommonStrings.Exit, MenuItemType.Standard, this.Exit) new EditorMenuItem(CommonStrings.Exit, MenuItemType.Standard, this.Exit)
}; };
private EditorMenuItem createExportMenu()
{
var exportItems = new List<MenuItem>
{
new EditorMenuItem(EditorStrings.ExportForEditing, MenuItemType.Standard, exportBeatmap) { Action = { Disabled = !RuntimeInfo.IsDesktop } },
new EditorMenuItem(EditorStrings.ExportForCompatibility, MenuItemType.Standard, exportLegacyBeatmap) { Action = { Disabled = !RuntimeInfo.IsDesktop } },
};
return new EditorMenuItem(CommonStrings.Export) { Items = exportItems };
}
private void exportBeatmap() private void exportBeatmap()
{ {
Save(); Save();
beatmapManager.Export(Beatmap.Value.BeatmapSetInfo); beatmapManager.Export(Beatmap.Value.BeatmapSetInfo);
} }
private void exportLegacyBeatmap()
{
Save();
beatmapManager.ExportLegacy(Beatmap.Value.BeatmapSetInfo);
}
/// <summary> /// <summary>
/// Beatmaps of the currently edited set, grouped by ruleset and ordered by difficulty. /// Beatmaps of the currently edited set, grouped by ruleset and ordered by difficulty.
/// </summary> /// </summary>