mirror of
https://github.com/ppy/osu.git
synced 2024-12-14 17:32:54 +08:00
Merge branch 'master' into extract-random-mod-logic-2
This commit is contained in:
commit
ff4745be59
36
Gemfile.lock
36
Gemfile.lock
@ -8,17 +8,17 @@ GEM
|
|||||||
artifactory (3.0.15)
|
artifactory (3.0.15)
|
||||||
atomos (0.1.3)
|
atomos (0.1.3)
|
||||||
aws-eventstream (1.2.0)
|
aws-eventstream (1.2.0)
|
||||||
aws-partitions (1.553.0)
|
aws-partitions (1.570.0)
|
||||||
aws-sdk-core (3.126.0)
|
aws-sdk-core (3.130.0)
|
||||||
aws-eventstream (~> 1, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
aws-partitions (~> 1, >= 1.525.0)
|
aws-partitions (~> 1, >= 1.525.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
jmespath (~> 1.0)
|
jmespath (~> 1.0)
|
||||||
aws-sdk-kms (1.54.0)
|
aws-sdk-kms (1.55.0)
|
||||||
aws-sdk-core (~> 3, >= 3.126.0)
|
aws-sdk-core (~> 3, >= 3.127.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
aws-sdk-s3 (1.112.0)
|
aws-sdk-s3 (1.113.0)
|
||||||
aws-sdk-core (~> 3, >= 3.126.0)
|
aws-sdk-core (~> 3, >= 3.127.0)
|
||||||
aws-sdk-kms (~> 1)
|
aws-sdk-kms (~> 1)
|
||||||
aws-sigv4 (~> 1.4)
|
aws-sigv4 (~> 1.4)
|
||||||
aws-sigv4 (1.4.0)
|
aws-sigv4 (1.4.0)
|
||||||
@ -36,8 +36,8 @@ GEM
|
|||||||
unf (>= 0.0.5, < 1.0.0)
|
unf (>= 0.0.5, < 1.0.0)
|
||||||
dotenv (2.7.6)
|
dotenv (2.7.6)
|
||||||
emoji_regex (3.2.3)
|
emoji_regex (3.2.3)
|
||||||
excon (0.91.0)
|
excon (0.92.1)
|
||||||
faraday (1.9.3)
|
faraday (1.10.0)
|
||||||
faraday-em_http (~> 1.0)
|
faraday-em_http (~> 1.0)
|
||||||
faraday-em_synchrony (~> 1.0)
|
faraday-em_synchrony (~> 1.0)
|
||||||
faraday-excon (~> 1.1)
|
faraday-excon (~> 1.1)
|
||||||
@ -66,7 +66,7 @@ GEM
|
|||||||
faraday_middleware (1.2.0)
|
faraday_middleware (1.2.0)
|
||||||
faraday (~> 1.0)
|
faraday (~> 1.0)
|
||||||
fastimage (2.2.6)
|
fastimage (2.2.6)
|
||||||
fastlane (2.204.2)
|
fastlane (2.205.1)
|
||||||
CFPropertyList (>= 2.3, < 4.0.0)
|
CFPropertyList (>= 2.3, < 4.0.0)
|
||||||
addressable (>= 2.8, < 3.0.0)
|
addressable (>= 2.8, < 3.0.0)
|
||||||
artifactory (~> 3.0)
|
artifactory (~> 3.0)
|
||||||
@ -130,10 +130,10 @@ GEM
|
|||||||
google-cloud-core (1.6.0)
|
google-cloud-core (1.6.0)
|
||||||
google-cloud-env (~> 1.0)
|
google-cloud-env (~> 1.0)
|
||||||
google-cloud-errors (~> 1.0)
|
google-cloud-errors (~> 1.0)
|
||||||
google-cloud-env (1.5.0)
|
google-cloud-env (1.6.0)
|
||||||
faraday (>= 0.17.3, < 2.0)
|
faraday (>= 0.17.3, < 3.0)
|
||||||
google-cloud-errors (1.2.0)
|
google-cloud-errors (1.2.0)
|
||||||
google-cloud-storage (1.36.0)
|
google-cloud-storage (1.36.1)
|
||||||
addressable (~> 2.8)
|
addressable (~> 2.8)
|
||||||
digest-crc (~> 0.4)
|
digest-crc (~> 0.4)
|
||||||
google-apis-iamcredentials_v1 (~> 0.1)
|
google-apis-iamcredentials_v1 (~> 0.1)
|
||||||
@ -141,8 +141,8 @@ GEM
|
|||||||
google-cloud-core (~> 1.6)
|
google-cloud-core (~> 1.6)
|
||||||
googleauth (>= 0.16.2, < 2.a)
|
googleauth (>= 0.16.2, < 2.a)
|
||||||
mini_mime (~> 1.0)
|
mini_mime (~> 1.0)
|
||||||
googleauth (1.1.0)
|
googleauth (1.1.2)
|
||||||
faraday (>= 0.17.3, < 2.0)
|
faraday (>= 0.17.3, < 3.a)
|
||||||
jwt (>= 1.4, < 3.0)
|
jwt (>= 1.4, < 3.0)
|
||||||
memoist (~> 0.16)
|
memoist (~> 0.16)
|
||||||
multi_json (~> 1.11)
|
multi_json (~> 1.11)
|
||||||
@ -152,7 +152,7 @@ GEM
|
|||||||
http-cookie (1.0.4)
|
http-cookie (1.0.4)
|
||||||
domain_name (~> 0.5)
|
domain_name (~> 0.5)
|
||||||
httpclient (2.8.3)
|
httpclient (2.8.3)
|
||||||
jmespath (1.5.0)
|
jmespath (1.6.1)
|
||||||
json (2.6.1)
|
json (2.6.1)
|
||||||
jwt (2.3.0)
|
jwt (2.3.0)
|
||||||
memoist (0.16.2)
|
memoist (0.16.2)
|
||||||
@ -182,9 +182,9 @@ GEM
|
|||||||
ruby2_keywords (0.0.5)
|
ruby2_keywords (0.0.5)
|
||||||
rubyzip (2.3.2)
|
rubyzip (2.3.2)
|
||||||
security (0.1.3)
|
security (0.1.3)
|
||||||
signet (0.16.0)
|
signet (0.16.1)
|
||||||
addressable (~> 2.8)
|
addressable (~> 2.8)
|
||||||
faraday (>= 0.17.3, < 2.0)
|
faraday (>= 0.17.5, < 3.0)
|
||||||
jwt (>= 1.5, < 3.0)
|
jwt (>= 1.5, < 3.0)
|
||||||
multi_json (~> 1.10)
|
multi_json (~> 1.10)
|
||||||
simctl (1.6.8)
|
simctl (1.6.8)
|
||||||
@ -205,7 +205,7 @@ GEM
|
|||||||
uber (0.1.0)
|
uber (0.1.0)
|
||||||
unf (0.1.4)
|
unf (0.1.4)
|
||||||
unf_ext
|
unf_ext
|
||||||
unf_ext (0.0.8)
|
unf_ext (0.0.8.1)
|
||||||
unicode-display_width (1.8.0)
|
unicode-display_width (1.8.0)
|
||||||
webrick (1.7.0)
|
webrick (1.7.0)
|
||||||
word_wrap (1.0.0)
|
word_wrap (1.0.0)
|
||||||
|
@ -3,22 +3,14 @@
|
|||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
|
||||||
using osu.Game.Rulesets.EmptyFreeform.Replays;
|
using osu.Game.Rulesets.EmptyFreeform.Replays;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Scoring;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.EmptyFreeform.Mods
|
namespace osu.Game.Rulesets.EmptyFreeform.Mods
|
||||||
{
|
{
|
||||||
public class EmptyFreeformModAutoplay : ModAutoplay
|
public class EmptyFreeformModAutoplay : ModAutoplay
|
||||||
{
|
{
|
||||||
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
|
public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList<Mod> mods)
|
||||||
{
|
=> new ModReplayData(new EmptyFreeformAutoGenerator(beatmap).Generate(), new ModCreatedUser { Username = "sample" });
|
||||||
ScoreInfo = new ScoreInfo
|
|
||||||
{
|
|
||||||
User = new APIUser { Username = "sample" },
|
|
||||||
},
|
|
||||||
Replay = new EmptyFreeformAutoGenerator(beatmap).Generate(),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,22 +3,14 @@
|
|||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Pippidon.Replays;
|
using osu.Game.Rulesets.Pippidon.Replays;
|
||||||
using osu.Game.Scoring;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Pippidon.Mods
|
namespace osu.Game.Rulesets.Pippidon.Mods
|
||||||
{
|
{
|
||||||
public class PippidonModAutoplay : ModAutoplay
|
public class PippidonModAutoplay : ModAutoplay
|
||||||
{
|
{
|
||||||
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
|
public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList<Mod> mods)
|
||||||
{
|
=> new ModReplayData(new PippidonAutoGenerator(beatmap).Generate(), new ModCreatedUser { Username = "sample" });
|
||||||
ScoreInfo = new ScoreInfo
|
|
||||||
{
|
|
||||||
User = new APIUser { Username = "sample" },
|
|
||||||
},
|
|
||||||
Replay = new PippidonAutoGenerator(beatmap).Generate(),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,24 +1,16 @@
|
|||||||
// 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 osu.Game.Beatmaps;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
|
||||||
using osu.Game.Rulesets.EmptyScrolling.Replays;
|
|
||||||
using osu.Game.Scoring;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.EmptyScrolling.Replays;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.EmptyScrolling.Mods
|
namespace osu.Game.Rulesets.EmptyScrolling.Mods
|
||||||
{
|
{
|
||||||
public class EmptyScrollingModAutoplay : ModAutoplay
|
public class EmptyScrollingModAutoplay : ModAutoplay
|
||||||
{
|
{
|
||||||
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
|
public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList<Mod> mods)
|
||||||
{
|
=> new ModReplayData(new EmptyScrollingAutoGenerator(beatmap).Generate(), new ModCreatedUser { Username = "sample" });
|
||||||
ScoreInfo = new ScoreInfo
|
|
||||||
{
|
|
||||||
User = new APIUser { Username = "sample" },
|
|
||||||
},
|
|
||||||
Replay = new EmptyScrollingAutoGenerator(beatmap).Generate(),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,22 +3,14 @@
|
|||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Pippidon.Replays;
|
using osu.Game.Rulesets.Pippidon.Replays;
|
||||||
using osu.Game.Scoring;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Pippidon.Mods
|
namespace osu.Game.Rulesets.Pippidon.Mods
|
||||||
{
|
{
|
||||||
public class PippidonModAutoplay : ModAutoplay
|
public class PippidonModAutoplay : ModAutoplay
|
||||||
{
|
{
|
||||||
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
|
public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList<Mod> mods)
|
||||||
{
|
=> new ModReplayData(new PippidonAutoGenerator(beatmap).Generate(), new ModCreatedUser { Username = "sample" });
|
||||||
ScoreInfo = new ScoreInfo
|
|
||||||
{
|
|
||||||
User = new APIUser { Username = "sample" },
|
|
||||||
},
|
|
||||||
Replay = new PippidonAutoGenerator(beatmap).Generate(),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ namespace osu.Game.Benchmarks
|
|||||||
storage = new TemporaryNativeStorage("realm-benchmark");
|
storage = new TemporaryNativeStorage("realm-benchmark");
|
||||||
storage.DeleteDirectory(string.Empty);
|
storage.DeleteDirectory(string.Empty);
|
||||||
|
|
||||||
realm = new RealmAccess(storage, "client");
|
realm = new RealmAccess(storage, OsuGameBase.CLIENT_DATABASE_FILENAME);
|
||||||
|
|
||||||
realm.Run(r =>
|
realm.Run(r =>
|
||||||
{
|
{
|
||||||
|
@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
{
|
{
|
||||||
public TestLegacySkin(SkinInfo skin, IResourceStore<byte[]> storage)
|
public TestLegacySkin(SkinInfo skin, IResourceStore<byte[]> storage)
|
||||||
// Bypass LegacySkinResourceStore to avoid returning null for retrieving files due to bad skin info (SkinInfo.Files = null).
|
// Bypass LegacySkinResourceStore to avoid returning null for retrieving files due to bad skin info (SkinInfo.Files = null).
|
||||||
: base(skin, storage, null, "skin.ini")
|
: base(skin, null, storage)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,19 +3,14 @@
|
|||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
|
||||||
using osu.Game.Rulesets.Catch.Replays;
|
using osu.Game.Rulesets.Catch.Replays;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Scoring;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Mods
|
namespace osu.Game.Rulesets.Catch.Mods
|
||||||
{
|
{
|
||||||
public class CatchModAutoplay : ModAutoplay
|
public class CatchModAutoplay : ModAutoplay
|
||||||
{
|
{
|
||||||
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
|
public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList<Mod> mods)
|
||||||
{
|
=> new ModReplayData(new CatchAutoGenerator(beatmap).Generate(), new ModCreatedUser { Username = "osu!salad" });
|
||||||
ScoreInfo = new ScoreInfo { User = new APIUser { Username = "osu!salad" } },
|
|
||||||
Replay = new CatchAutoGenerator(beatmap).Generate(),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,20 +3,15 @@
|
|||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
using osu.Game.Rulesets.Catch.Replays;
|
using osu.Game.Rulesets.Catch.Replays;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Scoring;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Mods
|
namespace osu.Game.Rulesets.Catch.Mods
|
||||||
{
|
{
|
||||||
public class CatchModCinema : ModCinema<CatchHitObject>
|
public class CatchModCinema : ModCinema<CatchHitObject>
|
||||||
{
|
{
|
||||||
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
|
public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList<Mod> mods)
|
||||||
{
|
=> new ModReplayData(new CatchAutoGenerator(beatmap).Generate(), new ModCreatedUser { Username = "osu!salad" });
|
||||||
ScoreInfo = new ScoreInfo { User = new APIUser { Username = "osu!salad" } },
|
|
||||||
Replay = new CatchAutoGenerator(beatmap).Generate(),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,20 +3,15 @@
|
|||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
|
||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mania.Replays;
|
using osu.Game.Rulesets.Mania.Replays;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Scoring;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Mods
|
namespace osu.Game.Rulesets.Mania.Mods
|
||||||
{
|
{
|
||||||
public class ManiaModAutoplay : ModAutoplay
|
public class ManiaModAutoplay : ModAutoplay
|
||||||
{
|
{
|
||||||
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
|
public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList<Mod> mods)
|
||||||
{
|
=> new ModReplayData(new ManiaAutoGenerator((ManiaBeatmap)beatmap).Generate(), new ModCreatedUser { Username = "osu!topus" });
|
||||||
ScoreInfo = new ScoreInfo { User = new APIUser { Username = "osu!topus" } },
|
|
||||||
Replay = new ManiaAutoGenerator((ManiaBeatmap)beatmap).Generate(),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,21 +3,16 @@
|
|||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
|
||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Mania.Replays;
|
using osu.Game.Rulesets.Mania.Replays;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Scoring;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Mods
|
namespace osu.Game.Rulesets.Mania.Mods
|
||||||
{
|
{
|
||||||
public class ManiaModCinema : ModCinema<ManiaHitObject>
|
public class ManiaModCinema : ModCinema<ManiaHitObject>
|
||||||
{
|
{
|
||||||
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
|
public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList<Mod> mods)
|
||||||
{
|
=> new ModReplayData(new ManiaAutoGenerator((ManiaBeatmap)beatmap).Generate(), new ModCreatedUser { Username = "osu!topus" });
|
||||||
ScoreInfo = new ScoreInfo { User = new APIUser { Username = "osu!topus" } },
|
|
||||||
Replay = new ManiaAutoGenerator((ManiaBeatmap)beatmap).Generate(),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
|
||||||
using osu.Game.Replays;
|
using osu.Game.Replays;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||||
@ -13,7 +12,6 @@ using osu.Game.Rulesets.Osu.Objects;
|
|||||||
using osu.Game.Rulesets.Osu.Replays;
|
using osu.Game.Rulesets.Osu.Replays;
|
||||||
using osu.Game.Rulesets.Osu.Scoring;
|
using osu.Game.Rulesets.Osu.Scoring;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Scoring;
|
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@ -67,11 +65,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
|
|
||||||
private class TestAutoMod : OsuModAutoplay
|
private class TestAutoMod : OsuModAutoplay
|
||||||
{
|
{
|
||||||
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
|
public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList<Mod> mods)
|
||||||
{
|
=> new ModReplayData(new MissingAutoGenerator(beatmap, mods).Generate(), new ModCreatedUser { Username = "Autoplay" });
|
||||||
ScoreInfo = new ScoreInfo { User = new APIUser { Username = "Autoplay" } },
|
|
||||||
Replay = new MissingAutoGenerator(beatmap, mods).Generate()
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class MissingAutoGenerator : OsuAutoGeneratorBase
|
private class MissingAutoGenerator : OsuAutoGeneratorBase
|
||||||
|
@ -5,10 +5,8 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Osu.Replays;
|
using osu.Game.Rulesets.Osu.Replays;
|
||||||
using osu.Game.Scoring;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Mods
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
{
|
{
|
||||||
@ -16,10 +14,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
{
|
{
|
||||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAimAssist), typeof(OsuModAutopilot), typeof(OsuModSpunOut) }).ToArray();
|
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAimAssist), typeof(OsuModAutopilot), typeof(OsuModSpunOut) }).ToArray();
|
||||||
|
|
||||||
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
|
public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList<Mod> mods)
|
||||||
{
|
=> new ModReplayData(new OsuAutoGenerator(beatmap, mods).Generate(), new ModCreatedUser { Username = "Autoplay" });
|
||||||
ScoreInfo = new ScoreInfo { User = new APIUser { Username = "Autoplay" } },
|
|
||||||
Replay = new OsuAutoGenerator(beatmap, mods).Generate()
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,11 +5,9 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Replays;
|
using osu.Game.Rulesets.Osu.Replays;
|
||||||
using osu.Game.Scoring;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Mods
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
{
|
{
|
||||||
@ -17,10 +15,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
{
|
{
|
||||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAimAssist), typeof(OsuModAutopilot), typeof(OsuModSpunOut) }).ToArray();
|
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAimAssist), typeof(OsuModAutopilot), typeof(OsuModSpunOut) }).ToArray();
|
||||||
|
|
||||||
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
|
public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList<Mod> mods)
|
||||||
{
|
=> new ModReplayData(new OsuAutoGenerator(beatmap, mods).Generate(), new ModCreatedUser { Username = "Autoplay" });
|
||||||
ScoreInfo = new ScoreInfo { User = new APIUser { Username = "Autoplay" } },
|
|
||||||
Replay = new OsuAutoGenerator(beatmap, mods).Generate()
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,19 +3,14 @@
|
|||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Taiko.Replays;
|
using osu.Game.Rulesets.Taiko.Replays;
|
||||||
using osu.Game.Scoring;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Mods
|
namespace osu.Game.Rulesets.Taiko.Mods
|
||||||
{
|
{
|
||||||
public class TaikoModAutoplay : ModAutoplay
|
public class TaikoModAutoplay : ModAutoplay
|
||||||
{
|
{
|
||||||
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
|
public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList<Mod> mods)
|
||||||
{
|
=> new ModReplayData(new TaikoAutoGenerator(beatmap).Generate(), new ModCreatedUser { Username = "mekkadosu!" });
|
||||||
ScoreInfo = new ScoreInfo { User = new APIUser { Username = "mekkadosu!" } },
|
|
||||||
Replay = new TaikoAutoGenerator(beatmap).Generate(),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,20 +3,15 @@
|
|||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Taiko.Objects;
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
using osu.Game.Rulesets.Taiko.Replays;
|
using osu.Game.Rulesets.Taiko.Replays;
|
||||||
using osu.Game.Scoring;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Mods
|
namespace osu.Game.Rulesets.Taiko.Mods
|
||||||
{
|
{
|
||||||
public class TaikoModCinema : ModCinema<TaikoHitObject>
|
public class TaikoModCinema : ModCinema<TaikoHitObject>
|
||||||
{
|
{
|
||||||
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
|
public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList<Mod> mods)
|
||||||
{
|
=> new ModReplayData(new TaikoAutoGenerator(beatmap).Generate(), new ModCreatedUser { Username = "mekkadosu!" });
|
||||||
ScoreInfo = new ScoreInfo { User = new APIUser { Username = "mekkadosu!" } },
|
|
||||||
Replay = new TaikoAutoGenerator(beatmap).Generate(),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -175,7 +175,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
private class TestLegacySkin : LegacySkin
|
private class TestLegacySkin : LegacySkin
|
||||||
{
|
{
|
||||||
public TestLegacySkin(IResourceStore<byte[]> storage, string fileName)
|
public TestLegacySkin(IResourceStore<byte[]> storage, string fileName)
|
||||||
: base(new SkinInfo { Name = "Test Skin", Creator = "Craftplacer" }, storage, null, fileName)
|
: base(new SkinInfo { Name = "Test Skin", Creator = "Craftplacer" }, null, storage, fileName)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@ namespace osu.Game.Tests.Database
|
|||||||
// ReSharper disable once AccessToDisposedClosure
|
// ReSharper disable once AccessToDisposedClosure
|
||||||
var testStorage = new OsuStorage(host, storage.GetStorageForDirectory(caller));
|
var testStorage = new OsuStorage(host, storage.GetStorageForDirectory(caller));
|
||||||
|
|
||||||
using (var realm = new RealmAccess(testStorage, "client"))
|
using (var realm = new RealmAccess(testStorage, OsuGameBase.CLIENT_DATABASE_FILENAME))
|
||||||
{
|
{
|
||||||
Logger.Log($"Running test using realm file {testStorage.GetFullPath(realm.Filename)}");
|
Logger.Log($"Running test using realm file {testStorage.GetFullPath(realm.Filename)}");
|
||||||
testAction(realm, testStorage);
|
testAction(realm, testStorage);
|
||||||
@ -62,7 +62,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
var testStorage = storage.GetStorageForDirectory(caller);
|
var testStorage = storage.GetStorageForDirectory(caller);
|
||||||
|
|
||||||
using (var realm = new RealmAccess(testStorage, "client"))
|
using (var realm = new RealmAccess(testStorage, OsuGameBase.CLIENT_DATABASE_FILENAME))
|
||||||
{
|
{
|
||||||
Logger.Log($"Running test using realm file {testStorage.GetFullPath(realm.Filename)}");
|
Logger.Log($"Running test using realm file {testStorage.GetFullPath(realm.Filename)}");
|
||||||
await testAction(realm, testStorage);
|
await testAction(realm, testStorage);
|
||||||
|
@ -148,7 +148,7 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
private class TestSkin : LegacySkin
|
private class TestSkin : LegacySkin
|
||||||
{
|
{
|
||||||
public TestSkin(string resourceName, IStorageResourceProvider resources)
|
public TestSkin(string resourceName, IStorageResourceProvider resources)
|
||||||
: base(DefaultLegacySkin.CreateInfo(), new TestResourceStore(resourceName), resources, "skin.ini")
|
: base(DefaultLegacySkin.CreateInfo(), resources, new TestResourceStore(resourceName))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -143,14 +143,14 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
Assert.That(osuStorage, Is.Not.Null);
|
Assert.That(osuStorage, Is.Not.Null);
|
||||||
|
|
||||||
// In the following tests, realm files are ignored as
|
// In the following tests, realm files are ignored as
|
||||||
// - in the case of checking the source, interacting with the pipe files (client.realm.note) may
|
// - in the case of checking the source, interacting with the pipe files (.realm.note) may
|
||||||
// lead to unexpected behaviour.
|
// lead to unexpected behaviour.
|
||||||
// - in the case of checking the destination, the files may have already been recreated by the game
|
// - in the case of checking the destination, the files may have already been recreated by the game
|
||||||
// as part of the standard migration flow.
|
// as part of the standard migration flow.
|
||||||
|
|
||||||
foreach (string file in osuStorage.IgnoreFiles)
|
foreach (string file in osuStorage.IgnoreFiles)
|
||||||
{
|
{
|
||||||
if (!file.Contains("realm", StringComparison.Ordinal))
|
if (!file.Contains(".realm", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
Assert.That(File.Exists(Path.Combine(originalDirectory, file)));
|
Assert.That(File.Exists(Path.Combine(originalDirectory, file)));
|
||||||
Assert.That(storage.Exists(file), Is.False, () => $"{file} exists in destination when it was expected to be ignored");
|
Assert.That(storage.Exists(file), Is.False, () => $"{file} exists in destination when it was expected to be ignored");
|
||||||
@ -159,7 +159,7 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
|
|
||||||
foreach (string dir in osuStorage.IgnoreDirectories)
|
foreach (string dir in osuStorage.IgnoreDirectories)
|
||||||
{
|
{
|
||||||
if (!dir.Contains("realm", StringComparison.Ordinal))
|
if (!dir.Contains(".realm", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
Assert.That(Directory.Exists(Path.Combine(originalDirectory, dir)));
|
Assert.That(Directory.Exists(Path.Combine(originalDirectory, dir)));
|
||||||
Assert.That(storage.Exists(dir), Is.False, () => $"{dir} exists in destination when it was expected to be ignored");
|
Assert.That(storage.Exists(dir), Is.False, () => $"{dir} exists in destination when it was expected to be ignored");
|
||||||
@ -188,19 +188,17 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
{
|
{
|
||||||
var osu = LoadOsuIntoHost(host);
|
var osu = LoadOsuIntoHost(host);
|
||||||
|
|
||||||
const string database_filename = "client.realm";
|
|
||||||
|
|
||||||
Assert.DoesNotThrow(() => osu.Migrate(customPath));
|
Assert.DoesNotThrow(() => osu.Migrate(customPath));
|
||||||
Assert.That(File.Exists(Path.Combine(customPath, database_filename)));
|
Assert.That(File.Exists(Path.Combine(customPath, OsuGameBase.CLIENT_DATABASE_FILENAME)));
|
||||||
|
|
||||||
Assert.DoesNotThrow(() => osu.Migrate(customPath2));
|
Assert.DoesNotThrow(() => osu.Migrate(customPath2));
|
||||||
Assert.That(File.Exists(Path.Combine(customPath2, database_filename)));
|
Assert.That(File.Exists(Path.Combine(customPath2, OsuGameBase.CLIENT_DATABASE_FILENAME)));
|
||||||
|
|
||||||
// some files may have been left behind for whatever reason, but that's not what we're testing here.
|
// some files may have been left behind for whatever reason, but that's not what we're testing here.
|
||||||
cleanupPath(customPath);
|
cleanupPath(customPath);
|
||||||
|
|
||||||
Assert.DoesNotThrow(() => osu.Migrate(customPath));
|
Assert.DoesNotThrow(() => osu.Migrate(customPath));
|
||||||
Assert.That(File.Exists(Path.Combine(customPath, database_filename)));
|
Assert.That(File.Exists(Path.Combine(customPath, OsuGameBase.CLIENT_DATABASE_FILENAME)));
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@ -233,6 +231,46 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMigrationFailsOnExistingData()
|
||||||
|
{
|
||||||
|
string customPath = prepareCustomPath();
|
||||||
|
string customPath2 = prepareCustomPath();
|
||||||
|
|
||||||
|
using (var host = new CustomTestHeadlessGameHost())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var osu = LoadOsuIntoHost(host);
|
||||||
|
|
||||||
|
var storage = osu.Dependencies.Get<Storage>();
|
||||||
|
var osuStorage = storage as OsuStorage;
|
||||||
|
|
||||||
|
string originalDirectory = storage.GetFullPath(".");
|
||||||
|
|
||||||
|
Assert.DoesNotThrow(() => osu.Migrate(customPath));
|
||||||
|
Assert.That(File.Exists(Path.Combine(customPath, OsuGameBase.CLIENT_DATABASE_FILENAME)));
|
||||||
|
|
||||||
|
Directory.CreateDirectory(customPath2);
|
||||||
|
File.Copy(Path.Combine(customPath, OsuGameBase.CLIENT_DATABASE_FILENAME), Path.Combine(customPath2, OsuGameBase.CLIENT_DATABASE_FILENAME));
|
||||||
|
|
||||||
|
// Fails because file already exists.
|
||||||
|
Assert.Throws<ArgumentException>(() => osu.Migrate(customPath2));
|
||||||
|
|
||||||
|
osuStorage?.ChangeDataPath(customPath2);
|
||||||
|
|
||||||
|
Assert.That(osuStorage?.CustomStoragePath, Is.EqualTo(customPath2));
|
||||||
|
Assert.That(new StreamReader(Path.Combine(originalDirectory, "storage.ini")).ReadToEnd().Contains($"FullPath = {customPath2}"));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
host.Exit();
|
||||||
|
cleanupPath(customPath);
|
||||||
|
cleanupPath(customPath2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestMigrationToNestedTargetFails()
|
public void TestMigrationToNestedTargetFails()
|
||||||
{
|
{
|
||||||
|
@ -1,12 +1,21 @@
|
|||||||
// 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.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Graphics.OpenGL.Textures;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
|
using osu.Framework.IO.Stores;
|
||||||
|
using osu.Game.Database;
|
||||||
|
using osu.Game.IO;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
using SixLabors.ImageSharp;
|
||||||
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
|
|
||||||
namespace osu.Game.Tests.NonVisual.Skinning
|
namespace osu.Game.Tests.NonVisual.Skinning
|
||||||
{
|
{
|
||||||
@ -71,7 +80,7 @@ namespace osu.Game.Tests.NonVisual.Skinning
|
|||||||
var texture = legacySkin.GetTexture(requestedComponent);
|
var texture = legacySkin.GetTexture(requestedComponent);
|
||||||
|
|
||||||
Assert.IsNotNull(texture);
|
Assert.IsNotNull(texture);
|
||||||
Assert.AreEqual(textureStore.Textures[expectedTexture], texture);
|
Assert.AreEqual(textureStore.Textures[expectedTexture].Width, texture.Width);
|
||||||
Assert.AreEqual(expectedScale, texture.ScaleAdjust);
|
Assert.AreEqual(expectedScale, texture.ScaleAdjust);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,23 +97,50 @@ namespace osu.Game.Tests.NonVisual.Skinning
|
|||||||
|
|
||||||
private class TestLegacySkin : LegacySkin
|
private class TestLegacySkin : LegacySkin
|
||||||
{
|
{
|
||||||
public TestLegacySkin(TextureStore textureStore)
|
public TestLegacySkin(IResourceStore<TextureUpload> textureStore)
|
||||||
: base(new SkinInfo(), null, null, string.Empty)
|
: base(new SkinInfo(), new TestResourceProvider(textureStore), null, string.Empty)
|
||||||
{
|
{
|
||||||
Textures = textureStore;
|
}
|
||||||
|
|
||||||
|
private class TestResourceProvider : IStorageResourceProvider
|
||||||
|
{
|
||||||
|
private readonly IResourceStore<TextureUpload> textureStore;
|
||||||
|
|
||||||
|
public TestResourceProvider(IResourceStore<TextureUpload> textureStore)
|
||||||
|
{
|
||||||
|
this.textureStore = textureStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AudioManager AudioManager => null;
|
||||||
|
public IResourceStore<byte[]> Files => null;
|
||||||
|
public IResourceStore<byte[]> Resources => null;
|
||||||
|
public RealmAccess RealmAccess => null;
|
||||||
|
public IResourceStore<TextureUpload> CreateTextureLoaderStore(IResourceStore<byte[]> underlyingStore) => textureStore;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestTextureStore : TextureStore
|
private class TestTextureStore : IResourceStore<TextureUpload>
|
||||||
{
|
{
|
||||||
public readonly Dictionary<string, Texture> Textures;
|
public readonly Dictionary<string, TextureUpload> Textures;
|
||||||
|
|
||||||
public TestTextureStore(params string[] fileNames)
|
public TestTextureStore(params string[] fileNames)
|
||||||
{
|
{
|
||||||
Textures = fileNames.ToDictionary(fileName => fileName, fileName => new Texture(1, 1));
|
// use an incrementing width to allow assertion matching on correct textures as they turn from uploads into actual textures.
|
||||||
|
int width = 1;
|
||||||
|
Textures = fileNames.ToDictionary(fileName => fileName, fileName => new TextureUpload(new Image<Rgba32>(width, width++)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Texture Get(string name, WrapMode wrapModeS, WrapMode wrapModeT) => Textures.GetValueOrDefault(name);
|
public TextureUpload Get(string name) => Textures.GetValueOrDefault(name);
|
||||||
|
|
||||||
|
public Task<TextureUpload> GetAsync(string name, CancellationToken cancellationToken = new CancellationToken()) => Task.FromResult(Get(name));
|
||||||
|
|
||||||
|
public Stream GetStream(string name) => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public IEnumerable<string> GetAvailableResources() => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -77,7 +77,7 @@ namespace osu.Game.Tests.Skins
|
|||||||
public class BeatmapSkinSource : LegacyBeatmapSkin
|
public class BeatmapSkinSource : LegacyBeatmapSkin
|
||||||
{
|
{
|
||||||
public BeatmapSkinSource()
|
public BeatmapSkinSource()
|
||||||
: base(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, null, null)
|
: base(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, null)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,7 +202,7 @@ namespace osu.Game.Tests.Skins
|
|||||||
public class BeatmapSkinSource : LegacyBeatmapSkin
|
public class BeatmapSkinSource : LegacyBeatmapSkin
|
||||||
{
|
{
|
||||||
public BeatmapSkinSource()
|
public BeatmapSkinSource()
|
||||||
: base(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, null, null)
|
: base(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, null)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,12 +5,14 @@ using System.ComponentModel;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps.Timing;
|
using osu.Game.Beatmaps.Timing;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Screens.Play.Break;
|
using osu.Game.Screens.Play.Break;
|
||||||
using osu.Game.Screens.Ranking;
|
using osu.Game.Screens.Ranking;
|
||||||
|
using osu.Game.Users.Drawables;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Gameplay
|
namespace osu.Game.Tests.Visual.Gameplay
|
||||||
{
|
{
|
||||||
@ -39,11 +41,18 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
seekToBreak(1);
|
seekToBreak(1);
|
||||||
|
|
||||||
AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime()));
|
AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime()));
|
||||||
AddUntilStep("results displayed", () => getResultsScreen() != null);
|
|
||||||
|
AddUntilStep("results displayed", () => getResultsScreen()?.IsLoaded == true);
|
||||||
|
|
||||||
AddAssert("score has combo", () => getResultsScreen().Score.Combo > 100);
|
AddAssert("score has combo", () => getResultsScreen().Score.Combo > 100);
|
||||||
AddAssert("score has no misses", () => getResultsScreen().Score.Statistics[HitResult.Miss] == 0);
|
AddAssert("score has no misses", () => getResultsScreen().Score.Statistics[HitResult.Miss] == 0);
|
||||||
|
|
||||||
|
AddUntilStep("avatar displayed", () => getAvatar() != null);
|
||||||
|
AddAssert("avatar not clickable", () => getAvatar().ChildrenOfType<OsuClickableContainer>().First().Action == null);
|
||||||
|
|
||||||
|
ClickableAvatar getAvatar() => getResultsScreen()
|
||||||
|
.ChildrenOfType<ClickableAvatar>().FirstOrDefault();
|
||||||
|
|
||||||
ResultsScreen getResultsScreen() => Stack.CurrentScreen as ResultsScreen;
|
ResultsScreen getResultsScreen() => Stack.CurrentScreen as ResultsScreen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestEmptyLegacyBeatmapSkinFallsBack()
|
public void TestEmptyLegacyBeatmapSkinFallsBack()
|
||||||
{
|
{
|
||||||
CreateSkinTest(DefaultSkin.CreateInfo(), () => new LegacyBeatmapSkin(new BeatmapInfo(), null, null));
|
CreateSkinTest(DefaultSkin.CreateInfo(), () => new LegacyBeatmapSkin(new BeatmapInfo(), null));
|
||||||
AddUntilStep("wait for hud load", () => Player.ChildrenOfType<SkinnableTargetContainer>().All(c => c.ComponentsLoaded));
|
AddUntilStep("wait for hud load", () => Player.ChildrenOfType<SkinnableTargetContainer>().All(c => c.ComponentsLoaded));
|
||||||
AddAssert("hud from default skin", () => AssertComponentsFromExpectedSource(SkinnableTarget.MainHUDComponents, skinManager.CurrentSkin.Value));
|
AddAssert("hud from default skin", () => AssertComponentsFromExpectedSource(SkinnableTarget.MainHUDComponents, skinManager.CurrentSkin.Value));
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
var beatmap = Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo, Array.Empty<Mod>());
|
var beatmap = Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo, Array.Empty<Mod>());
|
||||||
|
|
||||||
return new ScoreAccessibleReplayPlayer(ruleset.GetAutoplayMod()?.CreateReplayScore(beatmap, Array.Empty<Mod>()));
|
return new ScoreAccessibleReplayPlayer(ruleset.GetAutoplayMod()?.CreateScoreFromReplayData(beatmap, Array.Empty<Mod>()));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void AddCheckSteps()
|
protected override void AddCheckSteps()
|
||||||
|
@ -37,6 +37,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
Player.ScaleTo(0.4f);
|
Player.ScaleTo(0.4f);
|
||||||
LoadComponentAsync(skinEditor = new SkinEditor(Player), Add);
|
LoadComponentAsync(skinEditor = new SkinEditor(Player), Add);
|
||||||
});
|
});
|
||||||
|
AddUntilStep("wait for loaded", () => skinEditor.IsLoaded);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
80
osu.Game.Tests/Visual/Menus/TestSceneToolbarClock.cs
Normal file
80
osu.Game.Tests/Visual/Menus/TestSceneToolbarClock.cs
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Timing;
|
||||||
|
using osu.Game.Overlays.Toolbar;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Menus
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class TestSceneToolbarClock : OsuManualInputManagerTestScene
|
||||||
|
{
|
||||||
|
private readonly Container mainContainer;
|
||||||
|
|
||||||
|
public TestSceneToolbarClock()
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
mainContainer = new Container
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = Toolbar.HEIGHT,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Colour = Color4.Black,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
AutoSizeAxes = Axes.X,
|
||||||
|
Direction = FillDirection.Horizontal,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Colour = Color4.DarkRed,
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
Width = 2,
|
||||||
|
},
|
||||||
|
new ToolbarClock(),
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Colour = Color4.DarkRed,
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
Width = 2,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
AddSliderStep("scale", 0.5, 4, 1, scale => mainContainer.Scale = new Vector2((float)scale));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRealGameTime()
|
||||||
|
{
|
||||||
|
AddStep("Set game time real", () => mainContainer.Clock = Clock);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestLongGameTime()
|
||||||
|
{
|
||||||
|
AddStep("Set game time long", () => mainContainer.Clock = new FramedOffsetClock(Clock, false) { Offset = 3600.0 * 24 * 1000 * 98 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -67,7 +67,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Size = new Vector2(200, 50),
|
Size = new Vector2(250, 50),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -85,7 +85,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
InputManager.Click(MouseButton.Left);
|
InputManager.Click(MouseButton.Left);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddAssert("countdown button not visible", () => !this.ChildrenOfType<MultiplayerCountdownButton>().Single().IsPresent);
|
|
||||||
AddStep("finish countdown", () => MultiplayerClient.SkipToEndOfCountdown());
|
AddStep("finish countdown", () => MultiplayerClient.SkipToEndOfCountdown());
|
||||||
AddUntilStep("match started", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.WaitingForLoad);
|
AddUntilStep("match started", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.WaitingForLoad);
|
||||||
}
|
}
|
||||||
@ -103,7 +102,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
InputManager.Click(MouseButton.Left);
|
InputManager.Click(MouseButton.Left);
|
||||||
});
|
});
|
||||||
|
|
||||||
ClickButtonWhenEnabled<MultiplayerReadyButton>();
|
ClickButtonWhenEnabled<MultiplayerCountdownButton>();
|
||||||
|
AddStep("click the cancel button", () =>
|
||||||
|
{
|
||||||
|
var popoverButton = this.ChildrenOfType<Popover>().Single().ChildrenOfType<OsuButton>().Last();
|
||||||
|
InputManager.MoveMouseTo(popoverButton);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
AddStep("finish countdown", () => MultiplayerClient.SkipToEndOfCountdown());
|
AddStep("finish countdown", () => MultiplayerClient.SkipToEndOfCountdown());
|
||||||
AddUntilStep("match not started", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Ready);
|
AddUntilStep("match not started", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Ready);
|
||||||
@ -128,63 +133,21 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestCountdownButtonEnablementAndVisibilityWhileSpectating()
|
public void TestCountdownWhileSpectating()
|
||||||
{
|
{
|
||||||
AddStep("set spectating", () => MultiplayerClient.ChangeUserState(API.LocalUser.Value.OnlineID, MultiplayerUserState.Spectating));
|
AddStep("set spectating", () => MultiplayerClient.ChangeUserState(API.LocalUser.Value.OnlineID, MultiplayerUserState.Spectating));
|
||||||
AddUntilStep("local user is spectating", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Spectating);
|
AddUntilStep("local user is spectating", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Spectating);
|
||||||
|
|
||||||
AddAssert("countdown button is visible", () => this.ChildrenOfType<MultiplayerCountdownButton>().Single().IsPresent);
|
AddAssert("countdown button is visible", () => this.ChildrenOfType<MultiplayerCountdownButton>().Single().IsPresent);
|
||||||
AddAssert("countdown button disabled", () => !this.ChildrenOfType<MultiplayerCountdownButton>().Single().Enabled.Value);
|
AddAssert("countdown button enabled", () => this.ChildrenOfType<MultiplayerCountdownButton>().Single().Enabled.Value);
|
||||||
|
|
||||||
AddStep("add second user", () => MultiplayerClient.AddUser(new APIUser { Id = 2, Username = "Another user" }));
|
AddStep("add second user", () => MultiplayerClient.AddUser(new APIUser { Id = 2, Username = "Another user" }));
|
||||||
AddAssert("countdown button disabled", () => !this.ChildrenOfType<MultiplayerCountdownButton>().Single().Enabled.Value);
|
AddAssert("countdown button enabled", () => this.ChildrenOfType<MultiplayerCountdownButton>().Single().Enabled.Value);
|
||||||
|
|
||||||
AddStep("set second user ready", () => MultiplayerClient.ChangeUserState(2, MultiplayerUserState.Ready));
|
AddStep("set second user ready", () => MultiplayerClient.ChangeUserState(2, MultiplayerUserState.Ready));
|
||||||
AddAssert("countdown button enabled", () => this.ChildrenOfType<MultiplayerCountdownButton>().Single().Enabled.Value);
|
AddAssert("countdown button enabled", () => this.ChildrenOfType<MultiplayerCountdownButton>().Single().Enabled.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestSpectatingDuringCountdownWithNoReadyUsersCancelsCountdown()
|
|
||||||
{
|
|
||||||
ClickButtonWhenEnabled<MultiplayerReadyButton>();
|
|
||||||
AddUntilStep("countdown button shown", () => this.ChildrenOfType<MultiplayerCountdownButton>().SingleOrDefault()?.IsPresent == true);
|
|
||||||
ClickButtonWhenEnabled<MultiplayerCountdownButton>();
|
|
||||||
AddStep("click the first countdown button", () =>
|
|
||||||
{
|
|
||||||
var popoverButton = this.ChildrenOfType<Popover>().Single().ChildrenOfType<OsuButton>().First();
|
|
||||||
InputManager.MoveMouseTo(popoverButton);
|
|
||||||
InputManager.Click(MouseButton.Left);
|
|
||||||
});
|
|
||||||
|
|
||||||
AddStep("set spectating", () => MultiplayerClient.ChangeUserState(API.LocalUser.Value.OnlineID, MultiplayerUserState.Spectating));
|
|
||||||
AddUntilStep("local user is spectating", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Spectating);
|
|
||||||
|
|
||||||
AddStep("finish countdown", () => MultiplayerClient.SkipToEndOfCountdown());
|
|
||||||
AddUntilStep("match not started", () => MultiplayerClient.Room?.State == MultiplayerRoomState.Open);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestReadyButtonEnabledWhileSpectatingDuringCountdown()
|
|
||||||
{
|
|
||||||
AddStep("add second user", () => MultiplayerClient.AddUser(new APIUser { Id = 2, Username = "Another user" }));
|
|
||||||
AddStep("set second user ready", () => MultiplayerClient.ChangeUserState(2, MultiplayerUserState.Ready));
|
|
||||||
|
|
||||||
ClickButtonWhenEnabled<MultiplayerReadyButton>();
|
|
||||||
AddUntilStep("countdown button shown", () => this.ChildrenOfType<MultiplayerCountdownButton>().SingleOrDefault()?.IsPresent == true);
|
|
||||||
ClickButtonWhenEnabled<MultiplayerCountdownButton>();
|
|
||||||
AddStep("click the first countdown button", () =>
|
|
||||||
{
|
|
||||||
var popoverButton = this.ChildrenOfType<Popover>().Single().ChildrenOfType<OsuButton>().First();
|
|
||||||
InputManager.MoveMouseTo(popoverButton);
|
|
||||||
InputManager.Click(MouseButton.Left);
|
|
||||||
});
|
|
||||||
|
|
||||||
AddStep("set spectating", () => MultiplayerClient.ChangeUserState(API.LocalUser.Value.OnlineID, MultiplayerUserState.Spectating));
|
|
||||||
AddUntilStep("local user is spectating", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Spectating);
|
|
||||||
|
|
||||||
AddAssert("ready button enabled", () => this.ChildrenOfType<MultiplayerReadyButton>().Single().Enabled.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestBecomeHostDuringCountdownAndReady()
|
public void TestBecomeHostDuringCountdownAndReady()
|
||||||
{
|
{
|
||||||
@ -205,6 +168,31 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
AddAssert("countdown still active", () => MultiplayerClient.Room?.Countdown != null);
|
AddAssert("countdown still active", () => MultiplayerClient.Room?.Countdown != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCountdownButtonVisibilityWithAutoStartEnablement()
|
||||||
|
{
|
||||||
|
ClickButtonWhenEnabled<MultiplayerReadyButton>();
|
||||||
|
AddUntilStep("local user became ready", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Ready);
|
||||||
|
AddUntilStep("countdown button visible", () => this.ChildrenOfType<MultiplayerCountdownButton>().Single().IsPresent);
|
||||||
|
|
||||||
|
AddStep("enable auto start", () => MultiplayerClient.ChangeSettings(new MultiplayerRoomSettings { AutoStartDuration = TimeSpan.FromMinutes(1) }).WaitSafely());
|
||||||
|
|
||||||
|
ClickButtonWhenEnabled<MultiplayerReadyButton>();
|
||||||
|
AddUntilStep("local user became ready", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Ready);
|
||||||
|
AddUntilStep("countdown button not visible", () => !this.ChildrenOfType<MultiplayerCountdownButton>().Single().IsPresent);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestClickingReadyButtonUnReadiesDuringAutoStart()
|
||||||
|
{
|
||||||
|
AddStep("enable auto start", () => MultiplayerClient.ChangeSettings(new MultiplayerRoomSettings { AutoStartDuration = TimeSpan.FromMinutes(1) }).WaitSafely());
|
||||||
|
ClickButtonWhenEnabled<MultiplayerReadyButton>();
|
||||||
|
AddUntilStep("local user became ready", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Ready);
|
||||||
|
|
||||||
|
ClickButtonWhenEnabled<MultiplayerReadyButton>();
|
||||||
|
AddUntilStep("local user became idle", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Idle);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestDeletedBeatmapDisableReady()
|
public void TestDeletedBeatmapDisableReady()
|
||||||
{
|
{
|
||||||
|
@ -163,6 +163,25 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
AddUntilStep("second user crown visible", () => this.ChildrenOfType<ParticipantPanel>().ElementAt(1).ChildrenOfType<SpriteIcon>().First().Alpha == 1);
|
AddUntilStep("second user crown visible", () => this.ChildrenOfType<ParticipantPanel>().ElementAt(1).ChildrenOfType<SpriteIcon>().First().Alpha == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestHostGetsPinnedToTop()
|
||||||
|
{
|
||||||
|
AddStep("add user", () => MultiplayerClient.AddUser(new APIUser
|
||||||
|
{
|
||||||
|
Id = 3,
|
||||||
|
Username = "Second",
|
||||||
|
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
|
||||||
|
}));
|
||||||
|
|
||||||
|
AddStep("make second user host", () => MultiplayerClient.TransferHost(3));
|
||||||
|
AddAssert("second user above first", () =>
|
||||||
|
{
|
||||||
|
var first = this.ChildrenOfType<ParticipantPanel>().ElementAt(0);
|
||||||
|
var second = this.ChildrenOfType<ParticipantPanel>().ElementAt(1);
|
||||||
|
return second.Y < first.Y;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestKickButtonOnlyPresentWhenHost()
|
public void TestKickButtonOnlyPresentWhenHost()
|
||||||
{
|
{
|
||||||
@ -202,9 +221,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestManyUsers()
|
public void TestManyUsers()
|
||||||
{
|
{
|
||||||
|
const int users_count = 20;
|
||||||
|
|
||||||
AddStep("add many users", () =>
|
AddStep("add many users", () =>
|
||||||
{
|
{
|
||||||
for (int i = 0; i < 20; i++)
|
for (int i = 0; i < users_count; i++)
|
||||||
{
|
{
|
||||||
MultiplayerClient.AddUser(new APIUser
|
MultiplayerClient.AddUser(new APIUser
|
||||||
{
|
{
|
||||||
@ -243,6 +264,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
AddRepeatStep("switch hosts", () => MultiplayerClient.TransferHost(RNG.Next(0, users_count)), 10);
|
||||||
|
AddStep("give host back", () => MultiplayerClient.TransferHost(API.LocalUser.Value.Id));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -3,7 +3,9 @@
|
|||||||
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
@ -28,6 +30,23 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
stream.CopyTo(outStream);
|
stream.CopyTo(outStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp()
|
||||||
|
{
|
||||||
|
if (RuntimeInfo.OS == RuntimeInfo.Platform.macOS && RuntimeInformation.OSArchitecture == Architecture.Arm64)
|
||||||
|
Assert.Ignore("EF-to-realm migrations are not supported on M1 ARM architectures.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetUpSteps()
|
||||||
|
{
|
||||||
|
// base SetUpSteps are executed before the above SetUp, therefore early-return to allow ignoring test properly.
|
||||||
|
// attempting to ignore here would yield a TargetInvocationException instead.
|
||||||
|
if (RuntimeInfo.OS == RuntimeInfo.Platform.macOS && RuntimeInformation.OSArchitecture == Architecture.Arm64)
|
||||||
|
return;
|
||||||
|
|
||||||
|
base.SetUpSteps();
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestMigration()
|
public void TestMigration()
|
||||||
{
|
{
|
||||||
|
@ -116,7 +116,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
private void testBeatmapLabels(Ruleset ruleset)
|
private void testBeatmapLabels(Ruleset ruleset)
|
||||||
{
|
{
|
||||||
AddAssert("check version", () => infoWedge.Info.VersionLabel.Current.Value == $"{ruleset.ShortName}Version");
|
AddAssert("check version", () => infoWedge.Info.VersionLabel.Current.Value == $"{ruleset.ShortName}Version");
|
||||||
AddAssert("check title", () => infoWedge.Info.TitleLabel.Current.Value == $"{ruleset.ShortName}Source — {ruleset.ShortName}Title");
|
AddAssert("check title", () => infoWedge.Info.TitleLabel.Current.Value == $"{ruleset.ShortName}Title");
|
||||||
AddAssert("check artist", () => infoWedge.Info.ArtistLabel.Current.Value == $"{ruleset.ShortName}Artist");
|
AddAssert("check artist", () => infoWedge.Info.ArtistLabel.Current.Value == $"{ruleset.ShortName}Artist");
|
||||||
AddAssert("check author", () => infoWedge.Info.MapperContainer.ChildrenOfType<OsuSpriteText>().Any(s => s.Current.Value == $"{ruleset.ShortName}Author"));
|
AddAssert("check author", () => infoWedge.Info.MapperContainer.ChildrenOfType<OsuSpriteText>().Any(s => s.Current.Value == $"{ruleset.ShortName}Author"));
|
||||||
}
|
}
|
||||||
|
@ -68,7 +68,9 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
AddStep("reset defaults", () =>
|
AddStep("reset defaults", () =>
|
||||||
{
|
{
|
||||||
Ruleset.Value = new OsuRuleset().RulesetInfo;
|
Ruleset.Value = new OsuRuleset().RulesetInfo;
|
||||||
|
|
||||||
Beatmap.SetDefault();
|
Beatmap.SetDefault();
|
||||||
|
SelectedMods.SetDefault();
|
||||||
|
|
||||||
songSelect = null;
|
songSelect = null;
|
||||||
});
|
});
|
||||||
@ -563,7 +565,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestAutoplayViaCtrlEnter()
|
public void TestAutoplayShortcut()
|
||||||
{
|
{
|
||||||
addRulesetImportStep(0);
|
addRulesetImportStep(0);
|
||||||
|
|
||||||
@ -580,11 +582,65 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
AddUntilStep("wait for player", () => Stack.CurrentScreen is PlayerLoader);
|
AddUntilStep("wait for player", () => Stack.CurrentScreen is PlayerLoader);
|
||||||
|
|
||||||
AddAssert("autoplay enabled", () => songSelect.Mods.Value.FirstOrDefault() is ModAutoplay);
|
AddAssert("autoplay selected", () => songSelect.Mods.Value.Single() is ModAutoplay);
|
||||||
|
|
||||||
AddUntilStep("wait for return to ss", () => songSelect.IsCurrentScreen());
|
AddUntilStep("wait for return to ss", () => songSelect.IsCurrentScreen());
|
||||||
|
|
||||||
AddAssert("mod disabled", () => songSelect.Mods.Value.Count == 0);
|
AddAssert("no mods selected", () => songSelect.Mods.Value.Count == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAutoplayShortcutKeepsAutoplayIfSelectedAlready()
|
||||||
|
{
|
||||||
|
addRulesetImportStep(0);
|
||||||
|
|
||||||
|
createSongSelect();
|
||||||
|
|
||||||
|
AddUntilStep("wait for selection", () => !Beatmap.IsDefault);
|
||||||
|
|
||||||
|
changeMods(new OsuModAutoplay());
|
||||||
|
|
||||||
|
AddStep("press ctrl+enter", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.ControlLeft);
|
||||||
|
InputManager.Key(Key.Enter);
|
||||||
|
InputManager.ReleaseKey(Key.ControlLeft);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("wait for player", () => Stack.CurrentScreen is PlayerLoader);
|
||||||
|
|
||||||
|
AddAssert("autoplay selected", () => songSelect.Mods.Value.Single() is ModAutoplay);
|
||||||
|
|
||||||
|
AddUntilStep("wait for return to ss", () => songSelect.IsCurrentScreen());
|
||||||
|
|
||||||
|
AddAssert("autoplay still selected", () => songSelect.Mods.Value.Single() is ModAutoplay);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAutoplayShortcutReturnsInitialModsOnExit()
|
||||||
|
{
|
||||||
|
addRulesetImportStep(0);
|
||||||
|
|
||||||
|
createSongSelect();
|
||||||
|
|
||||||
|
AddUntilStep("wait for selection", () => !Beatmap.IsDefault);
|
||||||
|
|
||||||
|
changeMods(new OsuModRelax());
|
||||||
|
|
||||||
|
AddStep("press ctrl+enter", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.ControlLeft);
|
||||||
|
InputManager.Key(Key.Enter);
|
||||||
|
InputManager.ReleaseKey(Key.ControlLeft);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("wait for player", () => Stack.CurrentScreen is PlayerLoader);
|
||||||
|
|
||||||
|
AddAssert("only autoplay selected", () => songSelect.Mods.Value.Single() is ModAutoplay);
|
||||||
|
|
||||||
|
AddUntilStep("wait for return to ss", () => songSelect.IsCurrentScreen());
|
||||||
|
|
||||||
|
AddAssert("relax returned", () => songSelect.Mods.Value.Single() is ModRelax);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -0,0 +1,40 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Overlays.Mods;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class TestSceneModSettingsArea : OsuTestScene
|
||||||
|
{
|
||||||
|
[Cached]
|
||||||
|
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green);
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestModToggleArea()
|
||||||
|
{
|
||||||
|
ModSettingsArea modSettingsArea = null;
|
||||||
|
|
||||||
|
AddStep("create content", () => Child = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Child = modSettingsArea = new ModSettingsArea()
|
||||||
|
});
|
||||||
|
AddStep("set DT", () => modSettingsArea.SelectedMods.Value = new[] { new OsuModDoubleTime() });
|
||||||
|
AddStep("set DA", () => modSettingsArea.SelectedMods.Value = new Mod[] { new OsuModDifficultyAdjust() });
|
||||||
|
AddStep("set FL+WU+DA+AD", () => modSettingsArea.SelectedMods.Value = new Mod[] { new OsuModFlashlight(), new ModWindUp(), new OsuModDifficultyAdjust(), new OsuModApproachDifferent() });
|
||||||
|
AddStep("set empty", () => modSettingsArea.SelectedMods.Value = Array.Empty<Mod>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -225,7 +225,7 @@ namespace osu.Game.Beatmaps
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return new LegacyBeatmapSkin(BeatmapInfo, resources.Files, resources);
|
return new LegacyBeatmapSkin(BeatmapInfo, resources);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
@ -44,6 +44,8 @@ namespace osu.Game.Configuration
|
|||||||
|
|
||||||
SetDefault(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2f, 1f);
|
SetDefault(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2f, 1f);
|
||||||
|
|
||||||
|
SetDefault(OsuSetting.ToolbarClockDisplayMode, ToolbarClockDisplayMode.Full);
|
||||||
|
|
||||||
// Online settings
|
// Online settings
|
||||||
SetDefault(OsuSetting.Username, string.Empty);
|
SetDefault(OsuSetting.Username, string.Empty);
|
||||||
SetDefault(OsuSetting.Token, string.Empty);
|
SetDefault(OsuSetting.Token, string.Empty);
|
||||||
@ -295,6 +297,7 @@ namespace osu.Game.Configuration
|
|||||||
RandomSelectAlgorithm,
|
RandomSelectAlgorithm,
|
||||||
ShowFpsDisplay,
|
ShowFpsDisplay,
|
||||||
ChatDisplayHeight,
|
ChatDisplayHeight,
|
||||||
|
ToolbarClockDisplayMode,
|
||||||
Version,
|
Version,
|
||||||
ShowConvertedBeatmaps,
|
ShowConvertedBeatmaps,
|
||||||
Skin,
|
Skin,
|
||||||
|
13
osu.Game/Configuration/ToolbarClockDisplayMode.cs
Normal file
13
osu.Game/Configuration/ToolbarClockDisplayMode.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace osu.Game.Configuration
|
||||||
|
{
|
||||||
|
public enum ToolbarClockDisplayMode
|
||||||
|
{
|
||||||
|
Analog,
|
||||||
|
Digital,
|
||||||
|
DigitalWithRuntime,
|
||||||
|
Full
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,14 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using osu.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Development;
|
using osu.Framework.Development;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -14,6 +18,7 @@ using osu.Framework.Platform;
|
|||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Models;
|
using osu.Game.Models;
|
||||||
@ -29,8 +34,6 @@ using SharpCompress.Archives.Zip;
|
|||||||
using SharpCompress.Common;
|
using SharpCompress.Common;
|
||||||
using SharpCompress.Writers.Zip;
|
using SharpCompress.Writers.Zip;
|
||||||
|
|
||||||
#nullable enable
|
|
||||||
|
|
||||||
namespace osu.Game.Database
|
namespace osu.Game.Database
|
||||||
{
|
{
|
||||||
internal class EFToRealmMigrator : CompositeDrawable
|
internal class EFToRealmMigrator : CompositeDrawable
|
||||||
@ -57,7 +60,7 @@ namespace osu.Game.Database
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private Storage storage { get; set; } = null!;
|
private Storage storage { get; set; } = null!;
|
||||||
|
|
||||||
private readonly OsuSpriteText currentOperationText;
|
private readonly OsuTextFlowContainer currentOperationText;
|
||||||
|
|
||||||
public EFToRealmMigrator()
|
public EFToRealmMigrator()
|
||||||
{
|
{
|
||||||
@ -99,11 +102,13 @@ namespace osu.Game.Database
|
|||||||
{
|
{
|
||||||
State = { Value = Visibility.Visible }
|
State = { Value = Visibility.Visible }
|
||||||
},
|
},
|
||||||
currentOperationText = new OsuSpriteText
|
currentOperationText = new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: 30))
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Font = OsuFont.Default.With(size: 30)
|
AutoSizeAxes = Axes.Y,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
TextAnchor = Anchor.TopCentre,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -147,19 +152,34 @@ namespace osu.Game.Database
|
|||||||
log("Migration successful!");
|
log("Migration successful!");
|
||||||
|
|
||||||
if (DebugUtils.IsDebugBuild)
|
if (DebugUtils.IsDebugBuild)
|
||||||
Logger.Log("Your development database has been fully migrated to realm. If you switch back to a pre-realm branch and need your previous database, rename the backup file back to \"client.db\".\n\nNote that doing this can potentially leave your file store in a bad state.", level: LogLevel.Important);
|
{
|
||||||
|
Logger.Log(
|
||||||
|
"Your development database has been fully migrated to realm. If you switch back to a pre-realm branch and need your previous database, rename the backup file back to \"client.db\".\n\nNote that doing this can potentially leave your file store in a bad state.",
|
||||||
|
level: LogLevel.Important);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
log("Migration failed!");
|
log("Migration failed!");
|
||||||
Logger.Log(t.Exception.ToString(), LoggingTarget.Database);
|
Logger.Log(t.Exception.ToString(), LoggingTarget.Database);
|
||||||
|
|
||||||
|
if (RuntimeInfo.OS == RuntimeInfo.Platform.macOS && t.Exception.Flatten().InnerException is TypeInitializationException)
|
||||||
|
{
|
||||||
|
// Not guaranteed to be the only cause of exception, but let's roll with it for now.
|
||||||
|
log("Please download and run the intel version of osu! once\nto allow data migration to complete!");
|
||||||
|
efContextFactory.SetMigrationCompletion();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
notificationOverlay.Post(new SimpleErrorNotification
|
notificationOverlay.Post(new SimpleErrorNotification
|
||||||
{
|
{
|
||||||
Text = "IMPORTANT: During data migration, some of your data could not be successfully migrated. The previous version has been backed up.\n\nFor further assistance, please open a discussion on github and attach your backup files (click to get started).",
|
Text =
|
||||||
|
"IMPORTANT: During data migration, some of your data could not be successfully migrated. The previous version has been backed up.\n\nFor further assistance, please open a discussion on github and attach your backup files (click to get started).",
|
||||||
Activated = () =>
|
Activated = () =>
|
||||||
{
|
{
|
||||||
game.OpenUrlExternally($@"https://github.com/ppy/osu/discussions/new?title=Realm%20migration%20issue ({t.Exception.Message})&body=Please%20drag%20the%20""attach_me.zip""%20file%20here!&category=q-a", true);
|
game.OpenUrlExternally(
|
||||||
|
$@"https://github.com/ppy/osu/discussions/new?title=Realm%20migration%20issue ({t.Exception.Message})&body=Please%20drag%20the%20""attach_me.zip""%20file%20here!&category=q-a",
|
||||||
|
true);
|
||||||
|
|
||||||
const string attachment_filename = "attach_me.zip";
|
const string attachment_filename = "attach_me.zip";
|
||||||
const string backup_folder = "backups";
|
const string backup_folder = "backups";
|
||||||
|
@ -13,6 +13,7 @@ using System.Linq.Expressions;
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using osu.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Development;
|
using osu.Framework.Development;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
@ -211,7 +212,7 @@ namespace osu.Game.Database
|
|||||||
if (realm.All<ScoreInfo>().Any())
|
if (realm.All<ScoreInfo>().Any())
|
||||||
{
|
{
|
||||||
Logger.Log(@"Recovery aborted as the existing database has scores set already.", LoggingTarget.Database);
|
Logger.Log(@"Recovery aborted as the existing database has scores set already.", LoggingTarget.Database);
|
||||||
Logger.Log(@"To perform recovery, delete client.realm while osu! is not running.", LoggingTarget.Database);
|
Logger.Log($@"To perform recovery, delete {OsuGameBase.CLIENT_DATABASE_FILENAME} while osu! is not running.", LoggingTarget.Database);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -293,7 +294,18 @@ namespace osu.Game.Database
|
|||||||
/// Compact this realm.
|
/// Compact this realm.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public bool Compact() => Realm.Compact(getConfiguration());
|
public bool Compact()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Realm.Compact(getConfiguration());
|
||||||
|
}
|
||||||
|
// Catch can be removed along with entity framework. Is specifically to allow a failure message to arrive to the user (see similar catches in EFToRealmMigrator).
|
||||||
|
catch (AggregateException ae) when (RuntimeInfo.OS == RuntimeInfo.Platform.macOS && ae.Flatten().InnerException is TypeInitializationException)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Run work on realm with a return value.
|
/// Run work on realm with a return value.
|
||||||
@ -542,6 +554,11 @@ namespace osu.Game.Database
|
|||||||
|
|
||||||
return Realm.GetInstance(getConfiguration());
|
return Realm.GetInstance(getConfiguration());
|
||||||
}
|
}
|
||||||
|
// Catch can be removed along with entity framework. Is specifically to allow a failure message to arrive to the user (see similar catches in EFToRealmMigrator).
|
||||||
|
catch (AggregateException ae) when (RuntimeInfo.OS == RuntimeInfo.Platform.macOS && ae.Flatten().InnerException is TypeInitializationException)
|
||||||
|
{
|
||||||
|
return Realm.GetInstance();
|
||||||
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (tookSemaphoreLock)
|
if (tookSemaphoreLock)
|
||||||
|
@ -36,15 +36,15 @@ namespace osu.Game.IO
|
|||||||
public override string[] IgnoreDirectories => new[]
|
public override string[] IgnoreDirectories => new[]
|
||||||
{
|
{
|
||||||
"cache",
|
"cache",
|
||||||
"client.realm.management"
|
$"{OsuGameBase.CLIENT_DATABASE_FILENAME}.management",
|
||||||
};
|
};
|
||||||
|
|
||||||
public override string[] IgnoreFiles => new[]
|
public override string[] IgnoreFiles => new[]
|
||||||
{
|
{
|
||||||
"framework.ini",
|
"framework.ini",
|
||||||
"storage.ini",
|
"storage.ini",
|
||||||
"client.realm.note",
|
$"{OsuGameBase.CLIENT_DATABASE_FILENAME}.note",
|
||||||
"client.realm.lock",
|
$"{OsuGameBase.CLIENT_DATABASE_FILENAME}.lock",
|
||||||
};
|
};
|
||||||
|
|
||||||
public OsuStorage(GameHost host, Storage defaultStorage)
|
public OsuStorage(GameHost host, Storage defaultStorage)
|
||||||
@ -64,12 +64,22 @@ namespace osu.Game.IO
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void ResetCustomStoragePath()
|
public void ResetCustomStoragePath()
|
||||||
{
|
{
|
||||||
storageConfig.SetValue(StorageConfig.FullPath, string.Empty);
|
ChangeDataPath(string.Empty);
|
||||||
storageConfig.Save();
|
|
||||||
|
|
||||||
ChangeTargetStorage(defaultStorage);
|
ChangeTargetStorage(defaultStorage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the target data path without immediately switching.
|
||||||
|
/// This does NOT migrate any data.
|
||||||
|
/// The game should immediately be restarted after calling this.
|
||||||
|
/// </summary>
|
||||||
|
public void ChangeDataPath(string newPath)
|
||||||
|
{
|
||||||
|
storageConfig.SetValue(StorageConfig.FullPath, newPath);
|
||||||
|
storageConfig.Save();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Attempts to change to the user's custom storage path.
|
/// Attempts to change to the user's custom storage path.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -117,8 +127,7 @@ namespace osu.Game.IO
|
|||||||
{
|
{
|
||||||
bool cleanupSucceeded = base.Migrate(newStorage);
|
bool cleanupSucceeded = base.Migrate(newStorage);
|
||||||
|
|
||||||
storageConfig.SetValue(StorageConfig.FullPath, newStorage.GetFullPath("."));
|
ChangeDataPath(newStorage.GetFullPath("."));
|
||||||
storageConfig.Save();
|
|
||||||
|
|
||||||
return cleanupSucceeded;
|
return cleanupSucceeded;
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,11 @@ namespace osu.Game.Online.API.Requests.Responses
|
|||||||
[JsonObject(MemberSerialization.OptIn)]
|
[JsonObject(MemberSerialization.OptIn)]
|
||||||
public class APIUser : IEquatable<APIUser>, IUser
|
public class APIUser : IEquatable<APIUser>, IUser
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A user ID which can be used to represent any system user which is not attached to a user profile.
|
||||||
|
/// </summary>
|
||||||
|
public const int SYSTEM_USER_ID = 0;
|
||||||
|
|
||||||
[JsonProperty(@"id")]
|
[JsonProperty(@"id")]
|
||||||
public int Id { get; set; } = 1;
|
public int Id { get; set; } = 1;
|
||||||
|
|
||||||
@ -238,7 +243,7 @@ namespace osu.Game.Online.API.Requests.Responses
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly APIUser SYSTEM_USER = new APIUser
|
public static readonly APIUser SYSTEM_USER = new APIUser
|
||||||
{
|
{
|
||||||
Id = 0,
|
Id = SYSTEM_USER_ID,
|
||||||
Username = "system",
|
Username = "system",
|
||||||
Colour = @"9c0101",
|
Colour = @"9c0101",
|
||||||
};
|
};
|
||||||
|
@ -241,7 +241,9 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
/// <param name="password">The new password, if any.</param>
|
/// <param name="password">The new password, if any.</param>
|
||||||
/// <param name="matchType">The type of the match, if any.</param>
|
/// <param name="matchType">The type of the match, if any.</param>
|
||||||
/// <param name="queueMode">The new queue mode, if any.</param>
|
/// <param name="queueMode">The new queue mode, if any.</param>
|
||||||
public Task ChangeSettings(Optional<string> name = default, Optional<string> password = default, Optional<MatchType> matchType = default, Optional<QueueMode> queueMode = default)
|
/// <param name="autoStartDuration">The new auto-start countdown duration, if any.</param>
|
||||||
|
public Task ChangeSettings(Optional<string> name = default, Optional<string> password = default, Optional<MatchType> matchType = default, Optional<QueueMode> queueMode = default,
|
||||||
|
Optional<TimeSpan> autoStartDuration = default)
|
||||||
{
|
{
|
||||||
if (Room == null)
|
if (Room == null)
|
||||||
throw new InvalidOperationException("Must be joined to a match to change settings.");
|
throw new InvalidOperationException("Must be joined to a match to change settings.");
|
||||||
@ -252,6 +254,7 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
Password = password.GetOr(Room.Settings.Password),
|
Password = password.GetOr(Room.Settings.Password),
|
||||||
MatchType = matchType.GetOr(Room.Settings.MatchType),
|
MatchType = matchType.GetOr(Room.Settings.MatchType),
|
||||||
QueueMode = queueMode.GetOr(Room.Settings.QueueMode),
|
QueueMode = queueMode.GetOr(Room.Settings.QueueMode),
|
||||||
|
AutoStartDuration = autoStartDuration.GetOr(Room.Settings.AutoStartDuration),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -745,6 +748,7 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
APIRoom.Password.Value = Room.Settings.Password;
|
APIRoom.Password.Value = Room.Settings.Password;
|
||||||
APIRoom.Type.Value = Room.Settings.MatchType;
|
APIRoom.Type.Value = Room.Settings.MatchType;
|
||||||
APIRoom.QueueMode.Value = Room.Settings.QueueMode;
|
APIRoom.QueueMode.Value = Room.Settings.QueueMode;
|
||||||
|
APIRoom.AutoStartDuration.Value = Room.Settings.AutoStartDuration;
|
||||||
|
|
||||||
RoomUpdated?.Invoke();
|
RoomUpdated?.Invoke();
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,12 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
[Key(4)]
|
[Key(4)]
|
||||||
public QueueMode QueueMode { get; set; } = QueueMode.HostOnly;
|
public QueueMode QueueMode { get; set; } = QueueMode.HostOnly;
|
||||||
|
|
||||||
|
[Key(5)]
|
||||||
|
public TimeSpan AutoStartDuration { get; set; }
|
||||||
|
|
||||||
|
[IgnoreMember]
|
||||||
|
public bool AutoStartEnabled => AutoStartDuration != TimeSpan.Zero;
|
||||||
|
|
||||||
public bool Equals(MultiplayerRoomSettings? other)
|
public bool Equals(MultiplayerRoomSettings? other)
|
||||||
{
|
{
|
||||||
if (ReferenceEquals(this, other)) return true;
|
if (ReferenceEquals(this, other)) return true;
|
||||||
@ -37,13 +43,15 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
&& Name.Equals(other.Name, StringComparison.Ordinal)
|
&& Name.Equals(other.Name, StringComparison.Ordinal)
|
||||||
&& PlaylistItemId == other.PlaylistItemId
|
&& PlaylistItemId == other.PlaylistItemId
|
||||||
&& MatchType == other.MatchType
|
&& MatchType == other.MatchType
|
||||||
&& QueueMode == other.QueueMode;
|
&& QueueMode == other.QueueMode
|
||||||
|
&& AutoStartDuration == other.AutoStartDuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => $"Name:{Name}"
|
public override string ToString() => $"Name:{Name}"
|
||||||
+ $" Password:{(string.IsNullOrEmpty(Password) ? "no" : "yes")}"
|
+ $" Password:{(string.IsNullOrEmpty(Password) ? "no" : "yes")}"
|
||||||
+ $" Type:{MatchType}"
|
+ $" Type:{MatchType}"
|
||||||
+ $" Item:{PlaylistItemId}"
|
+ $" Item:{PlaylistItemId}"
|
||||||
+ $" Queue:{QueueMode}";
|
+ $" Queue:{QueueMode}"
|
||||||
|
+ $" Start:{AutoStartDuration}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -92,6 +92,16 @@ namespace osu.Game.Online.Rooms
|
|||||||
set => QueueMode.Value = value;
|
set => QueueMode.Value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Cached]
|
||||||
|
public readonly Bindable<TimeSpan> AutoStartDuration = new Bindable<TimeSpan>();
|
||||||
|
|
||||||
|
[JsonProperty("auto_start_duration")]
|
||||||
|
private ushort autoStartDuration
|
||||||
|
{
|
||||||
|
get => (ushort)AutoStartDuration.Value.TotalSeconds;
|
||||||
|
set => AutoStartDuration.Value = TimeSpan.FromSeconds(value);
|
||||||
|
}
|
||||||
|
|
||||||
[Cached]
|
[Cached]
|
||||||
public readonly Bindable<int?> MaxParticipants = new Bindable<int?>();
|
public readonly Bindable<int?> MaxParticipants = new Bindable<int?>();
|
||||||
|
|
||||||
@ -172,6 +182,7 @@ namespace osu.Game.Online.Rooms
|
|||||||
EndDate.Value = other.EndDate.Value;
|
EndDate.Value = other.EndDate.Value;
|
||||||
UserScore.Value = other.UserScore.Value;
|
UserScore.Value = other.UserScore.Value;
|
||||||
QueueMode.Value = other.QueueMode.Value;
|
QueueMode.Value = other.QueueMode.Value;
|
||||||
|
AutoStartDuration.Value = other.AutoStartDuration.Value;
|
||||||
DifficultyRange.Value = other.DifficultyRange.Value;
|
DifficultyRange.Value = other.DifficultyRange.Value;
|
||||||
PlaylistItemStats.Value = other.PlaylistItemStats.Value;
|
PlaylistItemStats.Value = other.PlaylistItemStats.Value;
|
||||||
CurrentPlaylistItem.Value = other.CurrentPlaylistItem.Value;
|
CurrentPlaylistItem.Value = other.CurrentPlaylistItem.Value;
|
||||||
|
@ -57,6 +57,11 @@ namespace osu.Game
|
|||||||
|
|
||||||
public const string CLIENT_STREAM_NAME = @"lazer";
|
public const string CLIENT_STREAM_NAME = @"lazer";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The filename of the main client database.
|
||||||
|
/// </summary>
|
||||||
|
public const string CLIENT_DATABASE_FILENAME = @"client.realm";
|
||||||
|
|
||||||
public const int SAMPLE_CONCURRENCY = 6;
|
public const int SAMPLE_CONCURRENCY = 6;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -200,7 +205,7 @@ namespace osu.Game
|
|||||||
if (Storage.Exists(DatabaseContextFactory.DATABASE_NAME))
|
if (Storage.Exists(DatabaseContextFactory.DATABASE_NAME))
|
||||||
dependencies.Cache(EFContextFactory = new DatabaseContextFactory(Storage));
|
dependencies.Cache(EFContextFactory = new DatabaseContextFactory(Storage));
|
||||||
|
|
||||||
dependencies.Cache(realm = new RealmAccess(Storage, "client", Host.UpdateThread, EFContextFactory));
|
dependencies.Cache(realm = new RealmAccess(Storage, CLIENT_DATABASE_FILENAME, Host.UpdateThread, EFContextFactory));
|
||||||
|
|
||||||
dependencies.CacheAs<RulesetStore>(RulesetStore = new RealmRulesetStore(realm, Storage));
|
dependencies.CacheAs<RulesetStore>(RulesetStore = new RealmRulesetStore(realm, Storage));
|
||||||
dependencies.CacheAs<IRulesetStore>(RulesetStore);
|
dependencies.CacheAs<IRulesetStore>(RulesetStore);
|
||||||
|
176
osu.Game/Overlays/Mods/ModSettingsArea.cs
Normal file
176
osu.Game/Overlays/Mods/ModSettingsArea.cs
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
// 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.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Mods
|
||||||
|
{
|
||||||
|
public class ModSettingsArea : CompositeDrawable
|
||||||
|
{
|
||||||
|
public Bindable<IReadOnlyList<Mod>> SelectedMods { get; } = new Bindable<IReadOnlyList<Mod>>();
|
||||||
|
|
||||||
|
private readonly Box background;
|
||||||
|
private readonly FillFlowContainer modSettingsFlow;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OverlayColourProvider colourProvider { get; set; }
|
||||||
|
|
||||||
|
public ModSettingsArea()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
Height = 250;
|
||||||
|
|
||||||
|
Anchor = Anchor.BottomRight;
|
||||||
|
Origin = Anchor.BottomRight;
|
||||||
|
|
||||||
|
InternalChild = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
BorderThickness = 2,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
background = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
|
},
|
||||||
|
new OsuScrollContainer(Direction.Horizontal)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
ScrollbarOverlapsContent = false,
|
||||||
|
Child = modSettingsFlow = new FillFlowContainer
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.X,
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
Padding = new MarginPadding { Vertical = 7, Horizontal = 70 },
|
||||||
|
Spacing = new Vector2(7),
|
||||||
|
Direction = FillDirection.Horizontal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
background.Colour = colourProvider.Dark3;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
SelectedMods.BindValueChanged(_ => updateMods());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateMods()
|
||||||
|
{
|
||||||
|
modSettingsFlow.Clear();
|
||||||
|
|
||||||
|
foreach (var mod in SelectedMods.Value.OrderBy(mod => mod.Type).ThenBy(mod => mod.Acronym))
|
||||||
|
{
|
||||||
|
var settings = mod.CreateSettingsControls().ToList();
|
||||||
|
|
||||||
|
if (settings.Count > 0)
|
||||||
|
{
|
||||||
|
if (modSettingsFlow.Any())
|
||||||
|
{
|
||||||
|
modSettingsFlow.Add(new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
Width = 2,
|
||||||
|
Colour = colourProvider.Dark4,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
modSettingsFlow.Add(new ModSettingsColumn(mod, settings));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnMouseDown(MouseDownEvent e) => true;
|
||||||
|
protected override bool OnHover(HoverEvent e) => true;
|
||||||
|
|
||||||
|
private class ModSettingsColumn : CompositeDrawable
|
||||||
|
{
|
||||||
|
public ModSettingsColumn(Mod mod, IEnumerable<Drawable> settingsControls)
|
||||||
|
{
|
||||||
|
Width = 250;
|
||||||
|
RelativeSizeAxes = Axes.Y;
|
||||||
|
Padding = new MarginPadding { Bottom = 7 };
|
||||||
|
|
||||||
|
InternalChild = new GridContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
RowDimensions = new[]
|
||||||
|
{
|
||||||
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
|
new Dimension(GridSizeMode.Absolute, 10),
|
||||||
|
new Dimension()
|
||||||
|
},
|
||||||
|
Content = new[]
|
||||||
|
{
|
||||||
|
new Drawable[]
|
||||||
|
{
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Direction = FillDirection.Horizontal,
|
||||||
|
Spacing = new Vector2(7),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new ModSwitchTiny(mod)
|
||||||
|
{
|
||||||
|
Active = { Value = true },
|
||||||
|
Scale = new Vector2(0.6f),
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Anchor = Anchor.CentreLeft
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Text = mod.Name,
|
||||||
|
Font = OsuFont.Default.With(size: 16, weight: FontWeight.SemiBold),
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Margin = new MarginPadding { Bottom = 2 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new[] { Empty() },
|
||||||
|
new Drawable[]
|
||||||
|
{
|
||||||
|
new OsuScrollContainer(Direction.Vertical)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Child = new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Padding = new MarginPadding { Right = 7 },
|
||||||
|
ChildrenEnumerable = settingsControls,
|
||||||
|
Spacing = new Vector2(0, 7)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,11 +3,14 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
|
using osu.Game.IO;
|
||||||
|
using osu.Game.Overlays.Dialog;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
||||||
{
|
{
|
||||||
@ -16,6 +19,12 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private Storage storage { get; set; }
|
private Storage storage { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OsuGameBase game { get; set; }
|
||||||
|
|
||||||
|
[Resolved(canBeNull: true)]
|
||||||
|
private DialogOverlay dialogOverlay { get; set; }
|
||||||
|
|
||||||
protected override DirectoryInfo InitialPath => new DirectoryInfo(storage.GetFullPath(string.Empty)).Parent;
|
protected override DirectoryInfo InitialPath => new DirectoryInfo(storage.GetFullPath(string.Empty)).Parent;
|
||||||
|
|
||||||
public override bool AllowExternalScreenChange => false;
|
public override bool AllowExternalScreenChange => false;
|
||||||
@ -32,9 +41,30 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (target.GetDirectories().Length > 0 || target.GetFiles().Length > 0)
|
var directoryInfos = target.GetDirectories();
|
||||||
|
var fileInfos = target.GetFiles();
|
||||||
|
|
||||||
|
if (directoryInfos.Length > 0 || fileInfos.Length > 0)
|
||||||
|
{
|
||||||
|
// Quick test for whether there's already an osu! install at the target path.
|
||||||
|
if (fileInfos.Any(f => f.Name == OsuGameBase.CLIENT_DATABASE_FILENAME))
|
||||||
|
{
|
||||||
|
dialogOverlay.Push(new ConfirmDialog("The target directory already seems to have an osu! install. Use that data instead?", () =>
|
||||||
|
{
|
||||||
|
dialogOverlay.Push(new ConfirmDialog("To complete this operation, osu! will close. Please open it again to use the new data location.", () =>
|
||||||
|
{
|
||||||
|
(storage as OsuStorage)?.ChangeDataPath(target.FullName);
|
||||||
|
game.GracefullyExit();
|
||||||
|
}, () => { }));
|
||||||
|
},
|
||||||
|
() => { }));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
target = target.CreateSubdirectory("osu-lazer");
|
target = target.CreateSubdirectory("osu-lazer");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Logger.Log($"Error during migration: {e.Message}", level: LogLevel.Error);
|
Logger.Log($"Error during migration: {e.Message}", level: LogLevel.Error);
|
||||||
|
159
osu.Game/Overlays/Toolbar/AnalogClockDisplay.cs
Normal file
159
osu.Game/Overlays/Toolbar/AnalogClockDisplay.cs
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
// 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 osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Toolbar
|
||||||
|
{
|
||||||
|
public class AnalogClockDisplay : ClockDisplay
|
||||||
|
{
|
||||||
|
private const float hand_thickness = 2.4f;
|
||||||
|
|
||||||
|
private Drawable hour;
|
||||||
|
private Drawable minute;
|
||||||
|
private Drawable second;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Size = new Vector2(22);
|
||||||
|
|
||||||
|
InternalChildren = new[]
|
||||||
|
{
|
||||||
|
new CircularContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
BorderThickness = 2,
|
||||||
|
BorderColour = Color4.White,
|
||||||
|
Child = new Box
|
||||||
|
{
|
||||||
|
AlwaysPresent = true,
|
||||||
|
Alpha = 0,
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
|
},
|
||||||
|
},
|
||||||
|
hour = new LargeHand(0.34f),
|
||||||
|
minute = new LargeHand(0.48f),
|
||||||
|
second = new SecondHand(),
|
||||||
|
new CentreCircle
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateDisplay(DateTimeOffset now)
|
||||||
|
{
|
||||||
|
float secondFractional = now.Second / 60f;
|
||||||
|
float minuteFractional = (now.Minute + secondFractional) / 60f;
|
||||||
|
float hourFractional = ((minuteFractional + now.Hour) % 12) / 12f;
|
||||||
|
|
||||||
|
updateRotation(hour, hourFractional);
|
||||||
|
updateRotation(minute, minuteFractional);
|
||||||
|
updateRotation(second, secondFractional);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateRotation(Drawable hand, float fraction)
|
||||||
|
{
|
||||||
|
const float duration = 320;
|
||||||
|
|
||||||
|
float rotation = fraction * 360 - 90;
|
||||||
|
|
||||||
|
if (Math.Abs(hand.Rotation - rotation) > 180)
|
||||||
|
hand.RotateTo(rotation);
|
||||||
|
else
|
||||||
|
hand.RotateTo(rotation, duration, Easing.OutElastic);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CentreCircle : CompositeDrawable
|
||||||
|
{
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colours)
|
||||||
|
{
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new Circle
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Size = new Vector2(hand_thickness),
|
||||||
|
Colour = Color4.White,
|
||||||
|
},
|
||||||
|
new Circle
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Size = new Vector2(hand_thickness * 0.7f),
|
||||||
|
Colour = colours.PinkLight,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SecondHand : CompositeDrawable
|
||||||
|
{
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colours)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
Width = 0.66f;
|
||||||
|
|
||||||
|
Height = hand_thickness * 0.7f;
|
||||||
|
Anchor = Anchor.Centre;
|
||||||
|
Origin = Anchor.Custom;
|
||||||
|
|
||||||
|
OriginPosition = new Vector2(Height * 2, Height / 2);
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new Circle
|
||||||
|
{
|
||||||
|
Colour = colours.PinkLight,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class LargeHand : CompositeDrawable
|
||||||
|
{
|
||||||
|
public LargeHand(float length)
|
||||||
|
{
|
||||||
|
Width = length;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colours)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre;
|
||||||
|
Origin = Anchor.Custom;
|
||||||
|
|
||||||
|
OriginPosition = new Vector2(hand_thickness / 2); // offset x also, to ensure the centre of the line is centered on the face.
|
||||||
|
|
||||||
|
Height = hand_thickness;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new Circle
|
||||||
|
{
|
||||||
|
Colour = Color4.White,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
BorderThickness = 0.7f,
|
||||||
|
BorderColour = colours.Gray2,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
28
osu.Game/Overlays/Toolbar/ClockDisplay.cs
Normal file
28
osu.Game/Overlays/Toolbar/ClockDisplay.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// 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 osu.Framework.Graphics.Containers;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Toolbar
|
||||||
|
{
|
||||||
|
public abstract class ClockDisplay : CompositeDrawable
|
||||||
|
{
|
||||||
|
private int? lastSecond;
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
var now = DateTimeOffset.Now;
|
||||||
|
|
||||||
|
if (now.Second != lastSecond)
|
||||||
|
{
|
||||||
|
lastSecond = now.Second;
|
||||||
|
UpdateDisplay(now);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void UpdateDisplay(DateTimeOffset now);
|
||||||
|
}
|
||||||
|
}
|
64
osu.Game/Overlays/Toolbar/DigitalClockDisplay.cs
Normal file
64
osu.Game/Overlays/Toolbar/DigitalClockDisplay.cs
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
// 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 osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Toolbar
|
||||||
|
{
|
||||||
|
public class DigitalClockDisplay : ClockDisplay
|
||||||
|
{
|
||||||
|
private OsuSpriteText realTime;
|
||||||
|
private OsuSpriteText gameTime;
|
||||||
|
|
||||||
|
private bool showRuntime = true;
|
||||||
|
|
||||||
|
public bool ShowRuntime
|
||||||
|
{
|
||||||
|
get => showRuntime;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (showRuntime == value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
showRuntime = value;
|
||||||
|
updateMetrics();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colours)
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
realTime = new OsuSpriteText(),
|
||||||
|
gameTime = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Y = 14,
|
||||||
|
Colour = colours.PinkLight,
|
||||||
|
Scale = new Vector2(0.6f)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
updateMetrics();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateDisplay(DateTimeOffset now)
|
||||||
|
{
|
||||||
|
realTime.Text = $"{now:HH:mm:ss}";
|
||||||
|
gameTime.Text = $"running {new TimeSpan(TimeSpan.TicksPerSecond * (int)(Clock.CurrentTime / 1000)):c}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateMetrics()
|
||||||
|
{
|
||||||
|
Width = showRuntime ? 66 : 45; // Allows for space for game time up to 99 days (in the padding area since this is quite rare).
|
||||||
|
gameTime.FadeTo(showRuntime ? 1 : 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -104,6 +104,7 @@ namespace osu.Game.Overlays.Toolbar
|
|||||||
// Icon = FontAwesome.Solid.search
|
// Icon = FontAwesome.Solid.search
|
||||||
//},
|
//},
|
||||||
userButton = new ToolbarUserButton(),
|
userButton = new ToolbarUserButton(),
|
||||||
|
new ToolbarClock(),
|
||||||
new ToolbarNotificationButton(),
|
new ToolbarNotificationButton(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
101
osu.Game/Overlays/Toolbar/ToolbarClock.cs
Normal file
101
osu.Game/Overlays/Toolbar/ToolbarClock.cs
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
// 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.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Toolbar
|
||||||
|
{
|
||||||
|
public class ToolbarClock : CompositeDrawable
|
||||||
|
{
|
||||||
|
private Bindable<ToolbarClockDisplayMode> clockDisplayMode;
|
||||||
|
|
||||||
|
private DigitalClockDisplay digital;
|
||||||
|
private AnalogClockDisplay analog;
|
||||||
|
|
||||||
|
public ToolbarClock()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Y;
|
||||||
|
AutoSizeAxes = Axes.X;
|
||||||
|
|
||||||
|
Padding = new MarginPadding(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuConfigManager config)
|
||||||
|
{
|
||||||
|
clockDisplayMode = config.GetBindable<ToolbarClockDisplayMode>(OsuSetting.ToolbarClockDisplayMode);
|
||||||
|
|
||||||
|
InternalChild = new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
AutoSizeAxes = Axes.X,
|
||||||
|
Direction = FillDirection.Horizontal,
|
||||||
|
Spacing = new Vector2(5),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
analog = new AnalogClockDisplay
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
},
|
||||||
|
digital = new DigitalClockDisplay
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
clockDisplayMode.BindValueChanged(displayMode =>
|
||||||
|
{
|
||||||
|
bool showAnalog = displayMode.NewValue == ToolbarClockDisplayMode.Analog || displayMode.NewValue == ToolbarClockDisplayMode.Full;
|
||||||
|
bool showDigital = displayMode.NewValue != ToolbarClockDisplayMode.Analog;
|
||||||
|
bool showRuntime = displayMode.NewValue == ToolbarClockDisplayMode.DigitalWithRuntime || displayMode.NewValue == ToolbarClockDisplayMode.Full;
|
||||||
|
|
||||||
|
digital.FadeTo(showDigital ? 1 : 0);
|
||||||
|
digital.ShowRuntime = showRuntime;
|
||||||
|
|
||||||
|
analog.FadeTo(showAnalog ? 1 : 0);
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnClick(ClickEvent e)
|
||||||
|
{
|
||||||
|
cycleDisplayMode();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void cycleDisplayMode()
|
||||||
|
{
|
||||||
|
switch (clockDisplayMode.Value)
|
||||||
|
{
|
||||||
|
case ToolbarClockDisplayMode.Analog:
|
||||||
|
clockDisplayMode.Value = ToolbarClockDisplayMode.Full;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ToolbarClockDisplayMode.Digital:
|
||||||
|
clockDisplayMode.Value = ToolbarClockDisplayMode.Analog;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ToolbarClockDisplayMode.DigitalWithRuntime:
|
||||||
|
clockDisplayMode.Value = ToolbarClockDisplayMode.Digital;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ToolbarClockDisplayMode.Full:
|
||||||
|
clockDisplayMode.Value = ToolbarClockDisplayMode.DigitalWithRuntime;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -41,7 +41,7 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
public void ShowUser(IUser user)
|
public void ShowUser(IUser user)
|
||||||
{
|
{
|
||||||
if (user == APIUser.SYSTEM_USER)
|
if (user.OnlineID == APIUser.SYSTEM_USER_ID)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Show();
|
Show();
|
||||||
|
@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
private void regenerateAutoplay()
|
private void regenerateAutoplay()
|
||||||
{
|
{
|
||||||
var autoplayMod = drawableRuleset.Mods.OfType<ModAutoplay>().Single();
|
var autoplayMod = drawableRuleset.Mods.OfType<ModAutoplay>().Single();
|
||||||
drawableRuleset.SetReplayScore(autoplayMod.CreateReplayScore(drawableRuleset.Beatmap, drawableRuleset.Mods));
|
drawableRuleset.SetReplayScore(autoplayMod.CreateScoreFromReplayData(drawableRuleset.Beatmap, drawableRuleset.Mods));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addHitObject(HitObject hitObject)
|
private void addHitObject(HitObject hitObject)
|
||||||
|
@ -1,14 +1,22 @@
|
|||||||
// 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.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mods
|
namespace osu.Game.Rulesets.Mods
|
||||||
{
|
{
|
||||||
public interface ICreateReplay
|
[Obsolete("Use ICreateReplayData instead")] // Can be removed 20220929
|
||||||
|
public interface ICreateReplay : ICreateReplayData
|
||||||
{
|
{
|
||||||
public Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods);
|
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 });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
63
osu.Game/Rulesets/Mods/ICreateReplayData.cs
Normal file
63
osu.Game/Rulesets/Mods/ICreateReplayData.cs
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
// 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.Collections.Generic;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osu.Game.Replays;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Users;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mods
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A mod which creates full replay data, which is to be played back in place of a local user playing the game.
|
||||||
|
/// </summary>
|
||||||
|
public interface ICreateReplayData
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Create replay data.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="beatmap">The beatmap to create replay data for.</param>
|
||||||
|
/// <param name="mods">The mods to take into account when creating the replay data.</param>
|
||||||
|
/// <returns>A <see cref="ModReplayData"/> structure, containing the generated replay data.</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// For callers that want to receive a directly usable <see cref="Score"/> instance,
|
||||||
|
/// the <see cref="ModExtensions.CreateScoreFromReplayData"/> extension method is provided for convenience.
|
||||||
|
/// </remarks>
|
||||||
|
ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList<Mod> mods);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Data created by a mod that implements <see cref="ICreateReplayData"/>.
|
||||||
|
/// </summary>
|
||||||
|
public class ModReplayData
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The full replay data.
|
||||||
|
/// </summary>
|
||||||
|
public readonly Replay Replay;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Placeholder user data to show in place of the local user when the associated mod is active.
|
||||||
|
/// </summary>
|
||||||
|
public readonly ModCreatedUser User;
|
||||||
|
|
||||||
|
public ModReplayData(Replay replay, ModCreatedUser user = null)
|
||||||
|
{
|
||||||
|
Replay = replay;
|
||||||
|
User = user ?? new ModCreatedUser();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A user which is associated with a replay that was created by a mod (ie. autoplay or cinema).
|
||||||
|
/// </summary>
|
||||||
|
public class ModCreatedUser : IUser
|
||||||
|
{
|
||||||
|
public int OnlineID => APIUser.SYSTEM_USER_ID;
|
||||||
|
public bool IsBot => true;
|
||||||
|
|
||||||
|
public string Username { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
@ -11,7 +11,7 @@ using osu.Game.Scoring;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Mods
|
namespace osu.Game.Rulesets.Mods
|
||||||
{
|
{
|
||||||
public abstract class ModAutoplay : Mod, IApplicableFailOverride, ICreateReplay
|
public abstract class ModAutoplay : Mod, IApplicableFailOverride, ICreateReplayData
|
||||||
{
|
{
|
||||||
public override string Name => "Autoplay";
|
public override string Name => "Autoplay";
|
||||||
public override string Acronym => "AT";
|
public override string Acronym => "AT";
|
||||||
@ -26,10 +26,20 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
|
|
||||||
public override bool UserPlayable => false;
|
public override bool UserPlayable => false;
|
||||||
|
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail) };
|
public override Type[] IncompatibleMods => new[] { typeof(ModCinema), typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail) };
|
||||||
|
|
||||||
public override bool HasImplementation => GetType().GenericTypeArguments.Length == 0;
|
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 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 });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
public override string Description => "The whole playfield is on a wheel!";
|
public override string Description => "The whole playfield is on a wheel!";
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
|
|
||||||
public override string SettingDescription => $"{SpinSpeed.Value} rpm {Direction.Value.GetDescription().ToLowerInvariant()}";
|
public override string SettingDescription => $"{SpinSpeed.Value:N2} rpm {Direction.Value.GetDescription().ToLowerInvariant()}";
|
||||||
|
|
||||||
public void Update(Playfield playfield)
|
public void Update(Playfield playfield)
|
||||||
{
|
{
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
// 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.Linq;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
@ -14,8 +16,6 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
{
|
{
|
||||||
public virtual void ApplyToDrawableRuleset(DrawableRuleset<T> drawableRuleset)
|
public virtual void ApplyToDrawableRuleset(DrawableRuleset<T> drawableRuleset)
|
||||||
{
|
{
|
||||||
drawableRuleset.SetReplayScore(CreateReplayScore(drawableRuleset.Beatmap, drawableRuleset.Mods));
|
|
||||||
|
|
||||||
// AlwaysPresent required for hitsounds
|
// AlwaysPresent required for hitsounds
|
||||||
drawableRuleset.AlwaysPresent = true;
|
drawableRuleset.AlwaysPresent = true;
|
||||||
drawableRuleset.Hide();
|
drawableRuleset.Hide();
|
||||||
@ -29,6 +29,8 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
public override IconUsage? Icon => OsuIcon.ModCinema;
|
public override IconUsage? Icon => OsuIcon.ModCinema;
|
||||||
public override string Description => "Watch the video without visual distractions.";
|
public override string Description => "Watch the video without visual distractions.";
|
||||||
|
|
||||||
|
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModAutoplay)).ToArray();
|
||||||
|
|
||||||
public void ApplyToHUD(HUDOverlay overlay)
|
public void ApplyToHUD(HUDOverlay overlay)
|
||||||
{
|
{
|
||||||
overlay.ShowHud.Value = false;
|
overlay.ShowHud.Value = false;
|
||||||
|
31
osu.Game/Rulesets/Mods/ModExtensions.cs
Normal file
31
osu.Game/Rulesets/Mods/ModExtensions.cs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// 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.Collections.Generic;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mods
|
||||||
|
{
|
||||||
|
public static class ModExtensions
|
||||||
|
{
|
||||||
|
public static Score CreateScoreFromReplayData(this ICreateReplayData mod, IBeatmap beatmap, IReadOnlyList<Mod> mods)
|
||||||
|
{
|
||||||
|
var replayData = mod.CreateReplayData(beatmap, mods);
|
||||||
|
|
||||||
|
return new Score
|
||||||
|
{
|
||||||
|
Replay = replayData.Replay,
|
||||||
|
ScoreInfo =
|
||||||
|
{
|
||||||
|
User = new APIUser
|
||||||
|
{
|
||||||
|
Id = APIUser.SYSTEM_USER_ID,
|
||||||
|
Username = replayData.User.Username,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -90,12 +90,7 @@ namespace osu.Game.Scoring
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// <param name="score">The <see cref="ScoreInfo"/> to retrieve the bindable for.</param>
|
/// <param name="score">The <see cref="ScoreInfo"/> to retrieve the bindable for.</param>
|
||||||
/// <returns>The bindable containing the total score.</returns>
|
/// <returns>The bindable containing the total score.</returns>
|
||||||
public Bindable<long> GetBindableTotalScore([NotNull] ScoreInfo score)
|
public Bindable<long> GetBindableTotalScore([NotNull] ScoreInfo score) => new TotalScoreBindable(score, this, configManager);
|
||||||
{
|
|
||||||
var bindable = new TotalScoreBindable(score, this);
|
|
||||||
configManager?.BindWith(OsuSetting.ScoreDisplayMode, bindable.ScoringMode);
|
|
||||||
return bindable;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves a bindable that represents the formatted total score string of a <see cref="ScoreInfo"/>.
|
/// Retrieves a bindable that represents the formatted total score string of a <see cref="ScoreInfo"/>.
|
||||||
@ -118,7 +113,11 @@ namespace osu.Game.Scoring
|
|||||||
public void GetTotalScore([NotNull] ScoreInfo score, [NotNull] Action<long> callback, ScoringMode mode = ScoringMode.Standardised, CancellationToken cancellationToken = default)
|
public void GetTotalScore([NotNull] ScoreInfo score, [NotNull] Action<long> callback, ScoringMode mode = ScoringMode.Standardised, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
GetTotalScoreAsync(score, mode, cancellationToken)
|
GetTotalScoreAsync(score, mode, cancellationToken)
|
||||||
.ContinueWith(task => scheduler.Add(() => callback(task.GetResultSafely())), TaskContinuationOptions.OnlyOnRanToCompletion);
|
.ContinueWith(task => scheduler.Add(() =>
|
||||||
|
{
|
||||||
|
if (!cancellationToken.IsCancellationRequested)
|
||||||
|
callback(task.GetResultSafely());
|
||||||
|
}), TaskContinuationOptions.OnlyOnRanToCompletion);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -183,8 +182,7 @@ namespace osu.Game.Scoring
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private class TotalScoreBindable : Bindable<long>
|
private class TotalScoreBindable : Bindable<long>
|
||||||
{
|
{
|
||||||
public readonly Bindable<ScoringMode> ScoringMode = new Bindable<ScoringMode>();
|
private readonly Bindable<ScoringMode> scoringMode = new Bindable<ScoringMode>();
|
||||||
|
|
||||||
private readonly ScoreInfo score;
|
private readonly ScoreInfo score;
|
||||||
private readonly ScoreManager scoreManager;
|
private readonly ScoreManager scoreManager;
|
||||||
|
|
||||||
@ -195,12 +193,14 @@ namespace osu.Game.Scoring
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="score">The <see cref="ScoreInfo"/> to provide the total score of.</param>
|
/// <param name="score">The <see cref="ScoreInfo"/> to provide the total score of.</param>
|
||||||
/// <param name="scoreManager">The <see cref="ScoreManager"/>.</param>
|
/// <param name="scoreManager">The <see cref="ScoreManager"/>.</param>
|
||||||
public TotalScoreBindable(ScoreInfo score, ScoreManager scoreManager)
|
/// <param name="configManager">The config.</param>
|
||||||
|
public TotalScoreBindable(ScoreInfo score, ScoreManager scoreManager, OsuConfigManager configManager)
|
||||||
{
|
{
|
||||||
this.score = score;
|
this.score = score;
|
||||||
this.scoreManager = scoreManager;
|
this.scoreManager = scoreManager;
|
||||||
|
|
||||||
ScoringMode.BindValueChanged(onScoringModeChanged, true);
|
configManager?.BindWith(OsuSetting.ScoreDisplayMode, scoringMode);
|
||||||
|
scoringMode.BindValueChanged(onScoringModeChanged, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onScoringModeChanged(ValueChangedEvent<ScoringMode> mode)
|
private void onScoringModeChanged(ValueChangedEvent<ScoringMode> mode)
|
||||||
|
@ -79,10 +79,10 @@ namespace osu.Game.Screens.Menu
|
|||||||
|
|
||||||
private readonly ButtonArea buttonArea;
|
private readonly ButtonArea buttonArea;
|
||||||
|
|
||||||
private readonly Button backButton;
|
private readonly MainMenuButton backButton;
|
||||||
|
|
||||||
private readonly List<Button> buttonsTopLevel = new List<Button>();
|
private readonly List<MainMenuButton> buttonsTopLevel = new List<MainMenuButton>();
|
||||||
private readonly List<Button> buttonsPlay = new List<Button>();
|
private readonly List<MainMenuButton> buttonsPlay = new List<MainMenuButton>();
|
||||||
|
|
||||||
private Sample sampleBack;
|
private Sample sampleBack;
|
||||||
|
|
||||||
@ -100,8 +100,8 @@ namespace osu.Game.Screens.Menu
|
|||||||
|
|
||||||
buttonArea.AddRange(new Drawable[]
|
buttonArea.AddRange(new Drawable[]
|
||||||
{
|
{
|
||||||
new Button(ButtonSystemStrings.Settings, string.Empty, FontAwesome.Solid.Cog, new Color4(85, 85, 85, 255), () => OnSettings?.Invoke(), -WEDGE_WIDTH, Key.O),
|
new MainMenuButton(ButtonSystemStrings.Settings, string.Empty, FontAwesome.Solid.Cog, new Color4(85, 85, 85, 255), () => OnSettings?.Invoke(), -WEDGE_WIDTH, Key.O),
|
||||||
backButton = new Button(ButtonSystemStrings.Back, @"button-back-select", OsuIcon.LeftCircle, new Color4(51, 58, 94, 255), () => State = ButtonSystemState.TopLevel, -WEDGE_WIDTH)
|
backButton = new MainMenuButton(ButtonSystemStrings.Back, @"button-back-select", OsuIcon.LeftCircle, new Color4(51, 58, 94, 255), () => State = ButtonSystemState.TopLevel, -WEDGE_WIDTH)
|
||||||
{
|
{
|
||||||
VisibleState = ButtonSystemState.Play,
|
VisibleState = ButtonSystemState.Play,
|
||||||
},
|
},
|
||||||
@ -126,24 +126,24 @@ namespace osu.Game.Screens.Menu
|
|||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
private void load(AudioManager audio, IdleTracker idleTracker, GameHost host)
|
private void load(AudioManager audio, IdleTracker idleTracker, GameHost host)
|
||||||
{
|
{
|
||||||
buttonsPlay.Add(new Button(ButtonSystemStrings.Solo, @"button-solo-select", FontAwesome.Solid.User, new Color4(102, 68, 204, 255), () => OnSolo?.Invoke(), WEDGE_WIDTH, Key.P));
|
buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Solo, @"button-solo-select", FontAwesome.Solid.User, new Color4(102, 68, 204, 255), () => OnSolo?.Invoke(), WEDGE_WIDTH, Key.P));
|
||||||
buttonsPlay.Add(new Button(ButtonSystemStrings.Multi, @"button-generic-select", FontAwesome.Solid.Users, new Color4(94, 63, 186, 255), onMultiplayer, 0, Key.M));
|
buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Multi, @"button-generic-select", FontAwesome.Solid.Users, new Color4(94, 63, 186, 255), onMultiplayer, 0, Key.M));
|
||||||
buttonsPlay.Add(new Button(ButtonSystemStrings.Playlists, @"button-generic-select", OsuIcon.Charts, new Color4(94, 63, 186, 255), onPlaylists, 0, Key.L));
|
buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Playlists, @"button-generic-select", OsuIcon.Charts, new Color4(94, 63, 186, 255), onPlaylists, 0, Key.L));
|
||||||
buttonsPlay.ForEach(b => b.VisibleState = ButtonSystemState.Play);
|
buttonsPlay.ForEach(b => b.VisibleState = ButtonSystemState.Play);
|
||||||
|
|
||||||
buttonsTopLevel.Add(new Button(ButtonSystemStrings.Play, @"button-play-select", OsuIcon.Logo, new Color4(102, 68, 204, 255), () => State = ButtonSystemState.Play, WEDGE_WIDTH, Key.P));
|
buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Play, @"button-play-select", OsuIcon.Logo, new Color4(102, 68, 204, 255), () => State = ButtonSystemState.Play, WEDGE_WIDTH, Key.P));
|
||||||
buttonsTopLevel.Add(new Button(ButtonSystemStrings.Edit, @"button-edit-select", OsuIcon.EditCircle, new Color4(238, 170, 0, 255), () => OnEdit?.Invoke(), 0, Key.E));
|
buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Edit, @"button-edit-select", OsuIcon.EditCircle, new Color4(238, 170, 0, 255), () => OnEdit?.Invoke(), 0, Key.E));
|
||||||
buttonsTopLevel.Add(new Button(ButtonSystemStrings.Browse, @"button-direct-select", OsuIcon.ChevronDownCircle, new Color4(165, 204, 0, 255), () => OnBeatmapListing?.Invoke(), 0, Key.D));
|
buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Browse, @"button-direct-select", OsuIcon.ChevronDownCircle, new Color4(165, 204, 0, 255), () => OnBeatmapListing?.Invoke(), 0, Key.D));
|
||||||
|
|
||||||
if (host.CanExit)
|
if (host.CanExit)
|
||||||
buttonsTopLevel.Add(new Button(ButtonSystemStrings.Exit, string.Empty, OsuIcon.CrossCircle, new Color4(238, 51, 153, 255), () => OnExit?.Invoke(), 0, Key.Q));
|
buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Exit, string.Empty, OsuIcon.CrossCircle, new Color4(238, 51, 153, 255), () => OnExit?.Invoke(), 0, Key.Q));
|
||||||
|
|
||||||
buttonArea.AddRange(buttonsPlay);
|
buttonArea.AddRange(buttonsPlay);
|
||||||
buttonArea.AddRange(buttonsTopLevel);
|
buttonArea.AddRange(buttonsTopLevel);
|
||||||
|
|
||||||
buttonArea.ForEach(b =>
|
buttonArea.ForEach(b =>
|
||||||
{
|
{
|
||||||
if (b is Button)
|
if (b is MainMenuButton)
|
||||||
{
|
{
|
||||||
b.Origin = Anchor.CentreLeft;
|
b.Origin = Anchor.CentreLeft;
|
||||||
b.Anchor = Anchor.CentreLeft;
|
b.Anchor = Anchor.CentreLeft;
|
||||||
@ -305,7 +305,7 @@ namespace osu.Game.Screens.Menu
|
|||||||
{
|
{
|
||||||
buttonArea.ButtonSystemState = state;
|
buttonArea.ButtonSystemState = state;
|
||||||
|
|
||||||
foreach (var b in buttonArea.Children.OfType<Button>())
|
foreach (var b in buttonArea.Children.OfType<MainMenuButton>())
|
||||||
b.ButtonSystemState = state;
|
b.ButtonSystemState = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,7 +176,7 @@ namespace osu.Game.Screens.Menu
|
|||||||
|
|
||||||
private static readonly Color4 transparent_white = Color4.White.Opacity(0.2f);
|
private static readonly Color4 transparent_white = Color4.White.Opacity(0.2f);
|
||||||
|
|
||||||
private float[] audioData;
|
private readonly float[] audioData = new float[256];
|
||||||
|
|
||||||
private readonly QuadBatch<TexturedVertex2D> vertexBatch = new QuadBatch<TexturedVertex2D>(100, 10);
|
private readonly QuadBatch<TexturedVertex2D> vertexBatch = new QuadBatch<TexturedVertex2D>(100, 10);
|
||||||
|
|
||||||
@ -192,7 +192,8 @@ namespace osu.Game.Screens.Menu
|
|||||||
shader = Source.shader;
|
shader = Source.shader;
|
||||||
texture = Source.texture;
|
texture = Source.texture;
|
||||||
size = Source.DrawSize.X;
|
size = Source.DrawSize.X;
|
||||||
audioData = Source.frequencyAmplitudes;
|
|
||||||
|
Source.frequencyAmplitudes.AsSpan().CopyTo(audioData);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Draw(Action<TexturedVertex2D> vertexAction)
|
public override void Draw(Action<TexturedVertex2D> vertexAction)
|
||||||
|
@ -28,7 +28,7 @@ namespace osu.Game.Screens.Menu
|
|||||||
/// Button designed specifically for the osu!next main menu.
|
/// Button designed specifically for the osu!next main menu.
|
||||||
/// In order to correctly flow, we have to use a negative margin on the parent container (due to the parallelogram shape).
|
/// In order to correctly flow, we have to use a negative margin on the parent container (due to the parallelogram shape).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Button : BeatSyncedContainer, IStateful<ButtonState>
|
public class MainMenuButton : BeatSyncedContainer, IStateful<ButtonState>
|
||||||
{
|
{
|
||||||
public event Action<ButtonState> StateChanged;
|
public event Action<ButtonState> StateChanged;
|
||||||
|
|
||||||
@ -51,7 +51,7 @@ namespace osu.Game.Screens.Menu
|
|||||||
|
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => box.ReceivePositionalInputAt(screenSpacePos);
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => box.ReceivePositionalInputAt(screenSpacePos);
|
||||||
|
|
||||||
public Button(LocalisableString text, string sampleName, IconUsage symbol, Color4 colour, Action clickAction = null, float extraWidth = 0, Key triggerKey = Key.Unknown)
|
public MainMenuButton(LocalisableString text, string sampleName, IconUsage symbol, Color4 colour, Action clickAction = null, float extraWidth = 0, Key triggerKey = Key.Unknown)
|
||||||
{
|
{
|
||||||
this.sampleName = sampleName;
|
this.sampleName = sampleName;
|
||||||
this.clickAction = clickAction;
|
this.clickAction = clickAction;
|
||||||
@ -209,7 +209,7 @@ namespace osu.Game.Screens.Menu
|
|||||||
|
|
||||||
protected override bool OnKeyDown(KeyDownEvent e)
|
protected override bool OnKeyDown(KeyDownEvent e)
|
||||||
{
|
{
|
||||||
if (e.Repeat || e.ControlPressed || e.ShiftPressed || e.AltPressed)
|
if (e.Repeat || e.ControlPressed || e.ShiftPressed || e.AltPressed || e.SuperPressed)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (TriggerKey == e.Key && TriggerKey != Key.Unknown)
|
if (TriggerKey == e.Key && TriggerKey != Key.Unknown)
|
@ -7,6 +7,7 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Input.Bindings;
|
using osu.Game.Input.Bindings;
|
||||||
@ -93,7 +94,12 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected class SectionContainer : FillFlowContainer<Section>
|
/// <remarks>
|
||||||
|
/// <see cref="ReverseChildIDFillFlowContainer{T}"/> is used to ensure that if the nested <see cref="Section"/>s
|
||||||
|
/// use expanded overhanging content (like an <see cref="OsuDropdown{T}"/>'s dropdown),
|
||||||
|
/// then the overhanging content will be correctly Z-ordered.
|
||||||
|
/// </remarks>
|
||||||
|
protected class SectionContainer : ReverseChildIDFillFlowContainer<Section>
|
||||||
{
|
{
|
||||||
public SectionContainer()
|
public SectionContainer()
|
||||||
{
|
{
|
||||||
|
@ -30,7 +30,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
private Sample sampleReadyAll;
|
private Sample sampleReadyAll;
|
||||||
private Sample sampleUnready;
|
private Sample sampleUnready;
|
||||||
|
|
||||||
private readonly BindableBool enabled = new BindableBool();
|
private readonly MultiplayerReadyButton readyButton;
|
||||||
private readonly MultiplayerCountdownButton countdownButton;
|
private readonly MultiplayerCountdownButton countdownButton;
|
||||||
private int countReady;
|
private int countReady;
|
||||||
private ScheduledDelegate readySampleDelegate;
|
private ScheduledDelegate readySampleDelegate;
|
||||||
@ -50,12 +50,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
{
|
{
|
||||||
new Drawable[]
|
new Drawable[]
|
||||||
{
|
{
|
||||||
new MultiplayerReadyButton
|
readyButton = new MultiplayerReadyButton
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Size = Vector2.One,
|
Size = Vector2.One,
|
||||||
Action = onReadyClick,
|
Action = onReadyClick,
|
||||||
Enabled = { BindTarget = enabled },
|
|
||||||
},
|
},
|
||||||
countdownButton = new MultiplayerCountdownButton
|
countdownButton = new MultiplayerCountdownButton
|
||||||
{
|
{
|
||||||
@ -63,7 +62,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
Size = new Vector2(40, 1),
|
Size = new Vector2(40, 1),
|
||||||
Alpha = 0,
|
Alpha = 0,
|
||||||
Action = startCountdown,
|
Action = startCountdown,
|
||||||
Enabled = { BindTarget = enabled }
|
CancelAction = cancelCountdown
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -108,30 +107,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
Debug.Assert(clickOperation == null);
|
Debug.Assert(clickOperation == null);
|
||||||
clickOperation = ongoingOperationTracker.BeginOperation();
|
clickOperation = ongoingOperationTracker.BeginOperation();
|
||||||
|
|
||||||
// Ensure the current user becomes ready before being able to do anything else (start match, stop countdown, unready).
|
if (isReady() && Client.IsHost && Room.Countdown == null)
|
||||||
if (!isReady() || !Client.IsHost)
|
|
||||||
{
|
|
||||||
toggleReady();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Local user is the room host and is in a ready state.
|
|
||||||
// The only action they can take is to stop a countdown if one's currently running.
|
|
||||||
if (Room.Countdown != null)
|
|
||||||
{
|
|
||||||
stopCountdown();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// And if a countdown isn't running, start the match.
|
|
||||||
startMatch();
|
startMatch();
|
||||||
|
else
|
||||||
|
toggleReady();
|
||||||
|
|
||||||
bool isReady() => Client.LocalUser?.State == MultiplayerUserState.Ready || Client.LocalUser?.State == MultiplayerUserState.Spectating;
|
bool isReady() => Client.LocalUser?.State == MultiplayerUserState.Ready || Client.LocalUser?.State == MultiplayerUserState.Spectating;
|
||||||
|
|
||||||
void toggleReady() => Client.ToggleReady().ContinueWith(_ => endOperation());
|
void toggleReady() => Client.ToggleReady().ContinueWith(_ => endOperation());
|
||||||
|
|
||||||
void stopCountdown() => Client.SendMatchRequest(new StopCountdownRequest()).ContinueWith(_ => endOperation());
|
|
||||||
|
|
||||||
void startMatch() => Client.StartMatch().ContinueWith(t =>
|
void startMatch() => Client.StartMatch().ContinueWith(t =>
|
||||||
{
|
{
|
||||||
// accessing Exception here silences any potential errors from the antecedent task
|
// accessing Exception here silences any potential errors from the antecedent task
|
||||||
@ -153,6 +137,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
Client.SendMatchRequest(new StartMatchCountdownRequest { Duration = duration }).ContinueWith(_ => endOperation());
|
Client.SendMatchRequest(new StartMatchCountdownRequest { Duration = duration }).ContinueWith(_ => endOperation());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void cancelCountdown()
|
||||||
|
{
|
||||||
|
Debug.Assert(clickOperation == null);
|
||||||
|
clickOperation = ongoingOperationTracker.BeginOperation();
|
||||||
|
|
||||||
|
Client.SendMatchRequest(new StopCountdownRequest()).ContinueWith(_ => endOperation());
|
||||||
|
}
|
||||||
|
|
||||||
private void endOperation()
|
private void endOperation()
|
||||||
{
|
{
|
||||||
clickOperation?.Dispose();
|
clickOperation?.Dispose();
|
||||||
@ -163,7 +155,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
{
|
{
|
||||||
if (Room == null)
|
if (Room == null)
|
||||||
{
|
{
|
||||||
enabled.Value = false;
|
readyButton.Enabled.Value = false;
|
||||||
|
countdownButton.Enabled.Value = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,32 +165,33 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
int newCountReady = Room.Users.Count(u => u.State == MultiplayerUserState.Ready);
|
int newCountReady = Room.Users.Count(u => u.State == MultiplayerUserState.Ready);
|
||||||
int newCountTotal = Room.Users.Count(u => u.State != MultiplayerUserState.Spectating);
|
int newCountTotal = Room.Users.Count(u => u.State != MultiplayerUserState.Spectating);
|
||||||
|
|
||||||
if (Room.Countdown != null)
|
if (!Client.IsHost || Room.Settings.AutoStartEnabled)
|
||||||
countdownButton.Alpha = 0;
|
countdownButton.Hide();
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
switch (localUser?.State)
|
switch (localUser?.State)
|
||||||
{
|
{
|
||||||
default:
|
default:
|
||||||
countdownButton.Alpha = 0;
|
countdownButton.Hide();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case MultiplayerUserState.Idle:
|
||||||
case MultiplayerUserState.Spectating:
|
case MultiplayerUserState.Spectating:
|
||||||
case MultiplayerUserState.Ready:
|
case MultiplayerUserState.Ready:
|
||||||
countdownButton.Alpha = Room.Host?.Equals(localUser) == true ? 1 : 0;
|
countdownButton.Show();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enabled.Value =
|
readyButton.Enabled.Value = countdownButton.Enabled.Value =
|
||||||
Room.State == MultiplayerRoomState.Open
|
Room.State == MultiplayerRoomState.Open
|
||||||
&& CurrentPlaylistItem.Value?.ID == Room.Settings.PlaylistItemId
|
&& CurrentPlaylistItem.Value?.ID == Room.Settings.PlaylistItemId
|
||||||
&& !Room.Playlist.Single(i => i.ID == Room.Settings.PlaylistItemId).Expired
|
&& !Room.Playlist.Single(i => i.ID == Room.Settings.PlaylistItemId).Expired
|
||||||
&& !operationInProgress.Value;
|
&& !operationInProgress.Value;
|
||||||
|
|
||||||
// When the local user is the host and spectating the match, the "start match" state should be enabled if any users are ready.
|
// When the local user is the host and spectating the match, the ready button should be enabled only if any users are ready.
|
||||||
if (localUser?.State == MultiplayerUserState.Spectating)
|
if (localUser?.State == MultiplayerUserState.Spectating)
|
||||||
enabled.Value &= Room.Host?.Equals(localUser) == true && newCountReady > 0;
|
readyButton.Enabled.Value &= Client.IsHost && newCountReady > 0 && Room.Countdown == null;
|
||||||
|
|
||||||
if (newCountReady == countReady)
|
if (newCountReady == countReady)
|
||||||
return;
|
return;
|
||||||
|
@ -14,6 +14,7 @@ using osu.Framework.Graphics.UserInterface;
|
|||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Graphics.UserInterfaceV2;
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
|
using osu.Game.Online.Multiplayer;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||||
@ -30,12 +31,19 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
|
|
||||||
public new Action<TimeSpan> Action;
|
public new Action<TimeSpan> Action;
|
||||||
|
|
||||||
|
public Action CancelAction;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private MultiplayerClient multiplayerClient { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OsuColour colours { get; set; }
|
||||||
|
|
||||||
private readonly Drawable background;
|
private readonly Drawable background;
|
||||||
|
|
||||||
public MultiplayerCountdownButton()
|
public MultiplayerCountdownButton()
|
||||||
{
|
{
|
||||||
Icon = FontAwesome.Solid.CaretDown;
|
Icon = FontAwesome.Regular.Clock;
|
||||||
IconScale = new Vector2(0.6f);
|
|
||||||
|
|
||||||
Add(background = new Box
|
Add(background = new Box
|
||||||
{
|
{
|
||||||
@ -44,6 +52,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
});
|
});
|
||||||
|
|
||||||
base.Action = this.ShowPopover;
|
base.Action = this.ShowPopover;
|
||||||
|
|
||||||
|
TooltipText = "Countdown settings";
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -52,6 +62,38 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
background.Colour = colours.Green;
|
background.Colour = colours.Green;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
multiplayerClient.RoomUpdated += onRoomUpdated;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
multiplayerClient.RoomUpdated -= onRoomUpdated;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onRoomUpdated() => Scheduler.AddOnce(() =>
|
||||||
|
{
|
||||||
|
bool countdownActive = multiplayerClient.Room?.Countdown != null;
|
||||||
|
|
||||||
|
if (countdownActive)
|
||||||
|
{
|
||||||
|
background
|
||||||
|
.FadeColour(colours.YellowLight, 100, Easing.In)
|
||||||
|
.Then()
|
||||||
|
.FadeColour(colours.YellowDark, 900, Easing.OutQuint)
|
||||||
|
.Loop();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
background
|
||||||
|
.FadeColour(colours.Green, 200, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
public Popover GetPopover()
|
public Popover GetPopover()
|
||||||
{
|
{
|
||||||
var flow = new FillFlowContainer
|
var flow = new FillFlowContainer
|
||||||
@ -68,7 +110,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Text = $"Start match in {duration.Humanize()}",
|
Text = $"Start match in {duration.Humanize()}",
|
||||||
BackgroundColour = background.Colour,
|
BackgroundColour = colours.Green,
|
||||||
Action = () =>
|
Action = () =>
|
||||||
{
|
{
|
||||||
Action(duration);
|
Action(duration);
|
||||||
@ -77,6 +119,21 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (multiplayerClient.Room?.Countdown != null && multiplayerClient.IsHost)
|
||||||
|
{
|
||||||
|
flow.Add(new OsuButton
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Text = "Stop countdown",
|
||||||
|
BackgroundColour = colours.Red,
|
||||||
|
Action = () =>
|
||||||
|
{
|
||||||
|
CancelAction();
|
||||||
|
this.HidePopover();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return new OsuPopover { Child = flow };
|
return new OsuPopover { Child = flow };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// 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;
|
||||||
|
using System.ComponentModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
@ -21,6 +22,7 @@ using osu.Game.Online.Rooms;
|
|||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Screens.OnlinePlay.Match.Components;
|
using osu.Game.Screens.OnlinePlay.Match.Components;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
using Container = osu.Framework.Graphics.Containers.Container;
|
||||||
|
|
||||||
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||||
{
|
{
|
||||||
@ -56,7 +58,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
public Action SettingsApplied;
|
public Action SettingsApplied;
|
||||||
|
|
||||||
public OsuTextBox NameField, MaxParticipantsField;
|
public OsuTextBox NameField, MaxParticipantsField;
|
||||||
public RoomAvailabilityPicker AvailabilityPicker;
|
|
||||||
public MatchTypePicker TypePicker;
|
public MatchTypePicker TypePicker;
|
||||||
public OsuEnumDropdown<QueueMode> QueueModeDropdown;
|
public OsuEnumDropdown<QueueMode> QueueModeDropdown;
|
||||||
public OsuTextBox PasswordTextBox;
|
public OsuTextBox PasswordTextBox;
|
||||||
@ -64,6 +65,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
|
|
||||||
public OsuSpriteText ErrorText;
|
public OsuSpriteText ErrorText;
|
||||||
|
|
||||||
|
private OsuEnumDropdown<StartMode> startModeDropdown;
|
||||||
private OsuSpriteText typeLabel;
|
private OsuSpriteText typeLabel;
|
||||||
private LoadingLayer loadingLayer;
|
private LoadingLayer loadingLayer;
|
||||||
|
|
||||||
@ -163,14 +165,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
LengthLimit = 100,
|
LengthLimit = 100,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
new Section("Room visibility")
|
// new Section("Room visibility")
|
||||||
{
|
// {
|
||||||
Alpha = disabled_alpha,
|
// Alpha = disabled_alpha,
|
||||||
Child = AvailabilityPicker = new RoomAvailabilityPicker
|
// Child = AvailabilityPicker = new RoomAvailabilityPicker
|
||||||
{
|
// {
|
||||||
Enabled = { Value = false }
|
// Enabled = { Value = false }
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
new Section("Game type")
|
new Section("Game type")
|
||||||
{
|
{
|
||||||
Child = new FillFlowContainer
|
Child = new FillFlowContainer
|
||||||
@ -204,6 +206,18 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
RelativeSizeAxes = Axes.X
|
RelativeSizeAxes = Axes.X
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
new Section("Auto start")
|
||||||
|
{
|
||||||
|
Child = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = 40,
|
||||||
|
Child = startModeDropdown = new OsuEnumDropdown<StartMode>
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -321,12 +335,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
|
|
||||||
TypePicker.Current.BindValueChanged(type => typeLabel.Text = type.NewValue.GetLocalisableDescription(), true);
|
TypePicker.Current.BindValueChanged(type => typeLabel.Text = type.NewValue.GetLocalisableDescription(), true);
|
||||||
RoomName.BindValueChanged(name => NameField.Text = name.NewValue, true);
|
RoomName.BindValueChanged(name => NameField.Text = name.NewValue, true);
|
||||||
Availability.BindValueChanged(availability => AvailabilityPicker.Current.Value = availability.NewValue, true);
|
|
||||||
Type.BindValueChanged(type => TypePicker.Current.Value = type.NewValue, true);
|
Type.BindValueChanged(type => TypePicker.Current.Value = type.NewValue, true);
|
||||||
MaxParticipants.BindValueChanged(count => MaxParticipantsField.Text = count.NewValue?.ToString(), true);
|
MaxParticipants.BindValueChanged(count => MaxParticipantsField.Text = count.NewValue?.ToString(), true);
|
||||||
RoomID.BindValueChanged(roomId => playlistContainer.Alpha = roomId.NewValue == null ? 1 : 0, true);
|
RoomID.BindValueChanged(roomId => playlistContainer.Alpha = roomId.NewValue == null ? 1 : 0, true);
|
||||||
Password.BindValueChanged(password => PasswordTextBox.Text = password.NewValue ?? string.Empty, true);
|
Password.BindValueChanged(password => PasswordTextBox.Text = password.NewValue ?? string.Empty, true);
|
||||||
QueueMode.BindValueChanged(mode => QueueModeDropdown.Current.Value = mode.NewValue, true);
|
QueueMode.BindValueChanged(mode => QueueModeDropdown.Current.Value = mode.NewValue, true);
|
||||||
|
AutoStartDuration.BindValueChanged(duration => startModeDropdown.Current.Value = (StartMode)(int)duration.NewValue.TotalSeconds, true);
|
||||||
|
|
||||||
operationInProgress.BindTo(ongoingOperationTracker.InProgress);
|
operationInProgress.BindTo(ongoingOperationTracker.InProgress);
|
||||||
operationInProgress.BindValueChanged(v =>
|
operationInProgress.BindValueChanged(v =>
|
||||||
@ -363,6 +377,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
Debug.Assert(applyingSettingsOperation == null);
|
Debug.Assert(applyingSettingsOperation == null);
|
||||||
applyingSettingsOperation = ongoingOperationTracker.BeginOperation();
|
applyingSettingsOperation = ongoingOperationTracker.BeginOperation();
|
||||||
|
|
||||||
|
TimeSpan autoStartDuration = TimeSpan.FromSeconds((int)startModeDropdown.Current.Value);
|
||||||
|
|
||||||
// If the client is already in a room, update via the client.
|
// If the client is already in a room, update via the client.
|
||||||
// Otherwise, update the room directly in preparation for it to be submitted to the API on match creation.
|
// Otherwise, update the room directly in preparation for it to be submitted to the API on match creation.
|
||||||
if (client.Room != null)
|
if (client.Room != null)
|
||||||
@ -371,7 +387,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
name: NameField.Text,
|
name: NameField.Text,
|
||||||
password: PasswordTextBox.Text,
|
password: PasswordTextBox.Text,
|
||||||
matchType: TypePicker.Current.Value,
|
matchType: TypePicker.Current.Value,
|
||||||
queueMode: QueueModeDropdown.Current.Value)
|
queueMode: QueueModeDropdown.Current.Value,
|
||||||
|
autoStartDuration: autoStartDuration)
|
||||||
.ContinueWith(t => Schedule(() =>
|
.ContinueWith(t => Schedule(() =>
|
||||||
{
|
{
|
||||||
if (t.IsCompletedSuccessfully)
|
if (t.IsCompletedSuccessfully)
|
||||||
@ -383,10 +400,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
room.Name.Value = NameField.Text;
|
room.Name.Value = NameField.Text;
|
||||||
room.Availability.Value = AvailabilityPicker.Current.Value;
|
|
||||||
room.Type.Value = TypePicker.Current.Value;
|
room.Type.Value = TypePicker.Current.Value;
|
||||||
room.Password.Value = PasswordTextBox.Current.Value;
|
room.Password.Value = PasswordTextBox.Current.Value;
|
||||||
room.QueueMode.Value = QueueModeDropdown.Current.Value;
|
room.QueueMode.Value = QueueModeDropdown.Current.Value;
|
||||||
|
room.AutoStartDuration.Value = autoStartDuration;
|
||||||
|
|
||||||
if (int.TryParse(MaxParticipantsField.Text, out int max))
|
if (int.TryParse(MaxParticipantsField.Text, out int max))
|
||||||
room.MaxParticipants.Value = max;
|
room.MaxParticipants.Value = max;
|
||||||
@ -452,5 +469,23 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
Triangles.ColourDark = colours.YellowDark;
|
Triangles.ColourDark = colours.YellowDark;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private enum StartMode
|
||||||
|
{
|
||||||
|
[Description("Off")]
|
||||||
|
Off = 0,
|
||||||
|
|
||||||
|
[Description("30 seconds")]
|
||||||
|
Seconds_30 = 30,
|
||||||
|
|
||||||
|
[Description("1 minute")]
|
||||||
|
Seconds_60 = 60,
|
||||||
|
|
||||||
|
[Description("3 minutes")]
|
||||||
|
Seconds_180 = 180,
|
||||||
|
|
||||||
|
[Description("5 minutes")]
|
||||||
|
Seconds_300 = 300
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,18 +36,19 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
}
|
}
|
||||||
|
|
||||||
private MultiplayerCountdown countdown;
|
private MultiplayerCountdown countdown;
|
||||||
private DateTimeOffset countdownReceivedTime;
|
private DateTimeOffset countdownChangeTime;
|
||||||
private ScheduledDelegate countdownUpdateDelegate;
|
private ScheduledDelegate countdownUpdateDelegate;
|
||||||
|
|
||||||
private void onRoomUpdated() => Scheduler.AddOnce(() =>
|
private void onRoomUpdated() => Scheduler.AddOnce(() =>
|
||||||
{
|
{
|
||||||
if (countdown == null && room?.Countdown != null)
|
if (countdown != room?.Countdown)
|
||||||
countdownReceivedTime = DateTimeOffset.Now;
|
{
|
||||||
|
|
||||||
countdown = room?.Countdown;
|
countdown = room?.Countdown;
|
||||||
|
countdownChangeTime = DateTimeOffset.Now;
|
||||||
|
}
|
||||||
|
|
||||||
if (room?.Countdown != null)
|
if (countdown != null)
|
||||||
countdownUpdateDelegate ??= Scheduler.AddDelayed(updateButtonText, 1000, true);
|
countdownUpdateDelegate ??= Scheduler.AddDelayed(updateButtonText, 100, true);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
countdownUpdateDelegate?.Cancel();
|
countdownUpdateDelegate?.Cancel();
|
||||||
@ -74,7 +75,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
|
|
||||||
if (countdown != null)
|
if (countdown != null)
|
||||||
{
|
{
|
||||||
TimeSpan timeElapsed = DateTimeOffset.Now - countdownReceivedTime;
|
TimeSpan timeElapsed = DateTimeOffset.Now - countdownChangeTime;
|
||||||
TimeSpan countdownRemaining;
|
TimeSpan countdownRemaining;
|
||||||
|
|
||||||
if (timeElapsed > countdown.TimeRemaining)
|
if (timeElapsed > countdown.TimeRemaining)
|
||||||
@ -168,7 +169,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (room?.Countdown != null && multiplayerClient.IsHost && multiplayerClient.LocalUser?.State == MultiplayerUserState.Ready)
|
if (room?.Countdown != null && multiplayerClient.IsHost && multiplayerClient.LocalUser?.State == MultiplayerUserState.Ready && !room.Settings.AutoStartEnabled)
|
||||||
return "Cancel countdown";
|
return "Cancel countdown";
|
||||||
|
|
||||||
return base.TooltipText;
|
return base.TooltipText;
|
||||||
|
@ -80,6 +80,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
{
|
{
|
||||||
Schedule(() =>
|
Schedule(() =>
|
||||||
{
|
{
|
||||||
|
// If an error or server side trigger occurred this screen may have already exited by external means.
|
||||||
|
if (!this.IsCurrentScreen())
|
||||||
|
return;
|
||||||
|
|
||||||
loadingLayer.Hide();
|
loadingLayer.Hide();
|
||||||
|
|
||||||
if (t.IsFaulted)
|
if (t.IsFaulted)
|
||||||
|
@ -198,15 +198,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
|||||||
else
|
else
|
||||||
userModsDisplay.FadeOut(fade_time);
|
userModsDisplay.FadeOut(fade_time);
|
||||||
|
|
||||||
if (Client.IsHost && !User.Equals(Client.LocalUser))
|
kickButton.Alpha = Client.IsHost && !User.Equals(Client.LocalUser) ? 1 : 0;
|
||||||
kickButton.FadeIn(fade_time);
|
crown.Alpha = Room.Host?.Equals(User) == true ? 1 : 0;
|
||||||
else
|
|
||||||
kickButton.FadeOut(fade_time);
|
|
||||||
|
|
||||||
if (Room.Host?.Equals(User) == true)
|
|
||||||
crown.FadeIn(fade_time);
|
|
||||||
else
|
|
||||||
crown.FadeOut(fade_time);
|
|
||||||
|
|
||||||
// If the mods are updated at the end of the frame, the flow container will skip a reflow cycle: https://github.com/ppy/osu-framework/issues/4187
|
// If the mods are updated at the end of the frame, the flow container will skip a reflow cycle: https://github.com/ppy/osu-framework/issues/4187
|
||||||
// This looks particularly jarring here, so re-schedule the update to that start of our frame as a fix.
|
// This looks particularly jarring here, so re-schedule the update to that start of our frame as a fix.
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// 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.Linq;
|
using System.Linq;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -15,6 +16,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
|||||||
{
|
{
|
||||||
private FillFlowContainer<ParticipantPanel> panels;
|
private FillFlowContainer<ParticipantPanel> panels;
|
||||||
|
|
||||||
|
[CanBeNull]
|
||||||
|
private ParticipantPanel currentHostPanel;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
@ -55,6 +59,24 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
|||||||
// Add panels for all users new to the room.
|
// Add panels for all users new to the room.
|
||||||
foreach (var user in Room.Users.Except(panels.Select(p => p.User)))
|
foreach (var user in Room.Users.Except(panels.Select(p => p.User)))
|
||||||
panels.Add(new ParticipantPanel(user));
|
panels.Add(new ParticipantPanel(user));
|
||||||
|
|
||||||
|
if (currentHostPanel == null || !currentHostPanel.User.Equals(Room.Host))
|
||||||
|
{
|
||||||
|
// Reset position of previous host back to normal, if one existing.
|
||||||
|
if (currentHostPanel != null && panels.Contains(currentHostPanel))
|
||||||
|
panels.SetLayoutPosition(currentHostPanel, 0);
|
||||||
|
|
||||||
|
currentHostPanel = null;
|
||||||
|
|
||||||
|
// Change position of new host to display above all participants.
|
||||||
|
if (Room.Host != null)
|
||||||
|
{
|
||||||
|
currentHostPanel = panels.SingleOrDefault(u => u.User.Equals(Room.Host));
|
||||||
|
|
||||||
|
if (currentHostPanel != null)
|
||||||
|
panels.SetLayoutPosition(currentHostPanel, -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,6 +81,9 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
[Resolved(typeof(Room))]
|
[Resolved(typeof(Room))]
|
||||||
protected Bindable<QueueMode> QueueMode { get; private set; }
|
protected Bindable<QueueMode> QueueMode { get; private set; }
|
||||||
|
|
||||||
|
[Resolved(typeof(Room))]
|
||||||
|
protected Bindable<TimeSpan> AutoStartDuration { get; private set; }
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
private IBindable<PlaylistItem> subScreenSelectedItem { get; set; }
|
private IBindable<PlaylistItem> subScreenSelectedItem { get; set; }
|
||||||
|
|
||||||
|
@ -21,7 +21,9 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
|
|
||||||
private uint scheduledPopOutCurrentId;
|
private uint scheduledPopOutCurrentId;
|
||||||
|
|
||||||
private const double pop_out_duration = 150;
|
private const double big_pop_out_duration = 300;
|
||||||
|
|
||||||
|
private const double small_pop_out_duration = 100;
|
||||||
|
|
||||||
private const double fade_out_duration = 100;
|
private const double fade_out_duration = 100;
|
||||||
|
|
||||||
@ -65,32 +67,28 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
|
|
||||||
Margin = new MarginPadding(10);
|
Margin = new MarginPadding(10);
|
||||||
|
|
||||||
Scale = new Vector2(1.2f);
|
Scale = new Vector2(1.28f);
|
||||||
|
|
||||||
InternalChildren = new[]
|
InternalChildren = new[]
|
||||||
{
|
{
|
||||||
counterContainer = new Container
|
counterContainer = new Container
|
||||||
{
|
{
|
||||||
AutoSizeAxes = Axes.Both,
|
|
||||||
AlwaysPresent = true,
|
AlwaysPresent = true,
|
||||||
Children = new[]
|
Children = new[]
|
||||||
{
|
{
|
||||||
popOutCount = new LegacySpriteText(LegacyFont.Combo)
|
popOutCount = new LegacySpriteText(LegacyFont.Combo)
|
||||||
{
|
{
|
||||||
Alpha = 0,
|
Alpha = 0,
|
||||||
Margin = new MarginPadding(0.05f),
|
|
||||||
Blending = BlendingParameters.Additive,
|
Blending = BlendingParameters.Additive,
|
||||||
Anchor = Anchor.BottomLeft,
|
Anchor = Anchor.BottomLeft,
|
||||||
Origin = Anchor.BottomLeft,
|
|
||||||
BypassAutoSizeAxes = Axes.Both,
|
BypassAutoSizeAxes = Axes.Both,
|
||||||
},
|
},
|
||||||
displayedCountSpriteText = new LegacySpriteText(LegacyFont.Combo)
|
displayedCountSpriteText = new LegacySpriteText(LegacyFont.Combo)
|
||||||
{
|
{
|
||||||
// Initial text and AlwaysPresent allow the counter to have a size before it first displays a combo.
|
|
||||||
// This is useful for display in the skin editor.
|
|
||||||
Text = formatCount(0),
|
|
||||||
AlwaysPresent = true,
|
|
||||||
Alpha = 0,
|
Alpha = 0,
|
||||||
|
AlwaysPresent = true,
|
||||||
|
Anchor = Anchor.BottomLeft,
|
||||||
|
BypassAutoSizeAxes = Axes.Both,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -130,8 +128,25 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
((IHasText)displayedCountSpriteText).Text = formatCount(Current.Value);
|
((IHasText)displayedCountSpriteText).Text = formatCount(Current.Value);
|
||||||
|
((IHasText)popOutCount).Text = formatCount(Current.Value);
|
||||||
|
|
||||||
Current.BindValueChanged(combo => updateCount(combo.NewValue == 0), true);
|
Current.BindValueChanged(combo => updateCount(combo.NewValue == 0), true);
|
||||||
|
|
||||||
|
updateLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateLayout()
|
||||||
|
{
|
||||||
|
const float font_height_ratio = 0.625f;
|
||||||
|
const float vertical_offset = 9;
|
||||||
|
|
||||||
|
displayedCountSpriteText.OriginPosition = new Vector2(0, font_height_ratio * displayedCountSpriteText.Height + vertical_offset);
|
||||||
|
displayedCountSpriteText.Position = new Vector2(0, -(1 - font_height_ratio) * displayedCountSpriteText.Height + vertical_offset);
|
||||||
|
|
||||||
|
popOutCount.OriginPosition = new Vector2(3, font_height_ratio * popOutCount.Height + vertical_offset); // In stable, the bigger pop out scales a bit to the left
|
||||||
|
popOutCount.Position = new Vector2(0, -(1 - font_height_ratio) * popOutCount.Height + vertical_offset);
|
||||||
|
|
||||||
|
counterContainer.Size = displayedCountSpriteText.Size;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateCount(bool rolling)
|
private void updateCount(bool rolling)
|
||||||
@ -164,27 +179,31 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
{
|
{
|
||||||
((IHasText)popOutCount).Text = formatCount(newValue);
|
((IHasText)popOutCount).Text = formatCount(newValue);
|
||||||
|
|
||||||
popOutCount.ScaleTo(1.6f);
|
popOutCount.ScaleTo(1.56f)
|
||||||
popOutCount.FadeTo(0.75f);
|
.ScaleTo(1, big_pop_out_duration);
|
||||||
popOutCount.MoveTo(Vector2.Zero);
|
|
||||||
|
|
||||||
popOutCount.ScaleTo(1, pop_out_duration);
|
popOutCount.FadeTo(0.6f)
|
||||||
popOutCount.FadeOut(pop_out_duration);
|
.FadeOut(big_pop_out_duration);
|
||||||
popOutCount.MoveTo(displayedCountSpriteText.Position, pop_out_duration);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void transformNoPopOut(int newValue)
|
private void transformNoPopOut(int newValue)
|
||||||
{
|
{
|
||||||
((IHasText)displayedCountSpriteText).Text = formatCount(newValue);
|
((IHasText)displayedCountSpriteText).Text = formatCount(newValue);
|
||||||
|
|
||||||
|
counterContainer.Size = displayedCountSpriteText.Size;
|
||||||
|
|
||||||
displayedCountSpriteText.ScaleTo(1);
|
displayedCountSpriteText.ScaleTo(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void transformPopOutSmall(int newValue)
|
private void transformPopOutSmall(int newValue)
|
||||||
{
|
{
|
||||||
((IHasText)displayedCountSpriteText).Text = formatCount(newValue);
|
((IHasText)displayedCountSpriteText).Text = formatCount(newValue);
|
||||||
displayedCountSpriteText.ScaleTo(1.1f);
|
|
||||||
displayedCountSpriteText.ScaleTo(1, pop_out_duration);
|
counterContainer.Size = displayedCountSpriteText.Size;
|
||||||
|
|
||||||
|
displayedCountSpriteText.ScaleTo(1).Then()
|
||||||
|
.ScaleTo(1.1f, small_pop_out_duration / 2, Easing.In).Then()
|
||||||
|
.ScaleTo(1, small_pop_out_duration / 2, Easing.Out);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void scheduledPopOutSmall(uint id)
|
private void scheduledPopOutSmall(uint id)
|
||||||
@ -212,7 +231,7 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
Scheduler.AddDelayed(delegate
|
Scheduler.AddDelayed(delegate
|
||||||
{
|
{
|
||||||
scheduledPopOutSmall(newTaskId);
|
scheduledPopOutSmall(newTaskId);
|
||||||
}, pop_out_duration);
|
}, big_pop_out_duration - 140);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onCountRolling(int currentValue, int newValue)
|
private void onCountRolling(int currentValue, int newValue)
|
||||||
|
@ -287,12 +287,14 @@ namespace osu.Game.Screens.Select
|
|||||||
{
|
{
|
||||||
TitleLabel = new OsuSpriteText
|
TitleLabel = new OsuSpriteText
|
||||||
{
|
{
|
||||||
|
Current = { BindTarget = titleBinding },
|
||||||
Font = OsuFont.GetFont(size: 28, italics: true),
|
Font = OsuFont.GetFont(size: 28, italics: true),
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Truncate = true,
|
Truncate = true,
|
||||||
},
|
},
|
||||||
ArtistLabel = new OsuSpriteText
|
ArtistLabel = new OsuSpriteText
|
||||||
{
|
{
|
||||||
|
Current = { BindTarget = artistBinding },
|
||||||
Font = OsuFont.GetFont(size: 17, italics: true),
|
Font = OsuFont.GetFont(size: 17, italics: true),
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Truncate = true,
|
Truncate = true,
|
||||||
@ -314,9 +316,6 @@ namespace osu.Game.Screens.Select
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
titleBinding.BindValueChanged(_ => setMetadata(metadata.Source));
|
|
||||||
artistBinding.BindValueChanged(_ => setMetadata(metadata.Source), true);
|
|
||||||
|
|
||||||
addInfoLabels();
|
addInfoLabels();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -352,12 +351,6 @@ namespace osu.Game.Screens.Select
|
|||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setMetadata(string source)
|
|
||||||
{
|
|
||||||
ArtistLabel.Text = artistBinding.Value;
|
|
||||||
TitleLabel.Text = string.IsNullOrEmpty(source) ? titleBinding.Value : source + " — " + titleBinding.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addInfoLabels()
|
private void addInfoLabels()
|
||||||
{
|
{
|
||||||
if (working.Beatmap?.HitObjects?.Any() != true)
|
if (working.Beatmap?.HitObjects?.Any() != true)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// 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.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
@ -14,13 +15,13 @@ using osu.Game.Scoring;
|
|||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Screens.Ranking;
|
using osu.Game.Screens.Ranking;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
|
using osu.Game.Utils;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Select
|
namespace osu.Game.Screens.Select
|
||||||
{
|
{
|
||||||
public class PlaySongSelect : SongSelect
|
public class PlaySongSelect : SongSelect
|
||||||
{
|
{
|
||||||
private bool removeAutoModOnResume;
|
|
||||||
private OsuScreen playerLoader;
|
private OsuScreen playerLoader;
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
@ -43,25 +44,6 @@ namespace osu.Game.Screens.Select
|
|||||||
|
|
||||||
protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea();
|
protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea();
|
||||||
|
|
||||||
private ModAutoplay getAutoplayMod() => Ruleset.Value.CreateInstance().GetAutoplayMod();
|
|
||||||
|
|
||||||
public override void OnResuming(IScreen last)
|
|
||||||
{
|
|
||||||
base.OnResuming(last);
|
|
||||||
|
|
||||||
playerLoader = null;
|
|
||||||
|
|
||||||
if (removeAutoModOnResume)
|
|
||||||
{
|
|
||||||
var autoType = getAutoplayMod()?.GetType();
|
|
||||||
|
|
||||||
if (autoType != null)
|
|
||||||
Mods.Value = Mods.Value.Where(m => m.GetType() != autoType).ToArray();
|
|
||||||
|
|
||||||
removeAutoModOnResume = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool OnKeyDown(KeyDownEvent e)
|
protected override bool OnKeyDown(KeyDownEvent e)
|
||||||
{
|
{
|
||||||
switch (e.Key)
|
switch (e.Key)
|
||||||
@ -77,10 +59,16 @@ namespace osu.Game.Screens.Select
|
|||||||
return base.OnKeyDown(e);
|
return base.OnKeyDown(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IReadOnlyList<Mod> modsAtGameplayStart;
|
||||||
|
|
||||||
|
private ModAutoplay getAutoplayMod() => Ruleset.Value.CreateInstance().GetAutoplayMod();
|
||||||
|
|
||||||
protected override bool OnStart()
|
protected override bool OnStart()
|
||||||
{
|
{
|
||||||
if (playerLoader != null) return false;
|
if (playerLoader != null) return false;
|
||||||
|
|
||||||
|
modsAtGameplayStart = Mods.Value;
|
||||||
|
|
||||||
// Ctrl+Enter should start map with autoplay enabled.
|
// Ctrl+Enter should start map with autoplay enabled.
|
||||||
if (GetContainingInputManager().CurrentState?.Keyboard.ControlPressed == true)
|
if (GetContainingInputManager().CurrentState?.Keyboard.ControlPressed == true)
|
||||||
{
|
{
|
||||||
@ -95,13 +83,12 @@ namespace osu.Game.Screens.Select
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var mods = Mods.Value;
|
var mods = Mods.Value.Append(autoInstance).ToArray();
|
||||||
|
|
||||||
if (mods.All(m => m.GetType() != autoInstance.GetType()))
|
if (!ModUtils.CheckCompatibleSet(mods, out var invalid))
|
||||||
{
|
mods = mods.Except(invalid).Append(autoInstance).ToArray();
|
||||||
Mods.Value = mods.Append(autoInstance).ToArray();
|
|
||||||
removeAutoModOnResume = true;
|
Mods.Value = mods;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SampleConfirm?.Play();
|
SampleConfirm?.Play();
|
||||||
@ -111,12 +98,26 @@ namespace osu.Game.Screens.Select
|
|||||||
|
|
||||||
Player createPlayer()
|
Player createPlayer()
|
||||||
{
|
{
|
||||||
var replayGeneratingMod = Mods.Value.OfType<ICreateReplay>().FirstOrDefault();
|
var replayGeneratingMod = Mods.Value.OfType<ICreateReplayData>().FirstOrDefault();
|
||||||
|
|
||||||
if (replayGeneratingMod != null)
|
if (replayGeneratingMod != null)
|
||||||
return new ReplayPlayer((beatmap, mods) => replayGeneratingMod.CreateReplayScore(beatmap, mods));
|
{
|
||||||
|
return new ReplayPlayer((beatmap, mods) => replayGeneratingMod.CreateScoreFromReplayData(beatmap, mods));
|
||||||
|
}
|
||||||
|
|
||||||
return new SoloPlayer();
|
return new SoloPlayer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void OnResuming(IScreen last)
|
||||||
|
{
|
||||||
|
base.OnResuming(last);
|
||||||
|
|
||||||
|
if (playerLoader != null)
|
||||||
|
{
|
||||||
|
Mods.Value = modsAtGameplayStart;
|
||||||
|
playerLoader = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,11 +30,9 @@ namespace osu.Game.Skinning
|
|||||||
public DefaultLegacySkin(SkinInfo skin, IStorageResourceProvider resources)
|
public DefaultLegacySkin(SkinInfo skin, IStorageResourceProvider resources)
|
||||||
: base(
|
: base(
|
||||||
skin,
|
skin,
|
||||||
new NamespacedResourceStore<byte[]>(resources.Resources, "Skins/Legacy"),
|
|
||||||
resources,
|
resources,
|
||||||
// A default legacy skin may still have a skin.ini if it is modified by the user.
|
// In the case of the actual default legacy skin (ie. the fallback one, which a user hasn't applied any modifications to) we want to use the game provided resources.
|
||||||
// We must specify the stream directly as we are redirecting storage to the osu-resources location for other files.
|
skin.Protected ? new NamespacedResourceStore<byte[]>(resources.Resources, "Skins/Legacy") : null
|
||||||
new LegacyDatabasedSkinResourceStore(skin, resources.Files).GetStream("skin.ini")
|
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
Configuration.CustomColours["SliderBall"] = new Color4(2, 170, 255, 255);
|
Configuration.CustomColours["SliderBall"] = new Color4(2, 170, 255, 255);
|
||||||
|
@ -42,6 +42,9 @@ namespace osu.Game.Skinning.Editor
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuColour colours { get; set; }
|
private OsuColour colours { get; set; }
|
||||||
|
|
||||||
|
[Resolved(canBeNull: true)]
|
||||||
|
private SkinEditorOverlay skinEditorOverlay { get; set; }
|
||||||
|
|
||||||
[Cached]
|
[Cached]
|
||||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
|
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
|
||||||
|
|
||||||
@ -107,7 +110,7 @@ namespace osu.Game.Skinning.Editor
|
|||||||
new EditorMenuItem("Save", MenuItemType.Standard, Save),
|
new EditorMenuItem("Save", MenuItemType.Standard, Save),
|
||||||
new EditorMenuItem("Revert to default", MenuItemType.Destructive, revert),
|
new EditorMenuItem("Revert to default", MenuItemType.Destructive, revert),
|
||||||
new EditorMenuItemSpacer(),
|
new EditorMenuItemSpacer(),
|
||||||
new EditorMenuItem("Exit", MenuItemType.Standard, Hide),
|
new EditorMenuItem("Exit", MenuItemType.Standard, () => skinEditorOverlay?.Hide()),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ using osu.Game.Graphics.Sprites;
|
|||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Screens.Select;
|
using osu.Game.Screens.Select;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -94,7 +95,7 @@ namespace osu.Game.Skinning.Editor
|
|||||||
|
|
||||||
var replayGeneratingMod = ruleset.Value.CreateInstance().GetAutoplayMod();
|
var replayGeneratingMod = ruleset.Value.CreateInstance().GetAutoplayMod();
|
||||||
if (replayGeneratingMod != null)
|
if (replayGeneratingMod != null)
|
||||||
screen.Push(new PlayerLoader(() => new ReplayPlayer((beatmap, mods) => replayGeneratingMod.CreateReplayScore(beatmap, mods))));
|
screen.Push(new PlayerLoader(() => new ReplayPlayer((beatmap, mods) => replayGeneratingMod.CreateScoreFromReplayData(beatmap, mods))));
|
||||||
}, new[] { typeof(Player), typeof(SongSelect) })
|
}, new[] { typeof(Player), typeof(SongSelect) })
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
// 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 JetBrains.Annotations;
|
#nullable enable
|
||||||
|
|
||||||
using osu.Framework.Audio.Sample;
|
using osu.Framework.Audio.Sample;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -21,16 +22,14 @@ namespace osu.Game.Skinning
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="component">The requested component.</param>
|
/// <param name="component">The requested component.</param>
|
||||||
/// <returns>A drawable representation for the requested component, or null if unavailable.</returns>
|
/// <returns>A drawable representation for the requested component, or null if unavailable.</returns>
|
||||||
[CanBeNull]
|
Drawable? GetDrawableComponent(ISkinComponent component);
|
||||||
Drawable GetDrawableComponent(ISkinComponent component);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieve a <see cref="Texture"/>.
|
/// Retrieve a <see cref="Texture"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="componentName">The requested texture.</param>
|
/// <param name="componentName">The requested texture.</param>
|
||||||
/// <returns>A matching texture, or null if unavailable.</returns>
|
/// <returns>A matching texture, or null if unavailable.</returns>
|
||||||
[CanBeNull]
|
Texture? GetTexture(string componentName) => GetTexture(componentName, default, default);
|
||||||
Texture GetTexture(string componentName) => GetTexture(componentName, default, default);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieve a <see cref="Texture"/>.
|
/// Retrieve a <see cref="Texture"/>.
|
||||||
@ -39,23 +38,22 @@ namespace osu.Game.Skinning
|
|||||||
/// <param name="wrapModeS">The texture wrap mode in horizontal direction.</param>
|
/// <param name="wrapModeS">The texture wrap mode in horizontal direction.</param>
|
||||||
/// <param name="wrapModeT">The texture wrap mode in vertical direction.</param>
|
/// <param name="wrapModeT">The texture wrap mode in vertical direction.</param>
|
||||||
/// <returns>A matching texture, or null if unavailable.</returns>
|
/// <returns>A matching texture, or null if unavailable.</returns>
|
||||||
[CanBeNull]
|
Texture? GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT);
|
||||||
Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieve a <see cref="SampleChannel"/>.
|
/// Retrieve a <see cref="SampleChannel"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sampleInfo">The requested sample.</param>
|
/// <param name="sampleInfo">The requested sample.</param>
|
||||||
/// <returns>A matching sample channel, or null if unavailable.</returns>
|
/// <returns>A matching sample channel, or null if unavailable.</returns>
|
||||||
[CanBeNull]
|
ISample? GetSample(ISampleInfo sampleInfo);
|
||||||
ISample GetSample(ISampleInfo sampleInfo);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieve a configuration value.
|
/// Retrieve a configuration value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="lookup">The requested configuration value.</param>
|
/// <param name="lookup">The requested configuration value.</param>
|
||||||
/// <returns>A matching value boxed in an <see cref="IBindable{TValue}"/>, or null if unavailable.</returns>
|
/// <returns>A matching value boxed in an <see cref="IBindable{TValue}"/>, or null if unavailable.</returns>
|
||||||
[CanBeNull]
|
IBindable<TValue>? GetConfig<TLookup, TValue>(TLookup lookup)
|
||||||
IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup);
|
where TLookup : notnull
|
||||||
|
where TValue : notnull;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
using osu.Framework.Audio.Sample;
|
using osu.Framework.Audio.Sample;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.IO.Stores;
|
using osu.Framework.IO.Stores;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
@ -20,14 +23,28 @@ namespace osu.Game.Skinning
|
|||||||
protected override bool AllowManiaSkin => false;
|
protected override bool AllowManiaSkin => false;
|
||||||
protected override bool UseCustomSampleBanks => true;
|
protected override bool UseCustomSampleBanks => true;
|
||||||
|
|
||||||
public LegacyBeatmapSkin(BeatmapInfo beatmapInfo, IResourceStore<byte[]> storage, IStorageResourceProvider resources)
|
/// <summary>
|
||||||
: base(createSkinInfo(beatmapInfo), new LegacySkinResourceStore(beatmapInfo.BeatmapSet, storage), resources, beatmapInfo.Path)
|
/// Construct a new legacy beatmap skin instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="beatmapInfo">The model for this beatmap.</param>
|
||||||
|
/// <param name="resources">Access to raw game resources.</param>
|
||||||
|
public LegacyBeatmapSkin(BeatmapInfo beatmapInfo, IStorageResourceProvider? resources)
|
||||||
|
: base(createSkinInfo(beatmapInfo), resources, createRealmBackedStore(beatmapInfo, resources), beatmapInfo.Path.AsNonNull())
|
||||||
{
|
{
|
||||||
// Disallow default colours fallback on beatmap skins to allow using parent skin combo colours. (via SkinProvidingContainer)
|
// Disallow default colours fallback on beatmap skins to allow using parent skin combo colours. (via SkinProvidingContainer)
|
||||||
Configuration.AllowDefaultComboColoursFallback = false;
|
Configuration.AllowDefaultComboColoursFallback = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Drawable GetDrawableComponent(ISkinComponent component)
|
private static IResourceStore<byte[]> createRealmBackedStore(BeatmapInfo beatmapInfo, IStorageResourceProvider? resources)
|
||||||
|
{
|
||||||
|
if (resources == null)
|
||||||
|
// should only ever be used in tests.
|
||||||
|
return new ResourceStore<byte[]>();
|
||||||
|
|
||||||
|
return new RealmBackedResourceStore(beatmapInfo.BeatmapSet, resources.Files, new[] { @"ogg" });
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Drawable? GetDrawableComponent(ISkinComponent component)
|
||||||
{
|
{
|
||||||
if (component is SkinnableTargetComponent targetComponent)
|
if (component is SkinnableTargetComponent targetComponent)
|
||||||
{
|
{
|
||||||
@ -46,7 +63,7 @@ namespace osu.Game.Skinning
|
|||||||
return base.GetDrawableComponent(component);
|
return base.GetDrawableComponent(component);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
|
public override IBindable<TValue>? GetConfig<TLookup, TValue>(TLookup lookup)
|
||||||
{
|
{
|
||||||
switch (lookup)
|
switch (lookup)
|
||||||
{
|
{
|
||||||
@ -62,10 +79,10 @@ namespace osu.Game.Skinning
|
|||||||
return base.GetConfig<TLookup, TValue>(lookup);
|
return base.GetConfig<TLookup, TValue>(lookup);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IBindable<Color4> GetComboColour(IHasComboColours source, int comboIndex, IHasComboInformation combo)
|
protected override IBindable<Color4>? GetComboColour(IHasComboColours source, int comboIndex, IHasComboInformation combo)
|
||||||
=> base.GetComboColour(source, combo.ComboIndexWithOffsets, combo);
|
=> base.GetComboColour(source, combo.ComboIndexWithOffsets, combo);
|
||||||
|
|
||||||
public override ISample GetSample(ISampleInfo sampleInfo)
|
public override ISample? GetSample(ISampleInfo sampleInfo)
|
||||||
{
|
{
|
||||||
if (sampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacy && legacy.CustomSampleBank == 0)
|
if (sampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacy && legacy.CustomSampleBank == 0)
|
||||||
{
|
{
|
||||||
@ -77,6 +94,10 @@ namespace osu.Game.Skinning
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static SkinInfo createSkinInfo(BeatmapInfo beatmapInfo) =>
|
private static SkinInfo createSkinInfo(BeatmapInfo beatmapInfo) =>
|
||||||
new SkinInfo { Name = beatmapInfo.ToString(), Creator = beatmapInfo.Metadata.Author.Username ?? string.Empty };
|
new SkinInfo
|
||||||
|
{
|
||||||
|
Name = beatmapInfo.ToString(),
|
||||||
|
Creator = beatmapInfo.Metadata.Author.Username ?? string.Empty
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
@ -15,6 +17,7 @@ using osu.Framework.Graphics.Textures;
|
|||||||
using osu.Framework.IO.Stores;
|
using osu.Framework.IO.Stores;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Beatmaps.Formats;
|
using osu.Game.Beatmaps.Formats;
|
||||||
|
using osu.Game.Database;
|
||||||
using osu.Game.IO;
|
using osu.Game.IO;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
@ -27,12 +30,6 @@ namespace osu.Game.Skinning
|
|||||||
{
|
{
|
||||||
public class LegacySkin : Skin
|
public class LegacySkin : Skin
|
||||||
{
|
{
|
||||||
[CanBeNull]
|
|
||||||
protected TextureStore Textures;
|
|
||||||
|
|
||||||
[CanBeNull]
|
|
||||||
protected ISampleStore Samples;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether texture for the keys exists.
|
/// Whether texture for the keys exists.
|
||||||
/// Used to determine if the mania ruleset is skinned.
|
/// Used to determine if the mania ruleset is skinned.
|
||||||
@ -51,7 +48,7 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
[UsedImplicitly(ImplicitUseKindFlags.InstantiatedWithFixedConstructorSignature)]
|
[UsedImplicitly(ImplicitUseKindFlags.InstantiatedWithFixedConstructorSignature)]
|
||||||
public LegacySkin(SkinInfo skin, IStorageResourceProvider resources)
|
public LegacySkin(SkinInfo skin, IStorageResourceProvider resources)
|
||||||
: this(skin, new LegacyDatabasedSkinResourceStore(skin, resources.Files), resources, "skin.ini")
|
: this(skin, resources, null)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,36 +56,12 @@ namespace osu.Game.Skinning
|
|||||||
/// Construct a new legacy skin instance.
|
/// Construct a new legacy skin instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="skin">The model for this skin.</param>
|
/// <param name="skin">The model for this skin.</param>
|
||||||
/// <param name="storage">A storage for looking up files within this skin using user-facing filenames.</param>
|
|
||||||
/// <param name="resources">Access to raw game resources.</param>
|
/// <param name="resources">Access to raw game resources.</param>
|
||||||
|
/// <param name="storage">An optional store which will be used for looking up skin resources. If null, one will be created from realm <see cref="IHasRealmFiles"/> pattern.</param>
|
||||||
/// <param name="configurationFilename">The user-facing filename of the configuration file to be parsed. Can accept an .osu or skin.ini file.</param>
|
/// <param name="configurationFilename">The user-facing filename of the configuration file to be parsed. Can accept an .osu or skin.ini file.</param>
|
||||||
protected LegacySkin(SkinInfo skin, [CanBeNull] IResourceStore<byte[]> storage, [CanBeNull] IStorageResourceProvider resources, string configurationFilename)
|
protected LegacySkin(SkinInfo skin, IStorageResourceProvider? resources, IResourceStore<byte[]>? storage, string configurationFilename = @"skin.ini")
|
||||||
: this(skin, storage, resources, string.IsNullOrEmpty(configurationFilename) ? null : storage?.GetStream(configurationFilename))
|
: base(skin, resources, storage, configurationFilename)
|
||||||
{
|
{
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Construct a new legacy skin instance.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="skin">The model for this skin.</param>
|
|
||||||
/// <param name="storage">A storage for looking up files within this skin using user-facing filenames.</param>
|
|
||||||
/// <param name="resources">Access to raw game resources.</param>
|
|
||||||
/// <param name="configurationStream">An optional stream containing the contents of a skin.ini file.</param>
|
|
||||||
protected LegacySkin(SkinInfo skin, [CanBeNull] IResourceStore<byte[]> storage, [CanBeNull] IStorageResourceProvider resources, [CanBeNull] Stream configurationStream)
|
|
||||||
: base(skin, resources, configurationStream)
|
|
||||||
{
|
|
||||||
if (storage != null)
|
|
||||||
{
|
|
||||||
var samples = resources?.AudioManager?.GetSampleStore(storage);
|
|
||||||
if (samples != null)
|
|
||||||
samples.PlaybackConcurrency = OsuGameBase.SAMPLE_CONCURRENCY;
|
|
||||||
|
|
||||||
Samples = samples;
|
|
||||||
Textures = new TextureStore(resources?.CreateTextureLoaderStore(storage));
|
|
||||||
|
|
||||||
(storage as ResourceStore<byte[]>)?.AddExtension("ogg");
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo: this shouldn't really be duplicated here (from ManiaLegacySkinTransformer). we need to come up with a better solution.
|
// todo: this shouldn't really be duplicated here (from ManiaLegacySkinTransformer). we need to come up with a better solution.
|
||||||
hasKeyTexture = new Lazy<bool>(() => this.GetAnimation(
|
hasKeyTexture = new Lazy<bool>(() => this.GetAnimation(
|
||||||
lookupForMania<string>(new LegacyManiaSkinConfigurationLookup(4, LegacyManiaSkinConfigurationLookups.KeyImage, 0))?.Value ?? "mania-key1", true,
|
lookupForMania<string>(new LegacyManiaSkinConfigurationLookup(4, LegacyManiaSkinConfigurationLookups.KeyImage, 0))?.Value ?? "mania-key1", true,
|
||||||
@ -110,7 +83,7 @@ namespace osu.Game.Skinning
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
|
public override IBindable<TValue>? GetConfig<TLookup, TValue>(TLookup lookup)
|
||||||
{
|
{
|
||||||
switch (lookup)
|
switch (lookup)
|
||||||
{
|
{
|
||||||
@ -156,7 +129,7 @@ namespace osu.Game.Skinning
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IBindable<TValue> lookupForMania<TValue>(LegacyManiaSkinConfigurationLookup maniaLookup)
|
private IBindable<TValue>? lookupForMania<TValue>(LegacyManiaSkinConfigurationLookup maniaLookup)
|
||||||
{
|
{
|
||||||
if (!maniaConfigurations.TryGetValue(maniaLookup.Keys, out var existing))
|
if (!maniaConfigurations.TryGetValue(maniaLookup.Keys, out var existing))
|
||||||
maniaConfigurations[maniaLookup.Keys] = existing = new LegacyManiaSkinConfiguration(maniaLookup.Keys);
|
maniaConfigurations[maniaLookup.Keys] = existing = new LegacyManiaSkinConfiguration(maniaLookup.Keys);
|
||||||
@ -296,20 +269,20 @@ namespace osu.Game.Skinning
|
|||||||
/// <param name="source">The source to retrieve the combo colours from.</param>
|
/// <param name="source">The source to retrieve the combo colours from.</param>
|
||||||
/// <param name="colourIndex">The preferred index for retrieving the combo colour with.</param>
|
/// <param name="colourIndex">The preferred index for retrieving the combo colour with.</param>
|
||||||
/// <param name="combo">Information on the combo whose using the returned colour.</param>
|
/// <param name="combo">Information on the combo whose using the returned colour.</param>
|
||||||
protected virtual IBindable<Color4> GetComboColour(IHasComboColours source, int colourIndex, IHasComboInformation combo)
|
protected virtual IBindable<Color4>? GetComboColour(IHasComboColours source, int colourIndex, IHasComboInformation combo)
|
||||||
{
|
{
|
||||||
var colour = source.ComboColours?[colourIndex % source.ComboColours.Count];
|
var colour = source.ComboColours?[colourIndex % source.ComboColours.Count];
|
||||||
return colour.HasValue ? new Bindable<Color4>(colour.Value) : null;
|
return colour.HasValue ? new Bindable<Color4>(colour.Value) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IBindable<Color4> getCustomColour(IHasCustomColours source, string lookup)
|
private IBindable<Color4>? getCustomColour(IHasCustomColours source, string lookup)
|
||||||
=> source.CustomColours.TryGetValue(lookup, out var col) ? new Bindable<Color4>(col) : null;
|
=> source.CustomColours.TryGetValue(lookup, out var col) ? new Bindable<Color4>(col) : null;
|
||||||
|
|
||||||
private IBindable<string> getManiaImage(LegacyManiaSkinConfiguration source, string lookup)
|
private IBindable<string>? getManiaImage(LegacyManiaSkinConfiguration source, string lookup)
|
||||||
=> source.ImageLookups.TryGetValue(lookup, out string image) ? new Bindable<string>(image) : null;
|
=> source.ImageLookups.TryGetValue(lookup, out string image) ? new Bindable<string>(image) : null;
|
||||||
|
|
||||||
[CanBeNull]
|
private IBindable<TValue>? legacySettingLookup<TValue>(SkinConfiguration.LegacySetting legacySetting)
|
||||||
private IBindable<TValue> legacySettingLookup<TValue>(SkinConfiguration.LegacySetting legacySetting)
|
where TValue : notnull
|
||||||
{
|
{
|
||||||
switch (legacySetting)
|
switch (legacySetting)
|
||||||
{
|
{
|
||||||
@ -321,8 +294,9 @@ namespace osu.Game.Skinning
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[CanBeNull]
|
private IBindable<TValue>? genericLookup<TLookup, TValue>(TLookup lookup)
|
||||||
private IBindable<TValue> genericLookup<TLookup, TValue>(TLookup lookup)
|
where TLookup : notnull
|
||||||
|
where TValue : notnull
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -345,7 +319,7 @@ namespace osu.Game.Skinning
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Drawable GetDrawableComponent(ISkinComponent component)
|
public override Drawable? GetDrawableComponent(ISkinComponent component)
|
||||||
{
|
{
|
||||||
if (base.GetDrawableComponent(component) is Drawable c)
|
if (base.GetDrawableComponent(component) is Drawable c)
|
||||||
return c;
|
return c;
|
||||||
@ -385,8 +359,7 @@ namespace osu.Game.Skinning
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
Children = this.HasFont(LegacyFont.Score)
|
Children = new Drawable[]
|
||||||
? new Drawable[]
|
|
||||||
{
|
{
|
||||||
new LegacyComboCounter(),
|
new LegacyComboCounter(),
|
||||||
new LegacyScoreCounter(),
|
new LegacyScoreCounter(),
|
||||||
@ -395,16 +368,6 @@ namespace osu.Game.Skinning
|
|||||||
new SongProgress(),
|
new SongProgress(),
|
||||||
new BarHitErrorMeter(),
|
new BarHitErrorMeter(),
|
||||||
}
|
}
|
||||||
: new Drawable[]
|
|
||||||
{
|
|
||||||
// TODO: these should fallback to using osu!classic skin textures, rather than doing this.
|
|
||||||
new DefaultComboCounter(),
|
|
||||||
new DefaultScoreCounter(),
|
|
||||||
new DefaultAccuracyCounter(),
|
|
||||||
new DefaultHealthDisplay(),
|
|
||||||
new SongProgress(),
|
|
||||||
new BarHitErrorMeter(),
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return skinnableTargetWrapper;
|
return skinnableTargetWrapper;
|
||||||
@ -414,7 +377,7 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
case GameplaySkinComponent<HitResult> resultComponent:
|
case GameplaySkinComponent<HitResult> resultComponent:
|
||||||
// TODO: this should be inside the judgement pieces.
|
// TODO: this should be inside the judgement pieces.
|
||||||
Func<Drawable> createDrawable = () => getJudgementAnimation(resultComponent.Component);
|
Func<Drawable?> createDrawable = () => getJudgementAnimation(resultComponent.Component);
|
||||||
|
|
||||||
// kind of wasteful that we throw this away, but should do for now.
|
// kind of wasteful that we throw this away, but should do for now.
|
||||||
if (createDrawable() != null)
|
if (createDrawable() != null)
|
||||||
@ -433,7 +396,7 @@ namespace osu.Game.Skinning
|
|||||||
return this.GetAnimation(component.LookupName, false, false);
|
return this.GetAnimation(component.LookupName, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Texture getParticleTexture(HitResult result)
|
private Texture? getParticleTexture(HitResult result)
|
||||||
{
|
{
|
||||||
switch (result)
|
switch (result)
|
||||||
{
|
{
|
||||||
@ -450,7 +413,7 @@ namespace osu.Game.Skinning
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Drawable getJudgementAnimation(HitResult result)
|
private Drawable? getJudgementAnimation(HitResult result)
|
||||||
{
|
{
|
||||||
switch (result)
|
switch (result)
|
||||||
{
|
{
|
||||||
@ -470,7 +433,7 @@ namespace osu.Game.Skinning
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT)
|
public override Texture? GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT)
|
||||||
{
|
{
|
||||||
foreach (string name in getFallbackNames(componentName))
|
foreach (string name in getFallbackNames(componentName))
|
||||||
{
|
{
|
||||||
@ -498,7 +461,7 @@ namespace osu.Game.Skinning
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override ISample GetSample(ISampleInfo sampleInfo)
|
public override ISample? GetSample(ISampleInfo sampleInfo)
|
||||||
{
|
{
|
||||||
IEnumerable<string> lookupNames;
|
IEnumerable<string> lookupNames;
|
||||||
|
|
||||||
@ -551,12 +514,5 @@ namespace osu.Game.Skinning
|
|||||||
// Fall back to using the last piece for components coming from lazer (e.g. "Gameplay/osu/approachcircle" -> "approachcircle").
|
// Fall back to using the last piece for components coming from lazer (e.g. "Gameplay/osu/approachcircle" -> "approachcircle").
|
||||||
yield return componentName.Split('/').Last();
|
yield return componentName.Split('/').Last();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
|
||||||
{
|
|
||||||
base.Dispose(isDisposing);
|
|
||||||
Textures?.Dispose();
|
|
||||||
Samples?.Dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,39 +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 System.Linq;
|
|
||||||
using osu.Framework.Extensions;
|
|
||||||
using osu.Framework.IO.Stores;
|
|
||||||
using osu.Game.Database;
|
|
||||||
using osu.Game.Extensions;
|
|
||||||
|
|
||||||
namespace osu.Game.Skinning
|
|
||||||
{
|
|
||||||
public class LegacySkinResourceStore : ResourceStore<byte[]>
|
|
||||||
{
|
|
||||||
private readonly IHasNamedFiles source;
|
|
||||||
|
|
||||||
public LegacySkinResourceStore(IHasNamedFiles source, IResourceStore<byte[]> underlyingStore)
|
|
||||||
: base(underlyingStore)
|
|
||||||
{
|
|
||||||
this.source = source;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override IEnumerable<string> GetFilenames(string name)
|
|
||||||
{
|
|
||||||
foreach (string filename in base.GetFilenames(name))
|
|
||||||
{
|
|
||||||
string path = getPathForFile(filename.ToStandardisedPath());
|
|
||||||
if (path != null)
|
|
||||||
yield return path;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private string getPathForFile(string filename) =>
|
|
||||||
source.Files.FirstOrDefault(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase))?.File.GetStoragePath();
|
|
||||||
|
|
||||||
public override IEnumerable<string> GetAvailableResources() => source.Files.Select(f => f.Filename);
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,21 +4,29 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.IO.Stores;
|
using osu.Framework.IO.Stores;
|
||||||
|
using osu.Game.Database;
|
||||||
using osu.Game.Extensions;
|
using osu.Game.Extensions;
|
||||||
|
|
||||||
namespace osu.Game.Skinning
|
namespace osu.Game.Skinning
|
||||||
{
|
{
|
||||||
public class LegacyDatabasedSkinResourceStore : ResourceStore<byte[]>
|
public class RealmBackedResourceStore : ResourceStore<byte[]>
|
||||||
{
|
{
|
||||||
private readonly Dictionary<string, string> fileToStoragePathMapping = new Dictionary<string, string>();
|
private readonly Dictionary<string, string> fileToStoragePathMapping = new Dictionary<string, string>();
|
||||||
|
|
||||||
public LegacyDatabasedSkinResourceStore(SkinInfo source, IResourceStore<byte[]> underlyingStore)
|
public RealmBackedResourceStore(IHasRealmFiles source, IResourceStore<byte[]> underlyingStore, string[] extensions = null)
|
||||||
: base(underlyingStore)
|
: base(underlyingStore)
|
||||||
{
|
{
|
||||||
|
// Must be initialised before the file cache.
|
||||||
|
if (extensions != null)
|
||||||
|
{
|
||||||
|
foreach (string extension in extensions)
|
||||||
|
AddExtension(extension);
|
||||||
|
}
|
||||||
|
|
||||||
initialiseFileCache(source);
|
initialiseFileCache(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initialiseFileCache(SkinInfo source)
|
private void initialiseFileCache(IHasRealmFiles source)
|
||||||
{
|
{
|
||||||
fileToStoragePathMapping.Clear();
|
fileToStoragePathMapping.Clear();
|
||||||
foreach (var f in source.Files)
|
foreach (var f in source.Files)
|
@ -46,7 +46,10 @@ namespace osu.Game.Skinning
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IBindable<TValue>? GetConfig<TLookup, TValue>(TLookup lookup) => null;
|
public IBindable<TValue>? GetConfig<TLookup, TValue>(TLookup lookup)
|
||||||
|
where TLookup : notnull
|
||||||
|
where TValue : notnull
|
||||||
|
=> null;
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
@ -1,22 +1,24 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using JetBrains.Annotations;
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using osu.Framework.Audio.Sample;
|
using osu.Framework.Audio.Sample;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.OpenGL.Textures;
|
using osu.Framework.Graphics.OpenGL.Textures;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
|
using osu.Framework.IO.Stores;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.Extensions;
|
|
||||||
using osu.Game.IO;
|
using osu.Game.IO;
|
||||||
using osu.Game.Screens.Play.HUD;
|
using osu.Game.Screens.Play.HUD;
|
||||||
|
|
||||||
@ -24,8 +26,17 @@ namespace osu.Game.Skinning
|
|||||||
{
|
{
|
||||||
public abstract class Skin : IDisposable, ISkin
|
public abstract class Skin : IDisposable, ISkin
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A texture store which can be used to perform user file lookups for this skin.
|
||||||
|
/// </summary>
|
||||||
|
protected TextureStore? Textures { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A sample store which can be used to perform user file lookups for this skin.
|
||||||
|
/// </summary>
|
||||||
|
protected ISampleStore? Samples { get; }
|
||||||
|
|
||||||
public readonly Live<SkinInfo> SkinInfo;
|
public readonly Live<SkinInfo> SkinInfo;
|
||||||
private readonly IStorageResourceProvider resources;
|
|
||||||
|
|
||||||
public SkinConfiguration Configuration { get; set; }
|
public SkinConfiguration Configuration { get; set; }
|
||||||
|
|
||||||
@ -33,46 +44,61 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
private readonly Dictionary<SkinnableTarget, SkinnableInfo[]> drawableComponentInfo = new Dictionary<SkinnableTarget, SkinnableInfo[]>();
|
private readonly Dictionary<SkinnableTarget, SkinnableInfo[]> drawableComponentInfo = new Dictionary<SkinnableTarget, SkinnableInfo[]>();
|
||||||
|
|
||||||
public abstract ISample GetSample(ISampleInfo sampleInfo);
|
public abstract ISample? GetSample(ISampleInfo sampleInfo);
|
||||||
|
|
||||||
public Texture GetTexture(string componentName) => GetTexture(componentName, default, default);
|
public Texture? GetTexture(string componentName) => GetTexture(componentName, default, default);
|
||||||
|
|
||||||
public abstract Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT);
|
public abstract Texture? GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT);
|
||||||
|
|
||||||
public abstract IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup);
|
public abstract IBindable<TValue>? GetConfig<TLookup, TValue>(TLookup lookup)
|
||||||
|
where TLookup : notnull
|
||||||
|
where TValue : notnull;
|
||||||
|
|
||||||
protected Skin(SkinInfo skin, IStorageResourceProvider resources, [CanBeNull] Stream configurationStream = null)
|
/// <summary>
|
||||||
|
/// Construct a new skin.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="skin">The skin's metadata. Usually a live realm object.</param>
|
||||||
|
/// <param name="resources">Access to game-wide resources.</param>
|
||||||
|
/// <param name="storage">An optional store which will *replace* all file lookups that are usually sourced from <paramref name="skin"/>.</param>
|
||||||
|
/// <param name="configurationFilename">An optional filename to read the skin configuration from. If not provided, the configuration will be retrieved from the storage using "skin.ini".</param>
|
||||||
|
protected Skin(SkinInfo skin, IStorageResourceProvider? resources, IResourceStore<byte[]>? storage = null, string configurationFilename = @"skin.ini")
|
||||||
{
|
{
|
||||||
SkinInfo = resources?.RealmAccess != null
|
if (resources != null)
|
||||||
? skin.ToLive(resources.RealmAccess)
|
{
|
||||||
// This path should only be used in some tests.
|
SkinInfo = skin.ToLive(resources.RealmAccess);
|
||||||
: skin.ToLiveUnmanaged();
|
|
||||||
|
|
||||||
this.resources = resources;
|
storage ??= new RealmBackedResourceStore(skin, resources.Files, new[] { @"ogg" });
|
||||||
|
|
||||||
configurationStream ??= getConfigurationStream();
|
var samples = resources.AudioManager?.GetSampleStore(storage);
|
||||||
|
if (samples != null)
|
||||||
|
samples.PlaybackConcurrency = OsuGameBase.SAMPLE_CONCURRENCY;
|
||||||
|
|
||||||
|
Samples = samples;
|
||||||
|
Textures = new TextureStore(resources.CreateTextureLoaderStore(storage));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Generally only used for tests.
|
||||||
|
SkinInfo = skin.ToLiveUnmanaged();
|
||||||
|
}
|
||||||
|
|
||||||
|
var configurationStream = storage?.GetStream(configurationFilename);
|
||||||
|
|
||||||
if (configurationStream != null)
|
if (configurationStream != null)
|
||||||
|
{
|
||||||
// stream will be closed after use by LineBufferedReader.
|
// stream will be closed after use by LineBufferedReader.
|
||||||
ParseConfigurationStream(configurationStream);
|
ParseConfigurationStream(configurationStream);
|
||||||
|
Debug.Assert(Configuration != null);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
Configuration = new SkinConfiguration();
|
Configuration = new SkinConfiguration();
|
||||||
|
|
||||||
// skininfo files may be null for default skin.
|
// skininfo files may be null for default skin.
|
||||||
SkinInfo.PerformRead(s =>
|
|
||||||
{
|
|
||||||
// we may want to move this to some kind of async operation in the future.
|
|
||||||
foreach (SkinnableTarget skinnableTarget in Enum.GetValues(typeof(SkinnableTarget)))
|
foreach (SkinnableTarget skinnableTarget in Enum.GetValues(typeof(SkinnableTarget)))
|
||||||
{
|
{
|
||||||
string filename = $"{skinnableTarget}.json";
|
string filename = $"{skinnableTarget}.json";
|
||||||
|
|
||||||
// skininfo files may be null for default skin.
|
byte[]? bytes = storage?.Get(filename);
|
||||||
var fileInfo = s.Files.FirstOrDefault(f => f.Filename == filename);
|
|
||||||
|
|
||||||
if (fileInfo == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
byte[] bytes = resources?.Files.Get(fileInfo.File.GetStoragePath());
|
|
||||||
|
|
||||||
if (bytes == null)
|
if (bytes == null)
|
||||||
continue;
|
continue;
|
||||||
@ -92,7 +118,6 @@ namespace osu.Game.Skinning
|
|||||||
Logger.Error(ex, "Failed to load skin configuration.");
|
Logger.Error(ex, "Failed to load skin configuration.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void ParseConfigurationStream(Stream stream)
|
protected virtual void ParseConfigurationStream(Stream stream)
|
||||||
@ -101,16 +126,6 @@ namespace osu.Game.Skinning
|
|||||||
Configuration = new LegacySkinDecoder().Decode(reader);
|
Configuration = new LegacySkinDecoder().Decode(reader);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Stream getConfigurationStream()
|
|
||||||
{
|
|
||||||
string path = SkinInfo.PerformRead(s => s.Files.SingleOrDefault(f => f.Filename.Equals(@"skin.ini", StringComparison.OrdinalIgnoreCase))?.File.GetStoragePath());
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(path))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return resources?.Files.GetStream(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Remove all stored customisations for the provided target.
|
/// Remove all stored customisations for the provided target.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -129,7 +144,7 @@ namespace osu.Game.Skinning
|
|||||||
DrawableComponentInfo[targetContainer.Target] = targetContainer.CreateSkinnableInfo().ToArray();
|
DrawableComponentInfo[targetContainer.Target] = targetContainer.CreateSkinnableInfo().ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual Drawable GetDrawableComponent(ISkinComponent component)
|
public virtual Drawable? GetDrawableComponent(ISkinComponent component)
|
||||||
{
|
{
|
||||||
switch (component)
|
switch (component)
|
||||||
{
|
{
|
||||||
@ -137,9 +152,23 @@ namespace osu.Game.Skinning
|
|||||||
if (!DrawableComponentInfo.TryGetValue(target.Target, out var skinnableInfo))
|
if (!DrawableComponentInfo.TryGetValue(target.Target, out var skinnableInfo))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
var components = new List<Drawable>();
|
||||||
|
|
||||||
|
foreach (var i in skinnableInfo)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
components.Add(i.CreateInstance());
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.Error(e, $"Unable to create skin component {i.Type.Name}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return new SkinnableTargetComponentsContainer
|
return new SkinnableTargetComponentsContainer
|
||||||
{
|
{
|
||||||
ChildrenEnumerable = skinnableInfo.Select(i => i.CreateInstance())
|
Children = components,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,6 +197,9 @@ namespace osu.Game.Skinning
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
isDisposed = true;
|
isDisposed = true;
|
||||||
|
|
||||||
|
Textures?.Dispose();
|
||||||
|
Samples?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
@ -96,11 +96,13 @@ namespace osu.Game.Tests.Beatmaps
|
|||||||
AddStep("setup skins", () =>
|
AddStep("setup skins", () =>
|
||||||
{
|
{
|
||||||
userSkinInfo.Files.Clear();
|
userSkinInfo.Files.Clear();
|
||||||
|
if (!string.IsNullOrEmpty(userFile))
|
||||||
userSkinInfo.Files.Add(new RealmNamedFileUsage(new RealmFile { Hash = userFile }, userFile));
|
userSkinInfo.Files.Add(new RealmNamedFileUsage(new RealmFile { Hash = userFile }, userFile));
|
||||||
|
|
||||||
Debug.Assert(beatmapInfo.BeatmapSet != null);
|
Debug.Assert(beatmapInfo.BeatmapSet != null);
|
||||||
|
|
||||||
beatmapInfo.BeatmapSet.Files.Clear();
|
beatmapInfo.BeatmapSet.Files.Clear();
|
||||||
|
if (!string.IsNullOrEmpty(beatmapFile))
|
||||||
beatmapInfo.BeatmapSet.Files.Add(new RealmNamedFileUsage(new RealmFile { Hash = beatmapFile }, beatmapFile));
|
beatmapInfo.BeatmapSet.Files.Add(new RealmNamedFileUsage(new RealmFile { Hash = beatmapFile }, beatmapFile));
|
||||||
|
|
||||||
// Need to refresh the cached skin source to refresh the skin resource store.
|
// Need to refresh the cached skin source to refresh the skin resource store.
|
||||||
@ -191,22 +193,32 @@ namespace osu.Game.Tests.Beatmaps
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestWorkingBeatmap : ClockBackedTestWorkingBeatmap
|
private class TestWorkingBeatmap : ClockBackedTestWorkingBeatmap, IStorageResourceProvider
|
||||||
{
|
{
|
||||||
private readonly BeatmapInfo skinBeatmapInfo;
|
private readonly BeatmapInfo skinBeatmapInfo;
|
||||||
private readonly IResourceStore<byte[]> resourceStore;
|
|
||||||
|
|
||||||
private readonly IStorageResourceProvider resources;
|
private readonly IStorageResourceProvider resources;
|
||||||
|
|
||||||
public TestWorkingBeatmap(BeatmapInfo skinBeatmapInfo, IResourceStore<byte[]> resourceStore, IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock referenceClock, IStorageResourceProvider resources)
|
public TestWorkingBeatmap(BeatmapInfo skinBeatmapInfo, IResourceStore<byte[]> accessMarkingResourceStore, IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock referenceClock,
|
||||||
|
IStorageResourceProvider resources)
|
||||||
: base(beatmap, storyboard, referenceClock, resources.AudioManager)
|
: base(beatmap, storyboard, referenceClock, resources.AudioManager)
|
||||||
{
|
{
|
||||||
this.skinBeatmapInfo = skinBeatmapInfo;
|
this.skinBeatmapInfo = skinBeatmapInfo;
|
||||||
this.resourceStore = resourceStore;
|
Files = accessMarkingResourceStore;
|
||||||
this.resources = resources;
|
this.resources = resources;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected internal override ISkin GetSkin() => new LegacyBeatmapSkin(skinBeatmapInfo, resourceStore, resources);
|
protected internal override ISkin GetSkin() => new LegacyBeatmapSkin(skinBeatmapInfo, this);
|
||||||
|
|
||||||
|
public AudioManager AudioManager => resources.AudioManager;
|
||||||
|
|
||||||
|
public IResourceStore<byte[]> Files { get; }
|
||||||
|
|
||||||
|
public IResourceStore<byte[]> Resources => resources.Resources;
|
||||||
|
|
||||||
|
public RealmAccess RealmAccess => resources.RealmAccess;
|
||||||
|
|
||||||
|
public IResourceStore<TextureUpload> CreateTextureLoaderStore(IResourceStore<byte[]> underlyingStore) => resources.CreateTextureLoaderStore(underlyingStore);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ using System.Linq;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.IO.Stores;
|
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
@ -112,7 +111,7 @@ namespace osu.Game.Tests.Beatmaps
|
|||||||
public static readonly Color4 HYPER_DASH_FRUIT_COLOUR = Color4.DarkGoldenrod;
|
public static readonly Color4 HYPER_DASH_FRUIT_COLOUR = Color4.DarkGoldenrod;
|
||||||
|
|
||||||
public TestBeatmapSkin(BeatmapInfo beatmapInfo, bool hasColours)
|
public TestBeatmapSkin(BeatmapInfo beatmapInfo, bool hasColours)
|
||||||
: base(beatmapInfo, new ResourceStore<byte[]>(), null)
|
: base(beatmapInfo, null)
|
||||||
{
|
{
|
||||||
if (hasColours)
|
if (hasColours)
|
||||||
{
|
{
|
||||||
@ -141,7 +140,7 @@ namespace osu.Game.Tests.Beatmaps
|
|||||||
public static readonly Color4 HYPER_DASH_FRUIT_COLOUR = Color4.LightCyan;
|
public static readonly Color4 HYPER_DASH_FRUIT_COLOUR = Color4.LightCyan;
|
||||||
|
|
||||||
public TestSkin(bool hasCustomColours)
|
public TestSkin(bool hasCustomColours)
|
||||||
: base(new SkinInfo(), new ResourceStore<byte[]>(), null, string.Empty)
|
: base(new SkinInfo(), null, null)
|
||||||
{
|
{
|
||||||
if (hasCustomColours)
|
if (hasCustomColours)
|
||||||
{
|
{
|
||||||
|
@ -119,6 +119,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
Debug.Assert(Room != null);
|
Debug.Assert(Room != null);
|
||||||
|
|
||||||
((IMultiplayerClient)this).UserStateChanged(userId, newState);
|
((IMultiplayerClient)this).UserStateChanged(userId, newState);
|
||||||
|
updateRoomStateIfRequired();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateRoomStateIfRequired()
|
||||||
|
{
|
||||||
|
Debug.Assert(Room != null);
|
||||||
|
Debug.Assert(APIRoom != null);
|
||||||
|
|
||||||
Schedule(() =>
|
Schedule(() =>
|
||||||
{
|
{
|
||||||
@ -126,13 +133,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
{
|
{
|
||||||
case MultiplayerRoomState.Open:
|
case MultiplayerRoomState.Open:
|
||||||
// If there are no remaining ready users or the host is not ready, stop any existing countdown.
|
// If there are no remaining ready users or the host is not ready, stop any existing countdown.
|
||||||
// Todo: When we have an "automatic start" mode, this should also start a new countdown if any users _are_ ready.
|
|
||||||
// Todo: This doesn't yet support non-match-start countdowns.
|
// Todo: This doesn't yet support non-match-start countdowns.
|
||||||
bool shouldStopCountdown = Room.Users.All(u => u.State != MultiplayerUserState.Ready);
|
if (Room.Settings.AutoStartEnabled)
|
||||||
shouldStopCountdown |= Room.Host?.State != MultiplayerUserState.Ready && Room.Host?.State != MultiplayerUserState.Spectating;
|
{
|
||||||
|
bool shouldHaveCountdown = !APIRoom.Playlist.GetCurrentItem()!.Expired && Room.Users.Any(u => u.State == MultiplayerUserState.Ready);
|
||||||
|
|
||||||
|
if (shouldHaveCountdown && Room.Countdown == null)
|
||||||
|
startCountdown(new MatchStartCountdown { TimeRemaining = Room.Settings.AutoStartDuration }, StartMatch);
|
||||||
|
}
|
||||||
|
|
||||||
if (shouldStopCountdown)
|
|
||||||
countdownStopSource?.Cancel();
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MultiplayerRoomState.WaitingForLoad:
|
case MultiplayerRoomState.WaitingForLoad:
|
||||||
@ -204,7 +213,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
Name = apiRoom.Name.Value,
|
Name = apiRoom.Name.Value,
|
||||||
MatchType = apiRoom.Type.Value,
|
MatchType = apiRoom.Type.Value,
|
||||||
Password = password,
|
Password = password,
|
||||||
QueueMode = apiRoom.QueueMode.Value
|
QueueMode = apiRoom.QueueMode.Value,
|
||||||
|
AutoStartDuration = apiRoom.AutoStartDuration.Value
|
||||||
},
|
},
|
||||||
Playlist = serverSidePlaylist.ToList(),
|
Playlist = serverSidePlaylist.ToList(),
|
||||||
Users = { localUser },
|
Users = { localUser },
|
||||||
@ -263,6 +273,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
ChangeUserState(user.UserID, MultiplayerUserState.Idle);
|
ChangeUserState(user.UserID, MultiplayerUserState.Idle);
|
||||||
|
|
||||||
await changeMatchType(settings.MatchType).ConfigureAwait(false);
|
await changeMatchType(settings.MatchType).ConfigureAwait(false);
|
||||||
|
updateRoomStateIfRequired();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task ChangeState(MultiplayerUserState newState)
|
public override Task ChangeState(MultiplayerUserState newState)
|
||||||
@ -315,15 +326,42 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
switch (request)
|
switch (request)
|
||||||
{
|
{
|
||||||
case StartMatchCountdownRequest matchCountdownRequest:
|
case StartMatchCountdownRequest matchCountdownRequest:
|
||||||
|
startCountdown(new MatchStartCountdown { TimeRemaining = matchCountdownRequest.Duration }, StartMatch);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StopCountdownRequest _:
|
||||||
|
stopCountdown();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ChangeTeamRequest changeTeam:
|
||||||
|
|
||||||
|
TeamVersusRoomState roomState = (TeamVersusRoomState)Room.MatchState!;
|
||||||
|
TeamVersusUserState userState = (TeamVersusUserState)LocalUser.MatchState!;
|
||||||
|
|
||||||
|
var targetTeam = roomState.Teams.FirstOrDefault(t => t.ID == changeTeam.TeamID);
|
||||||
|
|
||||||
|
if (targetTeam != null)
|
||||||
|
{
|
||||||
|
userState.TeamID = targetTeam.ID;
|
||||||
|
|
||||||
|
await ((IMultiplayerClient)this).MatchUserStateChanged(LocalUser.UserID, userState).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startCountdown(MultiplayerCountdown countdown, Func<Task> continuation)
|
||||||
|
{
|
||||||
|
Debug.Assert(Room != null);
|
||||||
Debug.Assert(ThreadSafety.IsUpdateThread);
|
Debug.Assert(ThreadSafety.IsUpdateThread);
|
||||||
|
|
||||||
countdownStopSource?.Cancel();
|
stopCountdown();
|
||||||
|
|
||||||
// Note that this will leak CTSs, however this is a test method and we haven't noticed foregoing disposal of non-linked CTSs to be detrimental.
|
// Note that this will leak CTSs, however this is a test method and we haven't noticed foregoing disposal of non-linked CTSs to be detrimental.
|
||||||
// If necessary, this can be moved into the final schedule below, and the class-level fields be nulled out accordingly.
|
// If necessary, this can be moved into the final schedule below, and the class-level fields be nulled out accordingly.
|
||||||
var stopSource = countdownStopSource = new CancellationTokenSource();
|
var stopSource = countdownStopSource = new CancellationTokenSource();
|
||||||
var skipSource = countdownSkipSource = new CancellationTokenSource();
|
var skipSource = countdownSkipSource = new CancellationTokenSource();
|
||||||
var countdown = new MatchStartCountdown { TimeRemaining = matchCountdownRequest.Duration };
|
|
||||||
|
|
||||||
Task lastCountdownTask = countdownTask;
|
Task lastCountdownTask = countdownTask;
|
||||||
countdownTask = start();
|
countdownTask = start();
|
||||||
@ -344,7 +382,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (var cancellationSource = CancellationTokenSource.CreateLinkedTokenSource(stopSource.Token, skipSource.Token))
|
using (var cancellationSource = CancellationTokenSource.CreateLinkedTokenSource(stopSource.Token, skipSource.Token))
|
||||||
await Task.Delay(matchCountdownRequest.Duration, cancellationSource.Token).ConfigureAwait(false);
|
await Task.Delay(countdown.TimeRemaining, cancellationSource.Token).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
@ -362,36 +400,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
if (stopSource.IsCancellationRequested)
|
if (stopSource.IsCancellationRequested)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
StartMatch().WaitSafely();
|
continuation().WaitSafely();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case StopCountdownRequest _:
|
|
||||||
countdownStopSource?.Cancel();
|
|
||||||
|
|
||||||
Room.Countdown = null;
|
|
||||||
await MatchEvent(new CountdownChangedEvent { Countdown = Room.Countdown });
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ChangeTeamRequest changeTeam:
|
|
||||||
|
|
||||||
TeamVersusRoomState roomState = (TeamVersusRoomState)Room.MatchState!;
|
|
||||||
TeamVersusUserState userState = (TeamVersusUserState)LocalUser.MatchState!;
|
|
||||||
|
|
||||||
var targetTeam = roomState.Teams.FirstOrDefault(t => t.ID == changeTeam.TeamID);
|
|
||||||
|
|
||||||
if (targetTeam != null)
|
|
||||||
{
|
|
||||||
userState.TeamID = targetTeam.ID;
|
|
||||||
|
|
||||||
await ((IMultiplayerClient)this).MatchUserStateChanged(LocalUser.UserID, userState).ConfigureAwait(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
private void stopCountdown() => countdownStopSource?.Cancel();
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Task StartMatch()
|
public override Task StartMatch()
|
||||||
{
|
{
|
||||||
@ -427,6 +441,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
|
|
||||||
await addItem(item).ConfigureAwait(false);
|
await addItem(item).ConfigureAwait(false);
|
||||||
await updateCurrentItem(Room).ConfigureAwait(false);
|
await updateCurrentItem(Room).ConfigureAwait(false);
|
||||||
|
updateRoomStateIfRequired();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task AddPlaylistItem(MultiplayerPlaylistItem item) => AddUserPlaylistItem(api.LocalUser.Value.OnlineID, item);
|
public override Task AddPlaylistItem(MultiplayerPlaylistItem item) => AddUserPlaylistItem(api.LocalUser.Value.OnlineID, item);
|
||||||
@ -483,6 +498,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
await ((IMultiplayerClient)this).PlaylistItemRemoved(playlistItemId).ConfigureAwait(false);
|
await ((IMultiplayerClient)this).PlaylistItemRemoved(playlistItemId).ConfigureAwait(false);
|
||||||
|
|
||||||
await updateCurrentItem(Room).ConfigureAwait(false);
|
await updateCurrentItem(Room).ConfigureAwait(false);
|
||||||
|
updateRoomStateIfRequired();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task RemovePlaylistItem(long playlistItemId) => RemoveUserPlaylistItem(api.LocalUser.Value.OnlineID, playlistItemId);
|
public override Task RemovePlaylistItem(long playlistItemId) => RemoveUserPlaylistItem(api.LocalUser.Value.OnlineID, playlistItemId);
|
||||||
|
@ -74,7 +74,7 @@ namespace osu.Game.Tests.Visual
|
|||||||
[TearDownSteps]
|
[TearDownSteps]
|
||||||
public void TearDownSteps()
|
public void TearDownSteps()
|
||||||
{
|
{
|
||||||
if (DebugUtils.IsNUnitRunning)
|
if (DebugUtils.IsNUnitRunning && Game != null)
|
||||||
{
|
{
|
||||||
AddStep("exit game", () => Game.Exit());
|
AddStep("exit game", () => Game.Exit());
|
||||||
AddUntilStep("wait for game exit", () => Game.Parent == null);
|
AddUntilStep("wait for game exit", () => Game.Parent == null);
|
||||||
|
@ -115,11 +115,13 @@ namespace osu.Game.Tests.Visual
|
|||||||
|
|
||||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||||
{
|
{
|
||||||
headlessHostStorage = (parent.Get<GameHost>() as HeadlessGameHost)?.Storage;
|
var host = parent.Get<GameHost>();
|
||||||
|
|
||||||
|
headlessHostStorage = (host as HeadlessGameHost)?.Storage;
|
||||||
|
|
||||||
Resources = parent.Get<OsuGameBase>().Resources;
|
Resources = parent.Get<OsuGameBase>().Resources;
|
||||||
|
|
||||||
realm = new Lazy<RealmAccess>(() => new RealmAccess(LocalStorage, "client"));
|
realm = new Lazy<RealmAccess>(() => new RealmAccess(LocalStorage, OsuGameBase.CLIENT_DATABASE_FILENAME, host.UpdateThread));
|
||||||
|
|
||||||
RecycleLocalStorage(false);
|
RecycleLocalStorage(false);
|
||||||
|
|
||||||
|
@ -96,7 +96,7 @@ namespace osu.Game.Tests.Visual
|
|||||||
},
|
},
|
||||||
new OsuSpriteText
|
new OsuSpriteText
|
||||||
{
|
{
|
||||||
Text = skin?.SkinInfo?.Value.Name ?? "none",
|
Text = skin?.SkinInfo.Value.Name ?? "none",
|
||||||
Scale = new Vector2(1.5f),
|
Scale = new Vector2(1.5f),
|
||||||
Padding = new MarginPadding(5),
|
Padding = new MarginPadding(5),
|
||||||
},
|
},
|
||||||
@ -187,7 +187,7 @@ namespace osu.Game.Tests.Visual
|
|||||||
private readonly bool extrapolateAnimations;
|
private readonly bool extrapolateAnimations;
|
||||||
|
|
||||||
public TestLegacySkin(SkinInfo skin, IResourceStore<byte[]> storage, IStorageResourceProvider resources, bool extrapolateAnimations)
|
public TestLegacySkin(SkinInfo skin, IResourceStore<byte[]> storage, IStorageResourceProvider resources, bool extrapolateAnimations)
|
||||||
: base(skin, storage, resources, "skin.ini")
|
: base(skin, resources, storage)
|
||||||
{
|
{
|
||||||
this.extrapolateAnimations = extrapolateAnimations;
|
this.extrapolateAnimations = extrapolateAnimations;
|
||||||
}
|
}
|
||||||
|
@ -81,7 +81,7 @@ namespace osu.Game.Tests.Visual
|
|||||||
|
|
||||||
if (autoplayMod != null)
|
if (autoplayMod != null)
|
||||||
{
|
{
|
||||||
DrawableRuleset?.SetReplayScore(autoplayMod.CreateReplayScore(GameplayState.Beatmap, Mods.Value));
|
DrawableRuleset?.SetReplayScore(autoplayMod.CreateScoreFromReplayData(GameplayState.Beatmap, Mods.Value));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ namespace osu.Game.Tests.Visual
|
|||||||
/// Instantiate a replay player that renders an autoplay mod.
|
/// Instantiate a replay player that renders an autoplay mod.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TestReplayPlayer(bool allowPause = true, bool showResults = true, bool pauseOnFocusLost = false)
|
public TestReplayPlayer(bool allowPause = true, bool showResults = true, bool pauseOnFocusLost = false)
|
||||||
: base((beatmap, mods) => mods.OfType<ModAutoplay>().First().CreateReplayScore(beatmap, mods), new PlayerConfiguration
|
: base((beatmap, mods) => mods.OfType<ModAutoplay>().First().CreateScoreFromReplayData(beatmap, mods), new PlayerConfiguration
|
||||||
{
|
{
|
||||||
AllowPause = allowPause,
|
AllowPause = allowPause,
|
||||||
ShowResults = showResults
|
ShowResults = showResults
|
||||||
|
@ -21,7 +21,7 @@ namespace osu.Game.Users.Drawables
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool OpenOnClick
|
public bool OpenOnClick
|
||||||
{
|
{
|
||||||
set => clickableArea.Enabled.Value = value;
|
set => clickableArea.Enabled.Value = clickableArea.Action != null && value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -52,8 +52,10 @@ namespace osu.Game.Users.Drawables
|
|||||||
Add(clickableArea = new ClickableArea
|
Add(clickableArea = new ClickableArea
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Action = openProfile
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (user?.Id != APIUser.SYSTEM_USER_ID)
|
||||||
|
clickableArea.Action = openProfile;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
|
Loading…
Reference in New Issue
Block a user