1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-21 23:27:25 +08:00

Merge branch 'master' into stamina-available-fingers

This commit is contained in:
Dan Balasescu 2022-11-01 13:01:31 +09:00
commit 720d6b5996
19 changed files with 25 additions and 437 deletions

View File

@ -49,7 +49,7 @@ namespace osu.Game.Tournament.Tests.NonVisual
// manual cleaning so we can prepare a config file.
storage.DeleteDirectory(string.Empty);
using (var storageConfig = new TournamentStorageManager(storage))
using (var storageConfig = new TournamentConfigManager(storage))
storageConfig.SetValue(StorageConfig.CurrentTournament, custom_tournament);
try
@ -66,82 +66,5 @@ namespace osu.Game.Tournament.Tests.NonVisual
}
}
}
[Test]
public void TestMigration()
{
using (HeadlessGameHost host = new TestRunHeadlessGameHost(nameof(TestMigration), null)) // don't use clean run as we are writing test files for migration.
{
string osuRoot = Path.Combine(host.UserStoragePaths.First(), nameof(TestMigration));
string configFile = Path.Combine(osuRoot, "tournament.ini");
if (File.Exists(configFile))
File.Delete(configFile);
// Recreate the old setup that uses "tournament" as the base path.
string oldPath = Path.Combine(osuRoot, "tournament");
string videosPath = Path.Combine(oldPath, "Videos");
string modsPath = Path.Combine(oldPath, "Mods");
string flagsPath = Path.Combine(oldPath, "Flags");
Directory.CreateDirectory(videosPath);
Directory.CreateDirectory(modsPath);
Directory.CreateDirectory(flagsPath);
// Define testing files corresponding to the specific file migrations that are needed
string bracketFile = Path.Combine(osuRoot, TournamentGameBase.BRACKET_FILENAME);
string drawingsConfig = Path.Combine(osuRoot, "drawings.ini");
string drawingsFile = Path.Combine(osuRoot, "drawings.txt");
string drawingsResult = Path.Combine(osuRoot, "drawings_results.txt");
// Define sample files to test recursive copying
string videoFile = Path.Combine(videosPath, "video.mp4");
string modFile = Path.Combine(modsPath, "mod.png");
string flagFile = Path.Combine(flagsPath, "flag.png");
File.WriteAllText(bracketFile, "{}");
File.WriteAllText(drawingsConfig, "test");
File.WriteAllText(drawingsFile, "test");
File.WriteAllText(drawingsResult, "test");
File.WriteAllText(videoFile, "test");
File.WriteAllText(modFile, "test");
File.WriteAllText(flagFile, "test");
try
{
var osu = LoadTournament(host);
var storage = osu.Dependencies.Get<Storage>();
string migratedPath = Path.Combine(host.Storage.GetFullPath("."), "tournaments", "default");
videosPath = Path.Combine(migratedPath, "Videos");
modsPath = Path.Combine(migratedPath, "Mods");
flagsPath = Path.Combine(migratedPath, "Flags");
videoFile = Path.Combine(videosPath, "video.mp4");
modFile = Path.Combine(modsPath, "mod.png");
flagFile = Path.Combine(flagsPath, "flag.png");
Assert.That(storage.GetFullPath("."), Is.EqualTo(migratedPath));
Assert.True(storage.Exists(TournamentGameBase.BRACKET_FILENAME));
Assert.True(storage.Exists("drawings.txt"));
Assert.True(storage.Exists("drawings_results.txt"));
Assert.True(storage.Exists("drawings.ini"));
Assert.True(storage.Exists(videoFile));
Assert.True(storage.Exists(modFile));
Assert.True(storage.Exists(flagFile));
}
finally
{
host.Exit();
}
}
}
}
}

View File

