diff --git a/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs b/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs
index 394fd75488..1d207d04c7 100644
--- a/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs
+++ b/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs
@@ -8,7 +8,7 @@ using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Formats;
using osu.Game.IO;
using osu.Game.IO.Archives;
-using osu.Game.Resources;
+using osu.Game.Tests.Resources;
namespace osu.Game.Benchmarks
{
@@ -18,8 +18,8 @@ namespace osu.Game.Benchmarks
public override void SetUp()
{
- using (var resources = new DllResourceStore(OsuResources.ResourceAssembly))
- using (var archive = resources.GetStream("Beatmaps/241526 Soleily - Renatus.osz"))
+ using (var resources = new DllResourceStore(typeof(TestResources).Assembly))
+ using (var archive = resources.GetStream("Resources/Archives/241526 Soleily - Renatus.osz"))
using (var reader = new ZipArchiveReader(archive))
reader.GetStream("Soleily - Renatus (Gamu) [Insane].osu").CopyTo(beatmapStream);
}
diff --git a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj
index 88fe8f1150..41e726e05c 100644
--- a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj
+++ b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj
@@ -13,6 +13,7 @@
+
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
index df14b11e8a..75dfbf45f2 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
@@ -40,10 +40,10 @@ namespace osu.Game.Rulesets.Osu.Edit
[BackgroundDependencyLoader]
private void load()
{
+ LayerBelowRuleset.Add(distanceSnapGridContainer = new Container { RelativeSizeAxes = Axes.Both });
+
EditorBeatmap.SelectedHitObjects.CollectionChanged += (_, __) => updateDistanceSnapGrid();
EditorBeatmap.PlacementObject.ValueChanged += _ => updateDistanceSnapGrid();
-
- LayerBelowRuleset.Add(distanceSnapGridContainer = new Container { RelativeSizeAxes = Axes.Both });
}
protected override ComposeBlueprintContainer CreateBlueprintContainer() => new OsuBlueprintContainer(HitObjects);
@@ -86,10 +86,24 @@ namespace osu.Game.Rulesets.Osu.Edit
distanceSnapGridContainer.Clear();
distanceSnapGridCache.Invalidate();
- if (BlueprintContainer.CurrentTool is SelectTool && !EditorBeatmap.SelectedHitObjects.Any())
- return;
+ switch (BlueprintContainer.CurrentTool)
+ {
+ case SelectTool _:
+ if (!EditorBeatmap.SelectedHitObjects.Any())
+ return;
- if ((distanceSnapGrid = createDistanceSnapGrid(EditorBeatmap.SelectedHitObjects)) != null)
+ distanceSnapGrid = createDistanceSnapGrid(EditorBeatmap.SelectedHitObjects);
+ break;
+
+ default:
+ if (!CursorInPlacementArea)
+ return;
+
+ distanceSnapGrid = createDistanceSnapGrid(Enumerable.Empty());
+ break;
+ }
+
+ if (distanceSnapGrid != null)
{
distanceSnapGridContainer.Add(distanceSnapGrid);
distanceSnapGridCache.Validate();
diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs
index b286c054e9..f626b45e42 100644
--- a/osu.Game/Beatmaps/BeatmapManager.cs
+++ b/osu.Game/Beatmaps/BeatmapManager.cs
@@ -27,7 +27,6 @@ using osu.Game.Online.API.Requests;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects;
using Decoder = osu.Game.Beatmaps.Formats.Decoder;
-using ZipArchive = SharpCompress.Archives.Zip.ZipArchive;
namespace osu.Game.Beatmaps
{
@@ -66,7 +65,6 @@ namespace osu.Game.Beatmaps
private readonly AudioManager audioManager;
private readonly GameHost host;
private readonly BeatmapOnlineLookupQueue onlineLookupQueue;
- private readonly Storage exportStorage;
public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, AudioManager audioManager, GameHost host = null,
WorkingBeatmap defaultBeatmap = null)
@@ -83,7 +81,6 @@ namespace osu.Game.Beatmaps
beatmaps.BeatmapRestored += b => beatmapRestored.Value = new WeakReference(b);
onlineLookupQueue = new BeatmapOnlineLookupQueue(api, storage);
- exportStorage = storage.GetStorageForDirectory("exports");
}
protected override ArchiveDownloadRequest CreateDownloadRequest(BeatmapSetInfo set, bool minimiseDownloadSize) =>
@@ -214,26 +211,6 @@ namespace osu.Game.Beatmaps
workingCache.Remove(working);
}
- ///
- /// Exports a to an .osz package.
- ///
- /// The to export.
- public void Export(BeatmapSetInfo set)
- {
- var localSet = QueryBeatmapSet(s => s.ID == set.ID);
-
- using (var archive = ZipArchive.Create())
- {
- foreach (var file in localSet.Files)
- archive.AddEntry(file.Filename, Files.Storage.GetStream(file.FileInfo.StoragePath));
-
- using (var outputStream = exportStorage.GetStream($"{set}.osz", FileAccess.Write, FileMode.Create))
- archive.SaveTo(outputStream);
-
- exportStorage.OpenInNativeExplorer();
- }
- }
-
private readonly WeakList workingCache = new WeakList();
///
diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs
index 33b16cbaaf..f21f708f95 100644
--- a/osu.Game/Database/ArchiveModelManager.cs
+++ b/osu.Game/Database/ArchiveModelManager.cs
@@ -22,6 +22,7 @@ using osu.Game.IO.Archives;
using osu.Game.IPC;
using osu.Game.Overlays.Notifications;
using osu.Game.Utils;
+using SharpCompress.Archives.Zip;
using SharpCompress.Common;
using FileInfo = osu.Game.IO.FileInfo;
@@ -82,6 +83,8 @@ namespace osu.Game.Database
// ReSharper disable once NotAccessedField.Local (we should keep a reference to this so it is not finalised)
private ArchiveImportIPCChannel ipc;
+ private readonly Storage exportStorage;
+
protected ArchiveModelManager(Storage storage, IDatabaseContextFactory contextFactory, MutableDatabaseBackedStoreWithFileIncludes modelStore, IIpcHost importHost = null)
{
ContextFactory = contextFactory;
@@ -90,6 +93,8 @@ namespace osu.Game.Database
ModelStore.ItemAdded += item => handleEvent(() => itemAdded.Value = new WeakReference(item));
ModelStore.ItemRemoved += item => handleEvent(() => itemRemoved.Value = new WeakReference(item));
+ exportStorage = storage.GetStorageForDirectory("exports");
+
Files = new FileStore(contextFactory, storage);
if (importHost != null)
@@ -369,6 +374,29 @@ namespace osu.Game.Database
return item;
}, cancellationToken, TaskCreationOptions.HideScheduler, import_scheduler).Unwrap();
+ ///
+ /// Exports an item to a legacy (.zip based) package.
+ ///
+ /// The item to export.
+ public void Export(TModel item)
+ {
+ var retrievedItem = ModelStore.ConsumableItems.FirstOrDefault(s => s.ID == item.ID);
+
+ if (retrievedItem == null)
+ throw new ArgumentException("Specified model could not be found", nameof(item));
+
+ using (var archive = ZipArchive.Create())
+ {
+ foreach (var file in retrievedItem.Files)
+ archive.AddEntry(file.Filename, Files.Storage.GetStream(file.FileInfo.StoragePath));
+
+ using (var outputStream = exportStorage.GetStream($"{getValidFilename(item.ToString())}{HandledExtensions.First()}", FileAccess.Write, FileMode.Create))
+ archive.SaveTo(outputStream);
+
+ exportStorage.OpenInNativeExplorer();
+ }
+ }
+
public void UpdateFile(TModel model, TFileModel file, Stream contents)
{
using (var usage = ContextFactory.GetForWrite())
@@ -710,5 +738,12 @@ namespace osu.Game.Database
}
#endregion
+
+ private string getValidFilename(string filename)
+ {
+ foreach (char c in Path.GetInvalidFileNameChars())
+ filename = filename.Replace(c, '_');
+ return filename;
+ }
}
}
diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs
index 94080f5592..b84b9fec37 100644
--- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs
@@ -7,6 +7,7 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
+using osu.Framework.Logging;
using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface;
using osu.Game.Skinning;
@@ -38,9 +39,11 @@ namespace osu.Game.Overlays.Settings.Sections
private void load(OsuConfigManager config)
{
FlowContent.Spacing = new Vector2(0, 5);
+
Children = new Drawable[]
{
skinDropdown = new SkinSettingsDropdown(),
+ new ExportSkinButton(),
new SettingsSlider
{
LabelText = "Menu cursor size",
@@ -117,5 +120,35 @@ namespace osu.Game.Overlays.Settings.Sections
protected override DropdownMenu CreateMenu() => base.CreateMenu().With(m => m.MaxHeight = 200);
}
}
+
+ private class ExportSkinButton : SettingsButton
+ {
+ [Resolved]
+ private SkinManager skins { get; set; }
+
+ private Bindable currentSkin;
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Text = "Export selected skin";
+ Action = export;
+
+ currentSkin = skins.CurrentSkin.GetBoundCopy();
+ currentSkin.BindValueChanged(skin => Enabled.Value = skin.NewValue.SkinInfo.ID > 0, true);
+ }
+
+ private void export()
+ {
+ try
+ {
+ skins.Export(currentSkin.Value.SkinInfo);
+ }
+ catch (Exception e)
+ {
+ Logger.Log($"Could not export current skin: {e.Message}", level: LogLevel.Error);
+ }
+ }
+ }
}
}
diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs
index dca0513eb1..3541a78faa 100644
--- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs
+++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs
@@ -30,11 +30,13 @@ namespace osu.Game.Rulesets.Edit
///
protected readonly HitObject HitObject;
- [Resolved]
+ [Resolved(canBeNull: true)]
protected EditorClock EditorClock { get; private set; }
private readonly IBindable beatmap = new Bindable();
+ private Bindable startTimeBindable;
+
[Resolved]
private IPlacementHandler placementHandler { get; set; }
@@ -54,7 +56,8 @@ namespace osu.Game.Rulesets.Edit
{
this.beatmap.BindTo(beatmap);
- ApplyDefaultsToHitObject();
+ startTimeBindable = HitObject.StartTimeBindable.GetBoundCopy();
+ startTimeBindable.BindValueChanged(_ => ApplyDefaultsToHitObject(), true);
}
///
@@ -80,9 +83,6 @@ namespace osu.Game.Rulesets.Edit
PlacementActive = false;
}
- [Resolved(canBeNull: true)]
- private EditorClock editorClock { get; set; }
-
///
/// Updates the position of this to a new screen-space position.
///
@@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Edit
public virtual void UpdatePosition(SnapResult snapResult)
{
if (!PlacementActive)
- HitObject.StartTime = snapResult.Time ?? editorClock?.CurrentTime ?? Time.Current;
+ HitObject.StartTime = snapResult.Time ?? EditorClock?.CurrentTime ?? Time.Current;
}
///
diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs
index 6b9627188e..b9fe44ef3b 100644
--- a/osu.Game/Skinning/SkinInfo.cs
+++ b/osu.Game/Skinning/SkinInfo.cs
@@ -24,8 +24,6 @@ namespace osu.Game.Skinning
public bool DeletePending { get; set; }
- public string FullName => $"\"{Name}\" by {Creator}";
-
public static SkinInfo Default { get; } = new SkinInfo
{
Name = "osu!lazer",
@@ -34,6 +32,10 @@ namespace osu.Game.Skinning
public bool Equals(SkinInfo other) => other != null && ID == other.ID;
- public override string ToString() => FullName;
+ public override string ToString()
+ {
+ string author = Creator == null ? string.Empty : $"({Creator})";
+ return $"{Name} {author}".Trim();
+ }
}
}
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 010ef8578a..dbaf0697a0 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -19,7 +19,7 @@
-
+
diff --git a/osu.iOS.props b/osu.iOS.props
index 88b0c7dd8a..664a629369 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -75,7 +75,7 @@
-
+