mirror of
https://github.com/osukey/osukey.git
synced 2025-05-29 09:27:18 +09:00
Merge branch 'master' into idle-still-starts
This commit is contained in:
commit
3a9f16ccf2
@ -24,6 +24,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
typeof(ChangelogListing),
|
typeof(ChangelogListing),
|
||||||
typeof(ChangelogSingleBuild),
|
typeof(ChangelogSingleBuild),
|
||||||
typeof(ChangelogBuild),
|
typeof(ChangelogBuild),
|
||||||
|
typeof(Comments),
|
||||||
};
|
};
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
|
@ -82,6 +82,8 @@ namespace osu.Game.Beatmaps
|
|||||||
protected override ArchiveDownloadRequest<BeatmapSetInfo> CreateDownloadRequest(BeatmapSetInfo set, bool minimiseDownloadSize) =>
|
protected override ArchiveDownloadRequest<BeatmapSetInfo> CreateDownloadRequest(BeatmapSetInfo set, bool minimiseDownloadSize) =>
|
||||||
new DownloadBeatmapSetRequest(set, minimiseDownloadSize);
|
new DownloadBeatmapSetRequest(set, minimiseDownloadSize);
|
||||||
|
|
||||||
|
protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path)?.ToLowerInvariant() == ".osz";
|
||||||
|
|
||||||
protected override Task Populate(BeatmapSetInfo beatmapSet, ArchiveReader archive, CancellationToken cancellationToken = default)
|
protected override Task Populate(BeatmapSetInfo beatmapSet, ArchiveReader archive, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (archive != null)
|
if (archive != null)
|
||||||
@ -176,20 +178,23 @@ namespace osu.Game.Beatmaps
|
|||||||
if (beatmapInfo?.BeatmapSet == null || beatmapInfo == DefaultBeatmap?.BeatmapInfo)
|
if (beatmapInfo?.BeatmapSet == null || beatmapInfo == DefaultBeatmap?.BeatmapInfo)
|
||||||
return DefaultBeatmap;
|
return DefaultBeatmap;
|
||||||
|
|
||||||
var cached = workingCache.FirstOrDefault(w => w.BeatmapInfo?.ID == beatmapInfo.ID);
|
lock (workingCache)
|
||||||
|
{
|
||||||
|
var cached = workingCache.FirstOrDefault(w => w.BeatmapInfo?.ID == beatmapInfo.ID);
|
||||||
|
|
||||||
if (cached != null)
|
if (cached != null)
|
||||||
return cached;
|
return cached;
|
||||||
|
|
||||||
if (beatmapInfo.Metadata == null)
|
if (beatmapInfo.Metadata == null)
|
||||||
beatmapInfo.Metadata = beatmapInfo.BeatmapSet.Metadata;
|
beatmapInfo.Metadata = beatmapInfo.BeatmapSet.Metadata;
|
||||||
|
|
||||||
WorkingBeatmap working = new BeatmapManagerWorkingBeatmap(Files.Store, new LargeTextureStore(host?.CreateTextureLoaderStore(Files.Store)), beatmapInfo, audioManager);
|
WorkingBeatmap working = new BeatmapManagerWorkingBeatmap(Files.Store, new LargeTextureStore(host?.CreateTextureLoaderStore(Files.Store)), beatmapInfo, audioManager);
|
||||||
|
|
||||||
previous?.TransferTo(working);
|
previous?.TransferTo(working);
|
||||||
workingCache.Add(working);
|
workingCache.Add(working);
|
||||||
|
|
||||||
return working;
|
return working;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// 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;
|
||||||
@ -177,7 +177,7 @@ namespace osu.Game.Database
|
|||||||
// TODO: Add a check to prevent files from storage to be deleted.
|
// TODO: Add a check to prevent files from storage to be deleted.
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (import != null && File.Exists(path))
|
if (import != null && File.Exists(path) && ShouldDeleteArchive(path))
|
||||||
File.Delete(path);
|
File.Delete(path);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
@ -208,7 +208,7 @@ namespace osu.Game.Database
|
|||||||
{
|
{
|
||||||
model = CreateModel(archive);
|
model = CreateModel(archive);
|
||||||
|
|
||||||
if (model == null) return null;
|
if (model == null) return Task.FromResult<TModel>(null);
|
||||||
|
|
||||||
model.Hash = computeHash(archive);
|
model.Hash = computeHash(archive);
|
||||||
}
|
}
|
||||||
@ -499,6 +499,18 @@ namespace osu.Game.Database
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected virtual string ImportFromStablePath => null;
|
protected virtual string ImportFromStablePath => null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Select paths to import from stable. Default implementation iterates all directories in <see cref="ImportFromStablePath"/>.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual IEnumerable<string> GetStableImportPaths(Storage stableStoage) => stableStoage.GetDirectories(ImportFromStablePath);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether this specified path should be removed after successful import.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">The path for consideration. May be a file or a directory.</param>
|
||||||
|
/// <returns>Whether to perform deletion.</returns>
|
||||||
|
protected virtual bool ShouldDeleteArchive(string path) => false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This is a temporary method and will likely be replaced by a full-fledged (and more correctly placed) migration process in the future.
|
/// This is a temporary method and will likely be replaced by a full-fledged (and more correctly placed) migration process in the future.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -519,7 +531,7 @@ namespace osu.Game.Database
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.Run(async () => await Import(stable.GetDirectories(ImportFromStablePath).Select(f => stable.GetFullPath(f)).ToArray()));
|
return Task.Run(async () => await Import(GetStableImportPaths(GetStableStorage()).Select(f => stable.GetFullPath(f)).ToArray()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
@ -33,6 +33,8 @@ namespace osu.Game.Online.API.Requests.Responses
|
|||||||
[JsonProperty("versions")]
|
[JsonProperty("versions")]
|
||||||
public VersionNavigation Versions { get; set; }
|
public VersionNavigation Versions { get; set; }
|
||||||
|
|
||||||
|
public string Url => $"https://osu.ppy.sh/home/changelog/{UpdateStream.Name}/{Version}";
|
||||||
|
|
||||||
public class VersionNavigation
|
public class VersionNavigation
|
||||||
{
|
{
|
||||||
[JsonProperty("next")]
|
[JsonProperty("next")]
|
||||||
|
@ -387,6 +387,7 @@ namespace osu.Game
|
|||||||
BeatmapManager.PresentImport = items => PresentBeatmap(items.First());
|
BeatmapManager.PresentImport = items => PresentBeatmap(items.First());
|
||||||
|
|
||||||
ScoreManager.PostNotification = n => notifications?.Post(n);
|
ScoreManager.PostNotification = n => notifications?.Post(n);
|
||||||
|
ScoreManager.GetStableStorage = GetStorageForStableInstall;
|
||||||
ScoreManager.PresentImport = items => PresentScore(items.First());
|
ScoreManager.PresentImport = items => PresentScore(items.First());
|
||||||
|
|
||||||
Container logoContainer;
|
Container logoContainer;
|
||||||
|
@ -58,7 +58,11 @@ namespace osu.Game.Overlays.Changelog
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (build != null)
|
if (build != null)
|
||||||
Child = new ChangelogBuildWithNavigation(build) { SelectBuild = SelectBuild };
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new ChangelogBuildWithNavigation(build) { SelectBuild = SelectBuild },
|
||||||
|
new Comments(build)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ChangelogBuildWithNavigation : ChangelogBuild
|
public class ChangelogBuildWithNavigation : ChangelogBuild
|
||||||
|
79
osu.Game/Overlays/Changelog/Comments.cs
Normal file
79
osu.Game/Overlays/Changelog/Comments.cs
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
// 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.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Changelog
|
||||||
|
{
|
||||||
|
public class Comments : CompositeDrawable
|
||||||
|
{
|
||||||
|
private readonly APIChangelogBuild build;
|
||||||
|
|
||||||
|
public Comments(APIChangelogBuild build)
|
||||||
|
{
|
||||||
|
this.build = build;
|
||||||
|
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
|
||||||
|
Padding = new MarginPadding
|
||||||
|
{
|
||||||
|
Horizontal = 50,
|
||||||
|
Vertical = 20,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colours)
|
||||||
|
{
|
||||||
|
LinkFlowContainer text;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
CornerRadius = 10,
|
||||||
|
Child = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = colours.GreyVioletDarker
|
||||||
|
},
|
||||||
|
},
|
||||||
|
text = new LinkFlowContainer(t =>
|
||||||
|
{
|
||||||
|
t.Colour = colours.PinkLighter;
|
||||||
|
t.Font = OsuFont.Default.With(size: 14);
|
||||||
|
})
|
||||||
|
{
|
||||||
|
Padding = new MarginPadding(20),
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
text.AddParagraph("Got feedback?", t =>
|
||||||
|
{
|
||||||
|
t.Colour = Color4.White;
|
||||||
|
t.Font = OsuFont.Default.With(italics: true, size: 20);
|
||||||
|
t.Padding = new MarginPadding { Bottom = 20 };
|
||||||
|
});
|
||||||
|
|
||||||
|
text.AddParagraph("We would love to hear what you think of this update! ");
|
||||||
|
text.AddIcon(FontAwesome.Regular.GrinHearts);
|
||||||
|
|
||||||
|
text.AddParagraph("Please visit the ");
|
||||||
|
text.AddLink("web version", $"{build.Url}#comments");
|
||||||
|
text.AddText(" of this changelog to leave any comments.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,7 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
||||||
@ -16,14 +17,16 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
|||||||
protected override string Header => "General";
|
protected override string Header => "General";
|
||||||
|
|
||||||
private TriangleButton importBeatmapsButton;
|
private TriangleButton importBeatmapsButton;
|
||||||
|
private TriangleButton importScoresButton;
|
||||||
private TriangleButton importSkinsButton;
|
private TriangleButton importSkinsButton;
|
||||||
private TriangleButton deleteSkinsButton;
|
|
||||||
private TriangleButton deleteBeatmapsButton;
|
private TriangleButton deleteBeatmapsButton;
|
||||||
|
private TriangleButton deleteScoresButton;
|
||||||
|
private TriangleButton deleteSkinsButton;
|
||||||
private TriangleButton restoreButton;
|
private TriangleButton restoreButton;
|
||||||
private TriangleButton undeleteButton;
|
private TriangleButton undeleteButton;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(BeatmapManager beatmaps, SkinManager skins, DialogOverlay dialogOverlay)
|
private void load(BeatmapManager beatmaps, ScoreManager scores, SkinManager skins, DialogOverlay dialogOverlay)
|
||||||
{
|
{
|
||||||
if (beatmaps.SupportsImportFromStable)
|
if (beatmaps.SupportsImportFromStable)
|
||||||
{
|
{
|
||||||
@ -51,6 +54,32 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (scores.SupportsImportFromStable)
|
||||||
|
{
|
||||||
|
Add(importScoresButton = new SettingsButton
|
||||||
|
{
|
||||||
|
Text = "Import scores from stable",
|
||||||
|
Action = () =>
|
||||||
|
{
|
||||||
|
importScoresButton.Enabled.Value = false;
|
||||||
|
scores.ImportFromStableAsync().ContinueWith(t => Schedule(() => importScoresButton.Enabled.Value = true));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Add(deleteScoresButton = new DangerousSettingsButton
|
||||||
|
{
|
||||||
|
Text = "Delete ALL scores",
|
||||||
|
Action = () =>
|
||||||
|
{
|
||||||
|
dialogOverlay?.Push(new DeleteAllBeatmapsDialog(() =>
|
||||||
|
{
|
||||||
|
deleteScoresButton.Enabled.Value = false;
|
||||||
|
Task.Run(() => scores.Delete(scores.GetAllUsableScores())).ContinueWith(t => Schedule(() => deleteScoresButton.Enabled.Value = true));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (skins.SupportsImportFromStable)
|
if (skins.SupportsImportFromStable)
|
||||||
{
|
{
|
||||||
Add(importSkinsButton = new SettingsButton
|
Add(importSkinsButton = new SettingsButton
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
@ -24,7 +25,7 @@ namespace osu.Game.Scoring
|
|||||||
|
|
||||||
protected override string[] HashableFileTypes => new[] { ".osr" };
|
protected override string[] HashableFileTypes => new[] { ".osr" };
|
||||||
|
|
||||||
protected override string ImportFromStablePath => "Replays";
|
protected override string ImportFromStablePath => Path.Combine("Data", "r");
|
||||||
|
|
||||||
private readonly RulesetStore rulesets;
|
private readonly RulesetStore rulesets;
|
||||||
private readonly Func<BeatmapManager> beatmaps;
|
private readonly Func<BeatmapManager> beatmaps;
|
||||||
@ -55,6 +56,9 @@ namespace osu.Game.Scoring
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override IEnumerable<string> GetStableImportPaths(Storage stableStorage)
|
||||||
|
=> stableStorage.GetFiles(ImportFromStablePath).Where(p => HandledExtensions.Any(ext => Path.GetExtension(p)?.Equals(ext, StringComparison.InvariantCultureIgnoreCase) ?? false));
|
||||||
|
|
||||||
public Score GetScore(ScoreInfo score) => new LegacyDatabasedScore(score, rulesets, beatmaps(), Files.Store);
|
public Score GetScore(ScoreInfo score) => new LegacyDatabasedScore(score, rulesets, beatmaps(), Files.Store);
|
||||||
|
|
||||||
public List<ScoreInfo> GetAllUsableScores() => ModelStore.ConsumableItems.Where(s => !s.DeletePending).ToList();
|
public List<ScoreInfo> GetAllUsableScores() => ModelStore.ConsumableItems.Where(s => !s.DeletePending).ToList();
|
||||||
@ -65,6 +69,6 @@ namespace osu.Game.Scoring
|
|||||||
|
|
||||||
protected override ArchiveDownloadRequest<ScoreInfo> CreateDownloadRequest(ScoreInfo score, bool minimiseDownload) => new DownloadReplayRequest(score);
|
protected override ArchiveDownloadRequest<ScoreInfo> CreateDownloadRequest(ScoreInfo score, bool minimiseDownload) => new DownloadReplayRequest(score);
|
||||||
|
|
||||||
protected override bool CheckLocalAvailability(ScoreInfo model, IQueryable<ScoreInfo> items) => items.Any(s => s.OnlineScoreID == model.OnlineScoreID);
|
protected override bool CheckLocalAvailability(ScoreInfo model, IQueryable<ScoreInfo> items) => items.Any(s => s.OnlineScoreID == model.OnlineScoreID && s.Files.Any());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,11 +86,7 @@ namespace osu.Game.Screens.Play
|
|||||||
}
|
}
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
if (replayAvailability == ReplayAvailability.NotAvailable)
|
button.Enabled.Value = replayAvailability != ReplayAvailability.NotAvailable;
|
||||||
{
|
|
||||||
button.Enabled.Value = false;
|
|
||||||
button.Alpha = 0.6f;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum ReplayAvailability
|
private enum ReplayAvailability
|
||||||
|
@ -12,7 +12,7 @@ namespace osu.Game.Screens.Select
|
|||||||
public ImportFromStablePopup(Action importFromStable)
|
public ImportFromStablePopup(Action importFromStable)
|
||||||
{
|
{
|
||||||
HeaderText = @"You have no beatmaps!";
|
HeaderText = @"You have no beatmaps!";
|
||||||
BodyText = "An existing copy of osu! was found, though.\nWould you like to import your beatmaps (and skins)?";
|
BodyText = "An existing copy of osu! was found, though.\nWould you like to import your beatmaps, skins and scores?";
|
||||||
|
|
||||||
Icon = FontAwesome.Solid.Plane;
|
Icon = FontAwesome.Solid.Plane;
|
||||||
|
|
||||||
|
@ -35,6 +35,7 @@ using System.Linq;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Select
|
namespace osu.Game.Screens.Select
|
||||||
{
|
{
|
||||||
@ -215,7 +216,7 @@ namespace osu.Game.Screens.Select
|
|||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
private void load(BeatmapManager beatmaps, AudioManager audio, DialogOverlay dialog, OsuColour colours, SkinManager skins)
|
private void load(BeatmapManager beatmaps, AudioManager audio, DialogOverlay dialog, OsuColour colours, SkinManager skins, ScoreManager scores)
|
||||||
{
|
{
|
||||||
mods.BindTo(Mods);
|
mods.BindTo(Mods);
|
||||||
|
|
||||||
@ -252,7 +253,7 @@ namespace osu.Game.Screens.Select
|
|||||||
if (!beatmaps.GetAllUsableBeatmapSets().Any() && beatmaps.StableInstallationAvailable)
|
if (!beatmaps.GetAllUsableBeatmapSets().Any() && beatmaps.StableInstallationAvailable)
|
||||||
dialogOverlay.Push(new ImportFromStablePopup(() =>
|
dialogOverlay.Push(new ImportFromStablePopup(() =>
|
||||||
{
|
{
|
||||||
Task.Run(beatmaps.ImportFromStableAsync);
|
Task.Run(beatmaps.ImportFromStableAsync).ContinueWith(_ => scores.ImportFromStableAsync(), TaskContinuationOptions.OnlyOnRanToCompletion);
|
||||||
Task.Run(skins.ImportFromStableAsync);
|
Task.Run(skins.ImportFromStableAsync);
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@ -54,6 +55,8 @@ namespace osu.Game.Skinning
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path)?.ToLowerInvariant() == ".osk";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns a list of all usable <see cref="SkinInfo"/>s. Includes the special default skin plus all skins from <see cref="GetAllUserSkins"/>.
|
/// Returns a list of all usable <see cref="SkinInfo"/>s. Includes the special default skin plus all skins from <see cref="GetAllUserSkins"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user