@ -8,14 +8,23 @@ using osu.Framework.Platform;
namespace osu.Game.Tournament.Configuration
{
public class TournamentStorageManager : IniConfigManager<StorageConfig>
public class TournamentConfigManager : IniConfigManager<StorageConfig>
{
protected override string Filename => "tournament.ini";
public TournamentStorageManager(Storage storage)
private const string default_tournament = "default";
public TournamentConfigManager(Storage storage)
: base(storage)
{
}
protected override void InitialiseDefaults()
{
base.InitialiseDefaults();
SetDefault(StorageConfig.CurrentTournament, default_tournament);
}
}
public enum StorageConfig

View File

@ -1,10 +1,7 @@
// 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.
#nullable disable
using System.Collections.Generic;
using System.IO;
using osu.Framework.Bindables;
using osu.Framework.Logging;
using osu.Framework.Platform;
@ -13,35 +10,28 @@ using osu.Game.Tournament.Configuration;
namespace osu.Game.Tournament.IO
{
public class TournamentStorage : MigratableStorage
public class TournamentStorage : WrappedStorage
{
private const string default_tournament = "default";
private readonly Storage storage;
/// <summary>
/// The storage where all tournaments are located.
/// </summary>
public readonly Storage AllTournaments;
private readonly TournamentStorageManager storageConfig;
public readonly Bindable<string> CurrentTournament;
protected TournamentConfigManager TournamentConfigManager { get; }
public TournamentStorage(Storage storage)
: base(storage.GetStorageForDirectory("tournaments"), string.Empty)
{
this.storage = storage;
AllTournaments = UnderlyingStorage;
storageConfig = new TournamentStorageManager(storage);
TournamentConfigManager = new TournamentConfigManager(storage);
if (storage.Exists("tournament.ini"))
{
ChangeTargetStorage(AllTournaments.GetStorageForDirectory(storageConfig.Get<string>(StorageConfig.CurrentTournament)));
}
else
Migrate(AllTournaments.GetStorageForDirectory(default_tournament));
CurrentTournament = TournamentConfigManager.GetBindable<string>(StorageConfig.CurrentTournament);
ChangeTargetStorage(AllTournaments.GetStorageForDirectory(CurrentTournament.Value));
CurrentTournament = storageConfig.GetBindable<string>(StorageConfig.CurrentTournament);
Logger.Log("Using tournament storage: " + GetFullPath(string.Empty));
CurrentTournament.BindValueChanged(updateTournament);
@ -53,62 +43,6 @@ namespace osu.Game.Tournament.IO
Logger.Log("Changing tournament storage: " + GetFullPath(string.Empty));
}
protected override void ChangeTargetStorage(Storage newStorage)
{
// due to an unfortunate oversight, on OSes that are sensitive to pathname casing
// the custom flags directory needed to be named `Flags` (uppercase),
// while custom mods and videos directories needed to be named `mods` and `videos` respectively (lowercase).
// to unify handling to uppercase, move any non-compliant directories automatically for the user to migrate.
// can be removed 20220528
if (newStorage.ExistsDirectory("flags"))
AttemptOperation(() => Directory.Move(newStorage.GetFullPath("flags"), newStorage.GetFullPath("Flags")));
if (newStorage.ExistsDirectory("mods"))
AttemptOperation(() => Directory.Move(newStorage.GetFullPath("mods"), newStorage.GetFullPath("Mods")));
if (newStorage.ExistsDirectory("videos"))
AttemptOperation(() => Directory.Move(newStorage.GetFullPath("videos"), newStorage.GetFullPath("Videos")));
base.ChangeTargetStorage(newStorage);
}
public IEnumerable<string> ListTournaments() => AllTournaments.GetDirectories(string.Empty);
public override bool Migrate(Storage newStorage)
{
// this migration only happens once on moving to the per-tournament storage system.
// listed files are those known at that point in time.
// this can be removed at some point in the future (6 months obsoletion would mean 2021-04-19)
var source = new DirectoryInfo(storage.GetFullPath("tournament"));
var destination = new DirectoryInfo(newStorage.GetFullPath("."));
if (source.Exists)
{
Logger.Log("Migrating tournament assets to default tournament storage.");
CopyRecursive(source, destination);
DeleteRecursive(source);
}
moveFileIfExists(TournamentGameBase.BRACKET_FILENAME, destination);
moveFileIfExists("drawings.txt", destination);
moveFileIfExists("drawings_results.txt", destination);
moveFileIfExists("drawings.ini", destination);
ChangeTargetStorage(newStorage);
storageConfig.SetValue(StorageConfig.CurrentTournament, default_tournament);
storageConfig.Save();
return true;
}
private void moveFileIfExists(string file, DirectoryInfo destination)
{
if (!storage.Exists(file))
return;
Logger.Log($"Migrating {file} to default tournament storage.");
var fileInfo = new System.IO.FileInfo(storage.GetFullPath(file));
AttemptOperation(() => fileInfo.CopyTo(Path.Combine(destination.FullName, fileInfo.Name), true));
fileInfo.Delete();
}
}
}

View File

@ -238,14 +238,6 @@ namespace osu.Game.Beatmaps
#region Compatibility properties
[Ignored]
[Obsolete("Use BeatmapInfo.Difficulty instead.")] // can be removed 20220719
public BeatmapDifficulty BaseDifficulty
{
get => Difficulty;
set => Difficulty = value;
}
[Ignored]
public string? Path => File?.Filename;

View File

@ -3,7 +3,6 @@
#nullable disable
using System;
using System.Collections.Generic;
using osuTK.Graphics;
@ -22,11 +21,5 @@ namespace osu.Game.Beatmaps.Formats
/// if empty, <see cref="ComboColours"/> will fall back to default combo colours.
/// </summary>
List<Color4> CustomComboColours { get; }
/// <summary>
/// Adds combo colours to the list.
/// </summary>
[Obsolete("Use CustomComboColours directly.")] // can be removed 20220215
void AddComboColours(params Color4[] colours);
}
}

View File

@ -1,20 +0,0 @@
// 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.
#nullable disable
using System;
using System.ComponentModel;
namespace osu.Game.Beatmaps.Timing
{
[Obsolete("Use osu.Game.Beatmaps.Timing.TimeSignature instead.")]
public enum TimeSignatures // can be removed 20220722
{
[Description("4/4")]
SimpleQuadruple = 4,
[Description("3/4")]
SimpleTriple = 3
}
}

View File

@ -1,51 +0,0 @@
// 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.
#nullable disable
using System.ComponentModel.DataAnnotations.Schema;
using osu.Game.Database;
namespace osu.Game.Configuration
{
[Table("Settings")]
public class DatabasedSetting : IHasPrimaryKey // can be removed 20220315.
{
public int ID { get; set; }
public bool IsManaged => ID > 0;
public int? RulesetID { get; set; }
public int? Variant { get; set; }
public int? SkinInfoID { get; set; }
[Column("Key")]
public string Key { get; set; }
[Column("Value")]
public string StringValue
{
get => Value.ToString();
set => Value = value;
}
public object Value;
public DatabasedSetting(string key, object value)
{
Key = key;
Value = value;
}
/// <summary>
/// Constructor for derived classes that may require serialisation.
/// </summary>
public DatabasedSetting()
{
}
public override string ToString() => $"{Key}=>{Value}";
}
}

View File

@ -118,7 +118,6 @@ namespace osu.Game.Configuration
SetDefault(OsuSetting.Prefer24HourTime, CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern.Contains(@"tt"));
// Gameplay
SetDefault(OsuSetting.PositionalHitsounds, true); // replaced by level setting below, can be removed 20220703.
SetDefault(OsuSetting.PositionalHitsoundsLevel, 0.2f, 0, 1);
SetDefault(OsuSetting.DimLevel, 0.7, 0, 1, 0.01);
SetDefault(OsuSetting.BlurLevel, 0, 0, 1, 0.01);
@ -127,7 +126,6 @@ namespace osu.Game.Configuration
SetDefault(OsuSetting.HitLighting, true);
SetDefault(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Always);
SetDefault(OsuSetting.ShowProgressGraph, true);
SetDefault(OsuSetting.ShowHealthDisplayWhenCantFail, true);
SetDefault(OsuSetting.FadePlayfieldWhenHealthLow, true);
SetDefault(OsuSetting.KeyOverlay, false);
@ -203,14 +201,11 @@ namespace osu.Game.Configuration
if (!int.TryParse(pieces[0], out int year)) return;
if (!int.TryParse(pieces[1], out int monthDay)) return;
// ReSharper disable once UnusedVariable
int combined = (year * 10000) + monthDay;
if (combined < 20220103)
{
var positionalHitsoundsEnabled = GetBindable<bool>(OsuSetting.PositionalHitsounds);
if (!positionalHitsoundsEnabled.Value)
SetValue(OsuSetting.PositionalHitsoundsLevel, 0);
}
// migrations can be added here using a condition like:
// if (combined < 20220103) { performMigration() }
}
public override TrackedSettings CreateTrackedSettings()
@ -296,14 +291,11 @@ namespace osu.Game.Configuration
ShowStoryboard,
KeyOverlay,
GameplayLeaderboard,
PositionalHitsounds,
PositionalHitsoundsLevel,
AlwaysPlayFirstComboBreak,
FloatingComments,
HUDVisibilityMode,
// This has been migrated to the component itself. can be removed 20221027.
ShowProgressGraph,
ShowHealthDisplayWhenCantFail,
FadePlayfieldWhenHealthLow,
MouseDisableButtons,

View File

@ -857,17 +857,7 @@ namespace osu.Game.Database
if (legacyCollectionImporter.GetAvailableCount(storage).GetResultSafely() > 0)
{
legacyCollectionImporter.ImportFromStorage(storage).ContinueWith(task =>
{
if (task.Exception != null)
{
// can be removed 20221027 (just for initial safety).
Logger.Error(task.Exception.InnerException, "Collections could not be migrated to realm. Please provide your \"collection.db\" to the dev team.");
return;
}
storage.Move("collection.db", "collection.db.migrated");
});
legacyCollectionImporter.ImportFromStorage(storage).ContinueWith(_ => storage.Move("collection.db", "collection.db.migrated"));
}
break;

View File

@ -280,10 +280,7 @@ namespace osu.Game
configRuleset = LocalConfig.GetBindable<string>(OsuSetting.Ruleset);
uiScale = LocalConfig.GetBindable<float>(OsuSetting.UIScale);
var preferredRuleset = int.TryParse(configRuleset.Value, out int rulesetId)
// int parsing can be removed 20220522
? RulesetStore.GetRuleset(rulesetId)
: RulesetStore.GetRuleset(configRuleset.Value);
var preferredRuleset = RulesetStore.GetRuleset(configRuleset.Value);
try
{

View File

@ -1,18 +0,0 @@
// 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 System;
using System.Collections.Generic;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Mods
{
[Obsolete(@"Use the singular version IApplicableToDrawableHitObject instead.")] // Can be removed 20211216
public interface IApplicableToDrawableHitObjects : IApplicableToDrawableHitObject
{
void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables);
void IApplicableToDrawableHitObject.ApplyToDrawableHitObject(DrawableHitObject drawable) => ApplyToDrawableHitObjects(drawable.Yield());
}
}

View File

@ -1,22 +0,0 @@
// 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 System;
using System.Collections.Generic;
using osu.Game.Beatmaps;
using osu.Game.Scoring;
namespace osu.Game.Rulesets.Mods
{
[Obsolete("Use ICreateReplayData instead")] // Can be removed 20220929
public interface ICreateReplay : ICreateReplayData
{
public Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods);
ModReplayData ICreateReplayData.CreateReplayData(IBeatmap beatmap, IReadOnlyList<Mod> mods)
{
var replayScore = CreateReplayScore(beatmap, mods);
return new ModReplayData(replayScore.Replay, new ModCreatedUser { Username = replayScore.ScoreInfo.User.Username });
}
}
}

View File

@ -101,9 +101,6 @@ namespace osu.Game.Rulesets.Mods
[JsonIgnore]
public virtual bool ValidForMultiplayerAsFreeMod => true;
[Obsolete("Going forward, the concept of \"ranked\" doesn't exist. The only exceptions are automation mods, which should now override and set UserPlayable to false.")] // Can be removed 20211009
public virtual bool Ranked => false;
/// <summary>
/// Whether this mod requires configuration to apply changes to the game.
/// </summary>

View File

@ -8,7 +8,6 @@ using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Replays;
using osu.Game.Scoring;
namespace osu.Game.Rulesets.Mods
{
@ -33,16 +32,6 @@ namespace osu.Game.Rulesets.Mods
public override bool HasImplementation => GetType().GenericTypeArguments.Length == 0;
[Obsolete("Override CreateReplayData(IBeatmap, IReadOnlyList<Mod>) instead")] // Can be removed 20220929
public virtual Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score { Replay = new Replay() };
public virtual ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList<Mod> mods)
{
#pragma warning disable CS0618
var replayScore = CreateReplayScore(beatmap, mods);
#pragma warning restore CS0618
return new ModReplayData(replayScore.Replay, new ModCreatedUser { Username = replayScore.ScoreInfo.User.Username });
}
public virtual ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new ModReplayData(new Replay(), new ModCreatedUser { Username = @"autoplay" });
}
}

View File

@ -196,18 +196,6 @@ namespace osu.Game.Rulesets.Objects.Drawables
updateState(State.Value, true);
}
/// <summary>
/// Applies a hit object to be represented by this <see cref="DrawableHitObject"/>.
/// </summary>
[Obsolete("Use either overload of Apply that takes a single argument of type HitObject or HitObjectLifetimeEntry")] // Can be removed 20211021.
public void Apply([NotNull] HitObject hitObject, [CanBeNull] HitObjectLifetimeEntry lifetimeEntry)
{
if (lifetimeEntry != null)
Apply(lifetimeEntry);
else
Apply(hitObject);
}
/// <summary>
/// Applies a new <see cref="HitObject"/> to be represented by this <see cref="DrawableHitObject"/>.
/// A new <see cref="HitObjectLifetimeEntry"/> is automatically created and applied to this <see cref="DrawableHitObject"/>.

View File

@ -9,7 +9,6 @@ using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.UI;
using osu.Game.Skinning;
using osuTK;
namespace osu.Game.Screens.Play.HUD
@ -45,12 +44,6 @@ namespace osu.Game.Screens.Play.HUD
[Resolved]
private DrawableRuleset? drawableRuleset { get; set; }
[Resolved]
private OsuConfigManager config { get; set; } = null!;
[Resolved]
private SkinManager skinManager { get; set; } = null!;
public DefaultSongProgress()
{
RelativeSizeAxes = Axes.X;
@ -100,47 +93,6 @@ namespace osu.Game.Screens.Play.HUD
{
AllowSeeking.BindValueChanged(_ => updateBarVisibility(), true);
ShowGraph.BindValueChanged(_ => updateGraphVisibility(), true);
migrateSettingFromConfig();
}
/// <summary>
/// This setting has been migrated to a per-component level.
/// Only take the value from the config if it is in a non-default state (then reset it to default so it only applies once).
///
/// Can be removed 20221027.
/// </summary>
private void migrateSettingFromConfig()
{
Bindable<bool> configShowGraph = config.GetBindable<bool>(OsuSetting.ShowProgressGraph);
if (!configShowGraph.IsDefault)
{
ShowGraph.Value = configShowGraph.Value;
// This is pretty ugly, but the only way to make this stick...
var skinnableTarget = this.FindClosestParent<ISkinnableTarget>();
if (skinnableTarget != null)
{
// If the skin is not mutable, a mutable instance will be created, causing this migration logic to run again on the correct skin.
// Therefore we want to avoid resetting the config value on this invocation.
if (skinManager.EnsureMutableSkin())
return;
// If `EnsureMutableSkin` actually changed the skin, default layout may take a frame to apply.
// See `SkinnableTargetComponentsContainer`'s use of ScheduleAfterChildren.
ScheduleAfterChildren(() =>
{
var skin = skinManager.CurrentSkin.Value;
skin.UpdateDrawableTarget(skinnableTarget);
skinManager.Save(skin);
});
configShowGraph.SetDefault();
}
}
}
protected override void PopIn()

View File

@ -36,12 +36,6 @@ namespace osu.Game.Screens.Ranking.Statistics
/// </summary>
public readonly bool RequiresHitEvents;
[Obsolete("Use constructor which takes creation function instead.")] // Can be removed 20220803.
public StatisticItem([NotNull] string name, [NotNull] Drawable content, [CanBeNull] Dimension dimension = null)
: this(name, () => content, true, dimension)
{
}
/// <summary>
/// Creates a new <see cref="StatisticItem"/>, to be displayed inside a <see cref="StatisticRow"/> in the results screen.
/// </summary>

View File

@ -66,8 +66,6 @@ namespace osu.Game.Skinning
}
}
void IHasComboColours.AddComboColours(params Color4[] colours) => CustomComboColours.AddRange(colours);
public Dictionary<string, Color4> CustomColours { get; } = new Dictionary<string, Color4>();
public readonly Dictionary<string, string> ConfigDictionary = new Dictionary<string, string>();

View File

@ -4,11 +4,9 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using Newtonsoft.Json;
using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Game.Beatmaps;
using osu.Game.Database;
@ -33,9 +31,6 @@ namespace osu.Game.Skinning
this.skinResources = skinResources;
modelManager = new ModelManager<SkinInfo>(storage, realm);
// can be removed 20220420.
populateMissingHashes();
}
public override IEnumerable<string> HandledExtensions => new[] { ".osk" };
@ -158,18 +153,6 @@ namespace osu.Game.Skinning
}
modelManager.ReplaceFile(existingFile, stream, realm);
// can be removed 20220502.
if (!ensureIniWasUpdated(item))
{
Logger.Log($"Skin {item}'s skin.ini had issues and has been removed. Please report this and provide the problematic skin.", LoggingTarget.Database, LogLevel.Important);
var existingIni = item.GetFile(@"skin.ini");
if (existingIni != null)
item.Files.Remove(existingIni);
writeNewSkinIni();
}
}
}
@ -194,38 +177,6 @@ namespace osu.Game.Skinning
}
}
private bool ensureIniWasUpdated(SkinInfo item)
{
// This is a final consistency check to ensure that hash computation doesn't enter an infinite loop.
// With other changes to the surrounding code this should never be hit, but until we are 101% sure that there
// are no other cases let's avoid a hard startup crash by bailing and alerting.
var instance = createInstance(item);
return instance.Configuration.SkinInfo.Name == item.Name;
}
private void populateMissingHashes()
{
Realm.Run(realm =>
{
var skinsWithoutHashes = realm.All<SkinInfo>().Where(i => !i.Protected && string.IsNullOrEmpty(i.Hash)).ToArray();
foreach (SkinInfo skin in skinsWithoutHashes)
{
try
{
realm.Write(_ => skin.Hash = ComputeHash(skin));
}
catch (Exception e)
{
modelManager.Delete(skin);
Logger.Error(e, $"Existing skin {skin} has been deleted during hash recomputation due to being invalid");
}
}
});
}
private Skin createInstance(SkinInfo item) => item.CreateInstance(skinResources);
public void Save(Skin skin)