mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 15:07:44 +08:00
Merge branch 'master' into spectator-reliability
This commit is contained in:
commit
e947c97e10
@ -18,7 +18,6 @@
|
||||
<ItemGroup Label="Code Analysis">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3" PrivateAssets="All" />
|
||||
<AdditionalFiles Include="$(MSBuildThisFileDirectory)CodeAnalysis\BannedSymbols.txt" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="6.0.0" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Code Analysis">
|
||||
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)CodeAnalysis\osu.ruleset</CodeAnalysisRuleSet>
|
||||
|
@ -52,7 +52,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.211.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.217.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.223.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Transitive Dependencies">
|
||||
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
||||
|
@ -77,10 +77,9 @@ namespace osu.Desktop.LegacyIpc
|
||||
case LegacyIpcDifficultyCalculationRequest req:
|
||||
try
|
||||
{
|
||||
var ruleset = getLegacyRulesetFromID(req.RulesetId);
|
||||
|
||||
WorkingBeatmap beatmap = new FlatFileWorkingBeatmap(req.BeatmapFile);
|
||||
var ruleset = beatmap.BeatmapInfo.Ruleset.CreateInstance();
|
||||
Mod[] mods = ruleset.ConvertFromLegacyMods((LegacyMods)req.Mods).ToArray();
|
||||
WorkingBeatmap beatmap = new FlatFileWorkingBeatmap(req.BeatmapFile, _ => ruleset);
|
||||
|
||||
return new LegacyIpcDifficultyCalculationResponse
|
||||
{
|
||||
|
@ -8,12 +8,15 @@ using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.Judgements;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Skinning.Default;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
@ -23,13 +26,37 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
protected override bool AllowFail => true;
|
||||
|
||||
[Test]
|
||||
public void TestSpinnerAutoCompleted() => CreateModTest(new ModTestData
|
||||
public void TestSpinnerAutoCompleted()
|
||||
{
|
||||
DrawableSpinner spinner = null;
|
||||
JudgementResult lastResult = null;
|
||||
|
||||
CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new OsuModSpunOut(),
|
||||
Autoplay = false,
|
||||
Beatmap = singleSpinnerBeatmap,
|
||||
PassCondition = () => Player.ChildrenOfType<DrawableSpinner>().SingleOrDefault()?.Progress >= 1
|
||||
PassCondition = () =>
|
||||
{
|
||||
// Bind to the first spinner's results for further tracking.
|
||||
if (spinner == null)
|
||||
{
|
||||
// We only care about the first spinner we encounter for this test.
|
||||
var nextSpinner = Player.ChildrenOfType<DrawableSpinner>().SingleOrDefault();
|
||||
|
||||
if (nextSpinner == null)
|
||||
return false;
|
||||
|
||||
lastResult = null;
|
||||
|
||||
spinner = nextSpinner;
|
||||
spinner.OnNewResult += (o, result) => lastResult = result;
|
||||
}
|
||||
|
||||
return lastResult?.Type == HitResult.Great;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[TestCase(null)]
|
||||
[TestCase(typeof(OsuModDoubleTime))]
|
||||
@ -48,7 +75,57 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
PassCondition = () =>
|
||||
{
|
||||
var counter = Player.ChildrenOfType<SpinnerSpmCalculator>().SingleOrDefault();
|
||||
return counter != null && Precision.AlmostEquals(counter.Result.Value, 286, 1);
|
||||
var spinner = Player.ChildrenOfType<DrawableSpinner>().FirstOrDefault();
|
||||
|
||||
if (counter == null || spinner == null)
|
||||
return false;
|
||||
|
||||
// ignore cases where the spinner hasn't started as these lead to false-positives
|
||||
if (Precision.AlmostEquals(counter.Result.Value, 0, 1))
|
||||
return false;
|
||||
|
||||
float rotationSpeed = (float)(1.01 * spinner.HitObject.SpinsRequired / spinner.HitObject.Duration);
|
||||
|
||||
return Precision.AlmostEquals(counter.Result.Value, rotationSpeed * 1000 * 60, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSpinnerGetsNoBonusScore()
|
||||
{
|
||||
DrawableSpinner spinner = null;
|
||||
List<JudgementResult> results = new List<JudgementResult>();
|
||||
|
||||
CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new OsuModSpunOut(),
|
||||
Autoplay = false,
|
||||
Beatmap = singleSpinnerBeatmap,
|
||||
PassCondition = () =>
|
||||
{
|
||||
// Bind to the first spinner's results for further tracking.
|
||||
if (spinner == null)
|
||||
{
|
||||
// We only care about the first spinner we encounter for this test.
|
||||
var nextSpinner = Player.ChildrenOfType<DrawableSpinner>().SingleOrDefault();
|
||||
|
||||
if (nextSpinner == null)
|
||||
return false;
|
||||
|
||||
spinner = nextSpinner;
|
||||
spinner.OnNewResult += (o, result) => results.Add(result);
|
||||
|
||||
results.Clear();
|
||||
}
|
||||
|
||||
// we should only be checking the bonus/progress after the spinner has fully completed.
|
||||
if (results.OfType<OsuSpinnerJudgementResult>().All(r => r.TimeCompleted == null))
|
||||
return false;
|
||||
|
||||
return
|
||||
results.Any(r => r.Type == HitResult.SmallBonus)
|
||||
&& results.All(r => r.Type != HitResult.LargeBonus);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -45,7 +45,11 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
// for that reason using ElapsedFrameTime directly leads to fewer SPM with Half Time and more SPM with Double Time.
|
||||
// for spinners we want the real (wall clock) elapsed time; to achieve that, unapply the clock rate locally here.
|
||||
double rateIndependentElapsedTime = spinner.Clock.ElapsedFrameTime / spinner.Clock.Rate;
|
||||
spinner.RotationTracker.AddRotation(MathUtils.RadiansToDegrees((float)rateIndependentElapsedTime * 0.03f));
|
||||
|
||||
// multiply the SPM by 1.01 to ensure that the spinner is completed. if the calculation is left exact,
|
||||
// some spinners may not complete due to very minor decimal loss during calculation
|
||||
float rotationSpeed = (float)(1.01 * spinner.HitObject.SpinsRequired / spinner.HitObject.Duration);
|
||||
spinner.RotationTracker.AddRotation(MathUtils.RadiansToDegrees((float)rateIndependentElapsedTime * rotationSpeed * MathF.PI * 2.0f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -409,26 +409,26 @@ namespace osu.Game.Tests.Chat
|
||||
|
||||
Assert.AreEqual(result.Content, result.DisplayContent);
|
||||
Assert.AreEqual(2, result.Links.Count);
|
||||
Assert.AreEqual("osu://chan/#english", result.Links[0].Url);
|
||||
Assert.AreEqual("osu://chan/#japanese", result.Links[1].Url);
|
||||
Assert.AreEqual($"{OsuGameBase.OSU_PROTOCOL}chan/#english", result.Links[0].Url);
|
||||
Assert.AreEqual($"{OsuGameBase.OSU_PROTOCOL}chan/#japanese", result.Links[1].Url);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestOsuProtocol()
|
||||
{
|
||||
Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a custom protocol osu://chan/#english." });
|
||||
Message result = MessageFormatter.FormatMessage(new Message { Content = $"This is a custom protocol {OsuGameBase.OSU_PROTOCOL}chan/#english." });
|
||||
|
||||
Assert.AreEqual(result.Content, result.DisplayContent);
|
||||
Assert.AreEqual(1, result.Links.Count);
|
||||
Assert.AreEqual("osu://chan/#english", result.Links[0].Url);
|
||||
Assert.AreEqual($"{OsuGameBase.OSU_PROTOCOL}chan/#english", result.Links[0].Url);
|
||||
Assert.AreEqual(26, result.Links[0].Index);
|
||||
Assert.AreEqual(19, result.Links[0].Length);
|
||||
|
||||
result = MessageFormatter.FormatMessage(new Message { Content = "This is a [custom protocol](osu://chan/#english)." });
|
||||
result = MessageFormatter.FormatMessage(new Message { Content = $"This is a [custom protocol]({OsuGameBase.OSU_PROTOCOL}chan/#english)." });
|
||||
|
||||
Assert.AreEqual("This is a custom protocol.", result.DisplayContent);
|
||||
Assert.AreEqual(1, result.Links.Count);
|
||||
Assert.AreEqual("osu://chan/#english", result.Links[0].Url);
|
||||
Assert.AreEqual($"{OsuGameBase.OSU_PROTOCOL}chan/#english", result.Links[0].Url);
|
||||
Assert.AreEqual("#english", result.Links[0].Argument);
|
||||
Assert.AreEqual(10, result.Links[0].Index);
|
||||
Assert.AreEqual(15, result.Links[0].Length);
|
||||
|
@ -41,7 +41,7 @@ namespace osu.Game.Tests.Database
|
||||
RunTestWithRealmAsync(async (realm, storage) =>
|
||||
{
|
||||
using (var importer = new BeatmapModelManager(realm, storage))
|
||||
using (new RulesetStore(realm, storage))
|
||||
using (new RealmRulesetStore(realm, storage))
|
||||
{
|
||||
Live<BeatmapSetInfo>? beatmapSet;
|
||||
|
||||
@ -85,7 +85,7 @@ namespace osu.Game.Tests.Database
|
||||
RunTestWithRealmAsync(async (realm, storage) =>
|
||||
{
|
||||
using (var importer = new BeatmapModelManager(realm, storage))
|
||||
using (new RulesetStore(realm, storage))
|
||||
using (new RealmRulesetStore(realm, storage))
|
||||
{
|
||||
Live<BeatmapSetInfo>? beatmapSet;
|
||||
|
||||
@ -142,7 +142,7 @@ namespace osu.Game.Tests.Database
|
||||
RunTestWithRealmAsync(async (realm, storage) =>
|
||||
{
|
||||
using (var importer = new BeatmapModelManager(realm, storage))
|
||||
using (new RulesetStore(realm, storage))
|
||||
using (new RealmRulesetStore(realm, storage))
|
||||
{
|
||||
Live<BeatmapSetInfo>? imported;
|
||||
|
||||
@ -171,7 +171,7 @@ namespace osu.Game.Tests.Database
|
||||
RunTestWithRealmAsync(async (realm, storage) =>
|
||||
{
|
||||
using var importer = new BeatmapModelManager(realm, storage);
|
||||
using var store = new RulesetStore(realm, storage);
|
||||
using var store = new RealmRulesetStore(realm, storage);
|
||||
|
||||
await LoadOszIntoStore(importer, realm.Realm);
|
||||
});
|
||||
@ -183,7 +183,7 @@ namespace osu.Game.Tests.Database
|
||||
RunTestWithRealmAsync(async (realm, storage) =>
|
||||
{
|
||||
using var importer = new BeatmapModelManager(realm, storage);
|
||||
using var store = new RulesetStore(realm, storage);
|
||||
using var store = new RealmRulesetStore(realm, storage);
|
||||
|
||||
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
||||
|
||||
@ -201,7 +201,7 @@ namespace osu.Game.Tests.Database
|
||||
RunTestWithRealmAsync(async (realm, storage) =>
|
||||
{
|
||||
using var importer = new BeatmapModelManager(realm, storage);
|
||||
using var store = new RulesetStore(realm, storage);
|
||||
using var store = new RealmRulesetStore(realm, storage);
|
||||
|
||||
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
||||
|
||||
@ -215,7 +215,7 @@ namespace osu.Game.Tests.Database
|
||||
RunTestWithRealmAsync(async (realm, storage) =>
|
||||
{
|
||||
using var importer = new BeatmapModelManager(realm, storage);
|
||||
using var store = new RulesetStore(realm, storage);
|
||||
using var store = new RealmRulesetStore(realm, storage);
|
||||
|
||||
string? tempPath = TestResources.GetTestBeatmapForImport();
|
||||
|
||||
@ -245,7 +245,7 @@ namespace osu.Game.Tests.Database
|
||||
RunTestWithRealmAsync(async (realm, storage) =>
|
||||
{
|
||||
using var importer = new BeatmapModelManager(realm, storage);
|
||||
using var store = new RulesetStore(realm, storage);
|
||||
using var store = new RealmRulesetStore(realm, storage);
|
||||
|
||||
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
||||
var importedSecondTime = await LoadOszIntoStore(importer, realm.Realm);
|
||||
@ -265,7 +265,7 @@ namespace osu.Game.Tests.Database
|
||||
RunTestWithRealmAsync(async (realm, storage) =>
|
||||
{
|
||||
using var importer = new BeatmapModelManager(realm, storage);
|
||||
using var store = new RulesetStore(realm, storage);
|
||||
using var store = new RealmRulesetStore(realm, storage);
|
||||
|
||||
string? temp = TestResources.GetTestBeatmapForImport();
|
||||
|
||||
@ -314,7 +314,7 @@ namespace osu.Game.Tests.Database
|
||||
RunTestWithRealmAsync(async (realm, storage) =>
|
||||
{
|
||||
using var importer = new BeatmapModelManager(realm, storage);
|
||||
using var store = new RulesetStore(realm, storage);
|
||||
using var store = new RealmRulesetStore(realm, storage);
|
||||
|
||||
string? temp = TestResources.GetTestBeatmapForImport();
|
||||
|
||||
@ -366,7 +366,7 @@ namespace osu.Game.Tests.Database
|
||||
RunTestWithRealmAsync(async (realm, storage) =>
|
||||
{
|
||||
using var importer = new BeatmapModelManager(realm, storage);
|
||||
using var store = new RulesetStore(realm, storage);
|
||||
using var store = new RealmRulesetStore(realm, storage);
|
||||
|
||||
string? temp = TestResources.GetTestBeatmapForImport();
|
||||
|
||||
@ -414,7 +414,7 @@ namespace osu.Game.Tests.Database
|
||||
RunTestWithRealmAsync(async (realm, storage) =>
|
||||
{
|
||||
using var importer = new BeatmapModelManager(realm, storage);
|
||||
using var store = new RulesetStore(realm, storage);
|
||||
using var store = new RealmRulesetStore(realm, storage);
|
||||
|
||||
string? temp = TestResources.GetTestBeatmapForImport();
|
||||
|
||||
@ -463,7 +463,7 @@ namespace osu.Game.Tests.Database
|
||||
RunTestWithRealmAsync(async (realm, storage) =>
|
||||
{
|
||||
using var importer = new BeatmapModelManager(realm, storage);
|
||||
using var store = new RulesetStore(realm, storage);
|
||||
using var store = new RealmRulesetStore(realm, storage);
|
||||
|
||||
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
||||
|
||||
@ -496,7 +496,7 @@ namespace osu.Game.Tests.Database
|
||||
RunTestWithRealmAsync(async (realm, storage) =>
|
||||
{
|
||||
using var importer = new BeatmapModelManager(realm, storage);
|
||||
using var store = new RulesetStore(realm, storage);
|
||||
using var store = new RealmRulesetStore(realm, storage);
|
||||
|
||||
var progressNotification = new ImportProgressNotification();
|
||||
|
||||
@ -532,7 +532,7 @@ namespace osu.Game.Tests.Database
|
||||
};
|
||||
|
||||
using var importer = new BeatmapModelManager(realm, storage);
|
||||
using var store = new RulesetStore(realm, storage);
|
||||
using var store = new RealmRulesetStore(realm, storage);
|
||||
|
||||
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
||||
|
||||
@ -582,7 +582,7 @@ namespace osu.Game.Tests.Database
|
||||
RunTestWithRealmAsync(async (realm, storage) =>
|
||||
{
|
||||
using var importer = new BeatmapModelManager(realm, storage);
|
||||
using var store = new RulesetStore(realm, storage);
|
||||
using var store = new RealmRulesetStore(realm, storage);
|
||||
|
||||
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
||||
|
||||
@ -606,7 +606,7 @@ namespace osu.Game.Tests.Database
|
||||
RunTestWithRealmAsync(async (realmFactory, storage) =>
|
||||
{
|
||||
using var importer = new BeatmapModelManager(realmFactory, storage);
|
||||
using var store = new RulesetStore(realmFactory, storage);
|
||||
using var store = new RealmRulesetStore(realmFactory, storage);
|
||||
|
||||
var imported = await LoadOszIntoStore(importer, realmFactory.Realm);
|
||||
|
||||
@ -638,7 +638,7 @@ namespace osu.Game.Tests.Database
|
||||
RunTestWithRealmAsync(async (realm, storage) =>
|
||||
{
|
||||
using var importer = new NonOptimisedBeatmapImporter(realm, storage);
|
||||
using var store = new RulesetStore(realm, storage);
|
||||
using var store = new RealmRulesetStore(realm, storage);
|
||||
|
||||
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
||||
|
||||
@ -662,7 +662,7 @@ namespace osu.Game.Tests.Database
|
||||
RunTestWithRealmAsync(async (realm, storage) =>
|
||||
{
|
||||
using var importer = new BeatmapModelManager(realm, storage);
|
||||
using var store = new RulesetStore(realm, storage);
|
||||
using var store = new RealmRulesetStore(realm, storage);
|
||||
|
||||
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
||||
|
||||
@ -688,7 +688,7 @@ namespace osu.Game.Tests.Database
|
||||
RunTestWithRealm((realm, storage) =>
|
||||
{
|
||||
using var importer = new BeatmapModelManager(realm, storage);
|
||||
using var store = new RulesetStore(realm, storage);
|
||||
using var store = new RealmRulesetStore(realm, storage);
|
||||
|
||||
var metadata = new BeatmapMetadata
|
||||
{
|
||||
@ -734,7 +734,7 @@ namespace osu.Game.Tests.Database
|
||||
RunTestWithRealmAsync(async (realm, storage) =>
|
||||
{
|
||||
using var importer = new BeatmapModelManager(realm, storage);
|
||||
using var store = new RulesetStore(realm, storage);
|
||||
using var store = new RealmRulesetStore(realm, storage);
|
||||
|
||||
string? temp = TestResources.GetTestBeatmapForImport();
|
||||
using (File.OpenRead(temp))
|
||||
@ -751,7 +751,7 @@ namespace osu.Game.Tests.Database
|
||||
RunTestWithRealmAsync(async (realm, storage) =>
|
||||
{
|
||||
using var importer = new BeatmapModelManager(realm, storage);
|
||||
using var store = new RulesetStore(realm, storage);
|
||||
using var store = new RealmRulesetStore(realm, storage);
|
||||
|
||||
string? temp = TestResources.GetTestBeatmapForImport();
|
||||
|
||||
@ -787,7 +787,7 @@ namespace osu.Game.Tests.Database
|
||||
RunTestWithRealmAsync(async (realm, storage) =>
|
||||
{
|
||||
using var importer = new BeatmapModelManager(realm, storage);
|
||||
using var store = new RulesetStore(realm, storage);
|
||||
using var store = new RealmRulesetStore(realm, storage);
|
||||
|
||||
string? temp = TestResources.GetTestBeatmapForImport();
|
||||
|
||||
@ -829,7 +829,7 @@ namespace osu.Game.Tests.Database
|
||||
RunTestWithRealmAsync(async (realm, storage) =>
|
||||
{
|
||||
using var importer = new BeatmapModelManager(realm, storage);
|
||||
using var store = new RulesetStore(realm, storage);
|
||||
using var store = new RealmRulesetStore(realm, storage);
|
||||
|
||||
string? temp = TestResources.GetTestBeatmapForImport();
|
||||
|
||||
@ -880,7 +880,7 @@ namespace osu.Game.Tests.Database
|
||||
RunTestWithRealmAsync(async (realm, storage) =>
|
||||
{
|
||||
using var importer = new BeatmapModelManager(realm, storage);
|
||||
using var store = new RulesetStore(realm, storage);
|
||||
using var store = new RealmRulesetStore(realm, storage);
|
||||
|
||||
string? temp = TestResources.GetTestBeatmapForImport();
|
||||
await importer.Import(temp);
|
||||
|
@ -14,7 +14,7 @@ namespace osu.Game.Tests.Database
|
||||
{
|
||||
RunTestWithRealm((realm, storage) =>
|
||||
{
|
||||
var rulesets = new RulesetStore(realm, storage);
|
||||
var rulesets = new RealmRulesetStore(realm, storage);
|
||||
|
||||
Assert.AreEqual(4, rulesets.AvailableRulesets.Count());
|
||||
Assert.AreEqual(4, realm.Realm.All<RulesetInfo>().Count());
|
||||
@ -26,8 +26,8 @@ namespace osu.Game.Tests.Database
|
||||
{
|
||||
RunTestWithRealm((realm, storage) =>
|
||||
{
|
||||
var rulesets = new RulesetStore(realm, storage);
|
||||
var rulesets2 = new RulesetStore(realm, storage);
|
||||
var rulesets = new RealmRulesetStore(realm, storage);
|
||||
var rulesets2 = new RealmRulesetStore(realm, storage);
|
||||
|
||||
Assert.AreEqual(4, rulesets.AvailableRulesets.Count());
|
||||
Assert.AreEqual(4, rulesets2.AvailableRulesets.Count());
|
||||
@ -42,7 +42,7 @@ namespace osu.Game.Tests.Database
|
||||
{
|
||||
RunTestWithRealm((realm, storage) =>
|
||||
{
|
||||
var rulesets = new RulesetStore(realm, storage);
|
||||
var rulesets = new RealmRulesetStore(realm, storage);
|
||||
|
||||
Assert.IsFalse(rulesets.AvailableRulesets.First().IsManaged);
|
||||
Assert.IsFalse(rulesets.GetRuleset(0)?.IsManaged);
|
||||
|
@ -121,6 +121,17 @@ namespace osu.Game.Tests.Online
|
||||
Assert.That((deserialised?.Mods[0])?.Settings["speed_change"], Is.EqualTo(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAPIModDetachedFromSource()
|
||||
{
|
||||
var mod = new OsuModDoubleTime { SpeedChange = { Value = 1.01 } };
|
||||
var apiMod = new APIMod(mod);
|
||||
|
||||
mod.SpeedChange.Value = 1.5;
|
||||
|
||||
Assert.That(apiMod.Settings["speed_change"], Is.EqualTo(1.01d));
|
||||
}
|
||||
|
||||
private class TestRuleset : Ruleset
|
||||
{
|
||||
public override IEnumerable<Mod> GetModsFor(ModType type) => new Mod[]
|
||||
|
@ -49,7 +49,7 @@ namespace osu.Game.Tests.Online
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio, GameHost host)
|
||||
{
|
||||
Dependencies.Cache(rulesets = new RulesetStore(Realm));
|
||||
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
||||
Dependencies.CacheAs<BeatmapManager>(beatmaps = new TestBeatmapManager(LocalStorage, Realm, rulesets, API, audio, Resources, host, Beatmap.Default));
|
||||
Dependencies.CacheAs<BeatmapModelDownloader>(beatmapDownloader = new TestBeatmapModelDownloader(beatmaps, API));
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual.Background
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
{
|
||||
Dependencies.Cache(rulesets = new RulesetStore(Realm));
|
||||
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
|
||||
Dependencies.Cache(new OsuConfigManager(LocalStorage));
|
||||
Dependencies.Cache(Realm);
|
||||
|
@ -36,7 +36,7 @@ namespace osu.Game.Tests.Visual.Collections
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host)
|
||||
{
|
||||
Dependencies.Cache(rulesets = new RulesetStore(Realm));
|
||||
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesets, null, Audio, Resources, host, Beatmap.Default));
|
||||
Dependencies.Cache(Realm);
|
||||
|
||||
|
@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
{
|
||||
Dependencies.Cache(rulesets = new RulesetStore(Realm));
|
||||
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
|
||||
Dependencies.Cache(Realm);
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
{
|
||||
Dependencies.Cache(rulesets = new RulesetStore(Realm));
|
||||
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
|
||||
Dependencies.Cache(Realm);
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ using osu.Game.Configuration;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate;
|
||||
using osu.Game.Screens.Play;
|
||||
@ -377,7 +378,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
Beatmap.Value = beatmapManager.GetWorkingBeatmap(importedBeatmap);
|
||||
Ruleset.Value = importedBeatmap.Ruleset;
|
||||
|
||||
LoadScreen(spectatorScreen = new TestMultiSpectatorScreen(playingUsers.ToArray(), gameplayStartTime));
|
||||
LoadScreen(spectatorScreen = new TestMultiSpectatorScreen(SelectedRoom.Value, playingUsers.ToArray(), gameplayStartTime));
|
||||
});
|
||||
|
||||
AddUntilStep("wait for screen load", () => spectatorScreen.LoadState == LoadState.Loaded && (!waitForPlayerLoad || spectatorScreen.AllPlayersLoaded));
|
||||
@ -465,8 +466,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
private readonly double? gameplayStartTime;
|
||||
|
||||
public TestMultiSpectatorScreen(MultiplayerRoomUser[] users, double? gameplayStartTime = null)
|
||||
: base(users)
|
||||
public TestMultiSpectatorScreen(Room room, MultiplayerRoomUser[] users, double? gameplayStartTime = null)
|
||||
: base(room, users)
|
||||
{
|
||||
this.gameplayStartTime = gameplayStartTime;
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
{
|
||||
Dependencies.Cache(rulesets = new RulesetStore(Realm));
|
||||
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, API, audio, Resources, host, Beatmap.Default));
|
||||
Dependencies.Cache(Realm);
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
{
|
||||
Dependencies.Cache(rulesets = new RulesetStore(Realm));
|
||||
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
|
||||
Dependencies.Cache(Realm);
|
||||
|
||||
|
@ -46,7 +46,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
{
|
||||
Dependencies.Cache(rulesets = new RulesetStore(Realm));
|
||||
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
|
||||
Dependencies.Cache(Realm);
|
||||
|
||||
|
@ -33,7 +33,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
{
|
||||
Dependencies.Cache(rulesets = new RulesetStore(Realm));
|
||||
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
|
||||
Dependencies.Cache(Realm);
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
{
|
||||
Dependencies.Cache(rulesets = new RulesetStore(Realm));
|
||||
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, API, audio, Resources, host, Beatmap.Default));
|
||||
Dependencies.Cache(Realm);
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
{
|
||||
Dependencies.Cache(rulesets = new RulesetStore(Realm));
|
||||
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
|
||||
Dependencies.Cache(Realm);
|
||||
|
||||
|
@ -41,7 +41,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
{
|
||||
Dependencies.Cache(rulesets = new RulesetStore(Realm));
|
||||
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
|
||||
Dependencies.Cache(Realm);
|
||||
|
||||
|
@ -33,7 +33,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
{
|
||||
Dependencies.Cache(rulesets = new RulesetStore(Realm));
|
||||
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
|
||||
Dependencies.Cache(Realm);
|
||||
|
||||
|
@ -38,7 +38,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
{
|
||||
Dependencies.Cache(rulesets = new RulesetStore(Realm));
|
||||
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
|
||||
Dependencies.Cache(Realm);
|
||||
}
|
||||
|
@ -0,0 +1,54 @@
|
||||
// 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.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.BeatmapSet;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Navigation
|
||||
{
|
||||
public class TestSceneStartupBeatmapDisplay : OsuGameTestScene
|
||||
{
|
||||
private const int requested_beatmap_id = 75;
|
||||
private const int requested_beatmap_set_id = 1;
|
||||
|
||||
protected override TestOsuGame CreateTestGame() => new TestOsuGame(LocalStorage, API, new[] { $"osu://b/{requested_beatmap_id}" });
|
||||
|
||||
[SetUp]
|
||||
public void Setup() => Schedule(() =>
|
||||
{
|
||||
((DummyAPIAccess)API).HandleRequest = request =>
|
||||
{
|
||||
switch (request)
|
||||
{
|
||||
case GetBeatmapSetRequest gbr:
|
||||
var apiBeatmapSet = CreateAPIBeatmapSet();
|
||||
apiBeatmapSet.OnlineID = requested_beatmap_set_id;
|
||||
apiBeatmapSet.Beatmaps = apiBeatmapSet.Beatmaps.Append(new APIBeatmap
|
||||
{
|
||||
DifficultyName = "Target difficulty",
|
||||
OnlineID = requested_beatmap_id,
|
||||
}).ToArray();
|
||||
|
||||
gbr.TriggerSuccess(apiBeatmapSet);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestBeatmapLink()
|
||||
{
|
||||
AddUntilStep("Beatmap overlay displayed", () => Game.ChildrenOfType<BeatmapSetOverlay>().FirstOrDefault()?.State.Value == Visibility.Visible);
|
||||
AddUntilStep("Beatmap overlay showing content", () => Game.ChildrenOfType<BeatmapPicker>().FirstOrDefault()?.Beatmap.Value.OnlineID == requested_beatmap_id);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
// 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.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Overlays;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Navigation
|
||||
{
|
||||
public class TestSceneStartupBeatmapSetDisplay : OsuGameTestScene
|
||||
{
|
||||
private const int requested_beatmap_set_id = 1;
|
||||
|
||||
protected override TestOsuGame CreateTestGame() => new TestOsuGame(LocalStorage, API, new[] { $"osu://s/{requested_beatmap_set_id}" });
|
||||
|
||||
[SetUp]
|
||||
public void Setup() => Schedule(() =>
|
||||
{
|
||||
((DummyAPIAccess)API).HandleRequest = request =>
|
||||
{
|
||||
switch (request)
|
||||
{
|
||||
case GetBeatmapSetRequest gbr:
|
||||
|
||||
var apiBeatmapSet = CreateAPIBeatmapSet();
|
||||
apiBeatmapSet.OnlineID = requested_beatmap_set_id;
|
||||
apiBeatmapSet.Beatmaps = apiBeatmapSet.Beatmaps.Append(new APIBeatmap
|
||||
{
|
||||
DifficultyName = "Target difficulty",
|
||||
OnlineID = 75,
|
||||
}).ToArray();
|
||||
gbr.TriggerSuccess(apiBeatmapSet);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestBeatmapSetLink()
|
||||
{
|
||||
AddUntilStep("Beatmap overlay displayed", () => Game.ChildrenOfType<BeatmapSetOverlay>().FirstOrDefault()?.State.Value == Visibility.Visible);
|
||||
AddUntilStep("Beatmap overlay showing content", () => Game.ChildrenOfType<BeatmapSetOverlay>().FirstOrDefault()?.Header.BeatmapSet.Value.OnlineID == requested_beatmap_set_id);
|
||||
}
|
||||
}
|
||||
}
|
@ -87,8 +87,8 @@ namespace osu.Game.Tests.Visual.Online
|
||||
addMessageWithChecks("likes to post this [https://dev.ppy.sh/home link].", 1, true, true, expectedActions: LinkAction.External);
|
||||
addMessageWithChecks("Join my multiplayer game osump://12346.", 1, expectedActions: LinkAction.JoinMultiplayerMatch);
|
||||
addMessageWithChecks("Join my [multiplayer game](osump://12346).", 1, expectedActions: LinkAction.JoinMultiplayerMatch);
|
||||
addMessageWithChecks("Join my [#english](osu://chan/#english).", 1, expectedActions: LinkAction.OpenChannel);
|
||||
addMessageWithChecks("Join my osu://chan/#english.", 1, expectedActions: LinkAction.OpenChannel);
|
||||
addMessageWithChecks($"Join my [#english]({OsuGameBase.OSU_PROTOCOL}chan/#english).", 1, expectedActions: LinkAction.OpenChannel);
|
||||
addMessageWithChecks($"Join my {OsuGameBase.OSU_PROTOCOL}chan/#english.", 1, expectedActions: LinkAction.OpenChannel);
|
||||
addMessageWithChecks("Join my #english or #japanese channels.", 2, expectedActions: new[] { LinkAction.OpenChannel, LinkAction.OpenChannel });
|
||||
addMessageWithChecks("Join my #english or #nonexistent #hashtag channels.", 1, expectedActions: LinkAction.OpenChannel);
|
||||
|
||||
|
@ -39,7 +39,7 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
{
|
||||
Dependencies.Cache(rulesets = new RulesetStore(Realm));
|
||||
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, API, audio, Resources, host, Beatmap.Default));
|
||||
Dependencies.Cache(Realm);
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
||||
|
||||
dependencies.Cache(rulesetStore = new RulesetStore(Realm));
|
||||
dependencies.Cache(rulesetStore = new RealmRulesetStore(Realm));
|
||||
dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesetStore, null, dependencies.Get<AudioManager>(), Resources, dependencies.Get<GameHost>(), Beatmap.Default));
|
||||
dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, Realm, Scheduler));
|
||||
Dependencies.Cache(Realm);
|
||||
|
@ -36,7 +36,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host)
|
||||
{
|
||||
Dependencies.Cache(rulesets = new RulesetStore(Realm));
|
||||
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesets, null, Audio, Resources, host, Beatmap.Default));
|
||||
Dependencies.Cache(Realm);
|
||||
|
||||
|
@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
// These DI caches are required to ensure for interactive runs this test scene doesn't nuke all user beatmaps in the local install.
|
||||
// At a point we have isolated interactive test runs enough, this can likely be removed.
|
||||
Dependencies.Cache(rulesets = new RulesetStore(Realm));
|
||||
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(Realm);
|
||||
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, defaultBeatmap = Beatmap.Default));
|
||||
|
||||
|
@ -28,7 +28,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
{
|
||||
Dependencies.Cache(rulesets = new RulesetStore(Realm));
|
||||
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
|
||||
Dependencies.Cache(scoreManager = new ScoreManager(rulesets, () => beatmapManager, LocalStorage, Realm, Scheduler));
|
||||
Dependencies.Cache(Realm);
|
||||
|
@ -87,7 +87,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
||||
|
||||
dependencies.Cache(rulesetStore = new RulesetStore(Realm));
|
||||
dependencies.Cache(rulesetStore = new RealmRulesetStore(Realm));
|
||||
dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesetStore, null, dependencies.Get<AudioManager>(), Resources, dependencies.Get<GameHost>(), Beatmap.Default));
|
||||
dependencies.Cache(scoreManager = new ScoreManager(dependencies.Get<RulesetStore>(), () => beatmapManager, LocalStorage, Realm, Scheduler));
|
||||
Dependencies.Cache(Realm);
|
||||
|
81
osu.Game.Tests/Visual/UserInterface/TestSceneModPanel.cs
Normal file
81
osu.Game.Tests/Visual/UserInterface/TestSceneModPanel.cs
Normal file
@ -0,0 +1,81 @@
|
||||
// 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;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestSceneModPanel : OsuManualInputManagerTestScene
|
||||
{
|
||||
[Cached]
|
||||
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green);
|
||||
|
||||
[Test]
|
||||
public void TestVariousPanels()
|
||||
{
|
||||
AddStep("create content", () => Child = new FillFlowContainer
|
||||
{
|
||||
Width = 300,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Spacing = new Vector2(0, 5),
|
||||
Children = new[]
|
||||
{
|
||||
new ModPanel(new OsuModHalfTime()),
|
||||
new ModPanel(new OsuModFlashlight()),
|
||||
new ModPanel(new OsuModAutoplay()),
|
||||
new ModPanel(new OsuModAlternate()),
|
||||
new ModPanel(new OsuModApproachDifferent())
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestIncompatibilityDisplay()
|
||||
{
|
||||
IncompatibilityDisplayingModPanel panel = null;
|
||||
|
||||
AddStep("create panel with DT", () => Child = panel = new IncompatibilityDisplayingModPanel(new OsuModDoubleTime())
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.None,
|
||||
Width = 300
|
||||
});
|
||||
|
||||
clickPanel();
|
||||
AddAssert("panel active", () => panel.Active.Value);
|
||||
|
||||
clickPanel();
|
||||
AddAssert("panel not active", () => !panel.Active.Value);
|
||||
|
||||
AddStep("set incompatible mod", () => SelectedMods.Value = new[] { new OsuModHalfTime() });
|
||||
|
||||
clickPanel();
|
||||
AddAssert("panel not active", () => !panel.Active.Value);
|
||||
|
||||
AddStep("reset mods", () => SelectedMods.Value = Array.Empty<Mod>());
|
||||
|
||||
clickPanel();
|
||||
AddAssert("panel active", () => panel.Active.Value);
|
||||
|
||||
void clickPanel() => AddStep("click panel", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(panel);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
// 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.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Catch;
|
||||
using osu.Game.Rulesets.Mania;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Taiko;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestSceneModSwitchSmall : OsuTestScene
|
||||
{
|
||||
[Test]
|
||||
public void TestOsu() => createSwitchTestFor(new OsuRuleset());
|
||||
|
||||
[Test]
|
||||
public void TestTaiko() => createSwitchTestFor(new TaikoRuleset());
|
||||
|
||||
[Test]
|
||||
public void TestCatch() => createSwitchTestFor(new CatchRuleset());
|
||||
|
||||
[Test]
|
||||
public void TestMania() => createSwitchTestFor(new ManiaRuleset());
|
||||
|
||||
private void createSwitchTestFor(Ruleset ruleset)
|
||||
{
|
||||
AddStep("no colour scheme", () => Child = createContent(ruleset, null));
|
||||
|
||||
foreach (var scheme in Enum.GetValues(typeof(OverlayColourScheme)).Cast<OverlayColourScheme>())
|
||||
{
|
||||
AddStep($"{scheme} colour scheme", () => Child = createContent(ruleset, scheme));
|
||||
}
|
||||
|
||||
AddToggleStep("toggle active", active => this.ChildrenOfType<ModSwitchTiny>().ForEach(s => s.Active.Value = active));
|
||||
}
|
||||
|
||||
private static Drawable createContent(Ruleset ruleset, OverlayColourScheme? colourScheme)
|
||||
{
|
||||
var switchFlow = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(10),
|
||||
Padding = new MarginPadding(20),
|
||||
ChildrenEnumerable = ruleset.CreateAllMods()
|
||||
.GroupBy(mod => mod.Type)
|
||||
.Select(group => new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Full,
|
||||
Spacing = new Vector2(5),
|
||||
ChildrenEnumerable = group.Select(mod => new ModSwitchSmall(mod))
|
||||
})
|
||||
};
|
||||
|
||||
if (colourScheme != null)
|
||||
{
|
||||
return new DependencyProvidingContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
CachedDependencies = new (Type, object)[]
|
||||
{
|
||||
(typeof(OverlayColourProvider), new OverlayColourProvider(colourScheme.Value))
|
||||
},
|
||||
Child = switchFlow
|
||||
};
|
||||
}
|
||||
|
||||
return switchFlow;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
// 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.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Catch;
|
||||
using osu.Game.Rulesets.Mania;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Taiko;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestSceneModSwitchTiny : OsuTestScene
|
||||
{
|
||||
[Test]
|
||||
public void TestOsu() => createSwitchTestFor(new OsuRuleset());
|
||||
|
||||
[Test]
|
||||
public void TestTaiko() => createSwitchTestFor(new TaikoRuleset());
|
||||
|
||||
[Test]
|
||||
public void TestCatch() => createSwitchTestFor(new CatchRuleset());
|
||||
|
||||
[Test]
|
||||
public void TestMania() => createSwitchTestFor(new ManiaRuleset());
|
||||
|
||||
private void createSwitchTestFor(Ruleset ruleset)
|
||||
{
|
||||
AddStep("no colour scheme", () => Child = createContent(ruleset, null));
|
||||
|
||||
foreach (var scheme in Enum.GetValues(typeof(OverlayColourScheme)).Cast<OverlayColourScheme>())
|
||||
{
|
||||
AddStep($"{scheme} colour scheme", () => Child = createContent(ruleset, scheme));
|
||||
}
|
||||
|
||||
AddToggleStep("toggle active", active => this.ChildrenOfType<ModSwitchTiny>().ForEach(s => s.Active.Value = active));
|
||||
}
|
||||
|
||||
private static Drawable createContent(Ruleset ruleset, OverlayColourScheme? colourScheme)
|
||||
{
|
||||
var switchFlow = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(10),
|
||||
Padding = new MarginPadding(20),
|
||||
ChildrenEnumerable = ruleset.CreateAllMods()
|
||||
.GroupBy(mod => mod.Type)
|
||||
.Select(group => new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Full,
|
||||
Spacing = new Vector2(5),
|
||||
ChildrenEnumerable = group.Select(mod => new ModSwitchTiny(mod))
|
||||
})
|
||||
};
|
||||
|
||||
if (colourScheme != null)
|
||||
{
|
||||
return new DependencyProvidingContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
CachedDependencies = new (Type, object)[]
|
||||
{
|
||||
(typeof(OverlayColourProvider), new OverlayColourProvider(colourScheme.Value))
|
||||
},
|
||||
Child = switchFlow
|
||||
};
|
||||
}
|
||||
|
||||
return switchFlow;
|
||||
}
|
||||
}
|
||||
}
|
@ -152,18 +152,6 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
#region Compatibility properties
|
||||
|
||||
[Ignored]
|
||||
public int RulesetID
|
||||
{
|
||||
set
|
||||
{
|
||||
if (!string.IsNullOrEmpty(Ruleset.InstantiationInfo))
|
||||
throw new InvalidOperationException($"Cannot set a {nameof(RulesetID)} when {nameof(Ruleset)} is already set to an actual ruleset.");
|
||||
|
||||
Ruleset.OnlineID = value;
|
||||
}
|
||||
}
|
||||
|
||||
[Ignored]
|
||||
[Obsolete("Use BeatmapInfo.Difficulty instead.")] // can be removed 20220719
|
||||
public BeatmapDifficulty BaseDifficulty
|
||||
|
@ -7,7 +7,6 @@ using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Game.Beatmaps.Formats;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Beatmaps
|
||||
@ -20,18 +19,16 @@ namespace osu.Game.Beatmaps
|
||||
{
|
||||
private readonly Beatmap beatmap;
|
||||
|
||||
public FlatFileWorkingBeatmap(string file, Func<int, Ruleset> rulesetProvider, int? beatmapId = null)
|
||||
: this(readFromFile(file), rulesetProvider, beatmapId)
|
||||
public FlatFileWorkingBeatmap(string file, int? beatmapId = null)
|
||||
: this(readFromFile(file), beatmapId)
|
||||
{
|
||||
}
|
||||
|
||||
private FlatFileWorkingBeatmap(Beatmap beatmap, Func<int, Ruleset> rulesetProvider, int? beatmapId = null)
|
||||
private FlatFileWorkingBeatmap(Beatmap beatmap, int? beatmapId = null)
|
||||
: base(beatmap.BeatmapInfo, null)
|
||||
{
|
||||
this.beatmap = beatmap;
|
||||
|
||||
beatmap.BeatmapInfo.Ruleset = rulesetProvider(beatmap.BeatmapInfo.Ruleset.OnlineID).RulesetInfo;
|
||||
|
||||
if (beatmapId.HasValue)
|
||||
beatmap.BeatmapInfo.OnlineID = beatmapId.Value;
|
||||
}
|
||||
|
@ -5,7 +5,9 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.Rulesets;
|
||||
|
||||
namespace osu.Game.Beatmaps.Formats
|
||||
{
|
||||
@ -37,6 +39,15 @@ namespace osu.Game.Beatmaps.Formats
|
||||
LegacyStoryboardDecoder.Register();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register dependencies for use with static decoder classes.
|
||||
/// </summary>
|
||||
/// <param name="rulesets">A store containing all available rulesets (used by <see cref="LegacyBeatmapDecoder"/>).</param>
|
||||
public static void RegisterDependencies([NotNull] RulesetStore rulesets)
|
||||
{
|
||||
LegacyBeatmapDecoder.RulesetStore = rulesets ?? throw new ArgumentNullException(nameof(rulesets));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a <see cref="Decoder"/> to parse a <see cref="Beatmap"/>.
|
||||
/// </summary>
|
||||
|
@ -7,16 +7,20 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Beatmaps.Legacy;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Objects.Legacy;
|
||||
|
||||
namespace osu.Game.Beatmaps.Formats
|
||||
{
|
||||
public class LegacyBeatmapDecoder : LegacyDecoder<Beatmap>
|
||||
{
|
||||
internal static RulesetStore RulesetStore;
|
||||
|
||||
private Beatmap beatmap;
|
||||
|
||||
private ConvertHitObjectParser parser;
|
||||
@ -40,6 +44,12 @@ namespace osu.Game.Beatmaps.Formats
|
||||
public LegacyBeatmapDecoder(int version = LATEST_VERSION)
|
||||
: base(version)
|
||||
{
|
||||
if (RulesetStore == null)
|
||||
{
|
||||
Logger.Log($"A {nameof(RulesetStore)} was not provided via {nameof(Decoder)}.{nameof(RegisterDependencies)}; falling back to default {nameof(AssemblyRulesetStore)}.");
|
||||
RulesetStore = new AssemblyRulesetStore();
|
||||
}
|
||||
|
||||
// BeatmapVersion 4 and lower had an incorrect offset (stable has this set as 24ms off)
|
||||
offset = FormatVersion < 5 ? 24 : 0;
|
||||
}
|
||||
@ -158,7 +168,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
case @"Mode":
|
||||
int rulesetID = Parsing.ParseInt(pair.Value);
|
||||
|
||||
beatmap.BeatmapInfo.RulesetID = rulesetID;
|
||||
beatmap.BeatmapInfo.Ruleset = RulesetStore.GetRuleset(rulesetID) ?? throw new ArgumentException("Ruleset is not available locally.");
|
||||
|
||||
switch (rulesetID)
|
||||
{
|
||||
|
@ -5,6 +5,7 @@ using System;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Utils;
|
||||
@ -157,6 +158,36 @@ namespace osu.Game.Graphics
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the main accent colour for a <see cref="ModType"/>.
|
||||
/// </summary>
|
||||
public Color4 ForModType(ModType modType)
|
||||
{
|
||||
switch (modType)
|
||||
{
|
||||
case ModType.Automation:
|
||||
return Blue1;
|
||||
|
||||
case ModType.DifficultyIncrease:
|
||||
return Red1;
|
||||
|
||||
case ModType.DifficultyReduction:
|
||||
return Lime1;
|
||||
|
||||
case ModType.Conversion:
|
||||
return Purple1;
|
||||
|
||||
case ModType.Fun:
|
||||
return Pink1;
|
||||
|
||||
case ModType.System:
|
||||
return Gray7;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(modType), modType, "Unknown mod type");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a foreground text colour that is supposed to contrast well with
|
||||
/// the supplied <paramref name="backgroundColour"/>.
|
||||
|
@ -36,6 +36,8 @@ namespace osu.Game.Online.API
|
||||
|
||||
public string WebsiteRootUrl { get; }
|
||||
|
||||
public int APIVersion => 20220217; // We may want to pull this from the game version eventually.
|
||||
|
||||
public Exception LastLoginError { get; private set; }
|
||||
|
||||
public string ProvidedUsername { get; private set; }
|
||||
|
@ -42,7 +42,7 @@ namespace osu.Game.Online.API
|
||||
var bindable = (IBindable)property.GetValue(mod);
|
||||
|
||||
if (!bindable.IsDefault)
|
||||
Settings.Add(property.Name.Underscore(), bindable);
|
||||
Settings.Add(property.Name.Underscore(), ModUtils.GetSettingUnderlyingValue(bindable));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using JetBrains.Annotations;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Framework.IO.Network;
|
||||
@ -112,6 +113,9 @@ namespace osu.Game.Online.API
|
||||
WebRequest = CreateWebRequest();
|
||||
WebRequest.Failed += Fail;
|
||||
WebRequest.AllowRetryOnTimeout = false;
|
||||
|
||||
WebRequest.AddHeader("x-api-version", API.APIVersion.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
if (!string.IsNullOrEmpty(API.AccessToken))
|
||||
WebRequest.AddHeader("Authorization", $"Bearer {API.AccessToken}");
|
||||
|
||||
|
@ -33,6 +33,8 @@ namespace osu.Game.Online.API
|
||||
|
||||
public string WebsiteRootUrl => "http://localhost";
|
||||
|
||||
public int APIVersion => int.Parse(DateTime.Now.ToString("yyyyMMdd"));
|
||||
|
||||
public Exception LastLoginError { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
|
@ -57,6 +57,11 @@ namespace osu.Game.Online.API
|
||||
/// </summary>
|
||||
string WebsiteRootUrl { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The version of the API.
|
||||
/// </summary>
|
||||
int APIVersion { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The last login error that occurred, if any.
|
||||
/// </summary>
|
||||
|
@ -12,6 +12,7 @@ using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Online.API.Requests.Responses
|
||||
{
|
||||
[JsonObject(MemberSerialization.OptIn)]
|
||||
public class APIUser : IEquatable<APIUser>, IUser
|
||||
{
|
||||
[JsonProperty(@"id")]
|
||||
|
@ -618,7 +618,7 @@ namespace osu.Game.Online.Chat
|
||||
var req = new MarkChannelAsReadRequest(channel, message);
|
||||
|
||||
req.Success += () => channel.LastReadId = message.Id;
|
||||
req.Failure += e => Logger.Error(e, $"Failed to mark channel {channel} up to '{message}' as read");
|
||||
req.Failure += e => Logger.Log($"Failed to mark channel {channel} up to '{message}' as read ({e.Message})", LoggingTarget.Network);
|
||||
|
||||
api.Queue(req);
|
||||
}
|
||||
|
@ -268,10 +268,10 @@ namespace osu.Game.Online.Chat
|
||||
handleAdvanced(advanced_link_regex, result, startIndex);
|
||||
|
||||
// handle editor times
|
||||
handleMatches(time_regex, "{0}", "osu://edit/{0}", result, startIndex, LinkAction.OpenEditorTimestamp);
|
||||
handleMatches(time_regex, "{0}", $@"{OsuGameBase.OSU_PROTOCOL}edit/{{0}}", result, startIndex, LinkAction.OpenEditorTimestamp);
|
||||
|
||||
// handle channels
|
||||
handleMatches(channel_regex, "{0}", "osu://chan/{0}", result, startIndex, LinkAction.OpenChannel);
|
||||
handleMatches(channel_regex, "{0}", $@"{OsuGameBase.OSU_PROTOCOL}chan/{{0}}", result, startIndex, LinkAction.OpenChannel);
|
||||
|
||||
string empty = "";
|
||||
while (space-- > 0)
|
||||
|
@ -62,6 +62,10 @@ namespace osu.Game.Online.Rooms
|
||||
[JsonProperty("beatmap_id")]
|
||||
private int onlineBeatmapId => Beatmap.OnlineID;
|
||||
|
||||
/// <summary>
|
||||
/// A beatmap representing this playlist item.
|
||||
/// In many cases, this will *not* contain any usable information apart from OnlineID.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public IBeatmapInfo Beatmap { get; set; } = null!;
|
||||
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
@ -15,6 +14,7 @@ using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Online.Rooms
|
||||
{
|
||||
[JsonObject(MemberSerialization.OptIn)]
|
||||
public class Room : IDeepCloneable<Room>
|
||||
{
|
||||
[Cached]
|
||||
@ -37,8 +37,19 @@ namespace osu.Game.Online.Rooms
|
||||
[JsonProperty("channel_id")]
|
||||
public readonly Bindable<int> ChannelId = new Bindable<int>();
|
||||
|
||||
[JsonProperty("current_playlist_item")]
|
||||
[Cached]
|
||||
public readonly Bindable<PlaylistItem> CurrentPlaylistItem = new Bindable<PlaylistItem>();
|
||||
|
||||
[JsonProperty("playlist_item_stats")]
|
||||
[Cached]
|
||||
public readonly Bindable<RoomPlaylistItemStats> PlaylistItemStats = new Bindable<RoomPlaylistItemStats>();
|
||||
|
||||
[JsonProperty("difficulty_range")]
|
||||
[Cached]
|
||||
public readonly Bindable<RoomDifficultyRange> DifficultyRange = new Bindable<RoomDifficultyRange>();
|
||||
|
||||
[Cached]
|
||||
[JsonIgnore]
|
||||
public readonly Bindable<RoomCategory> Category = new Bindable<RoomCategory>();
|
||||
|
||||
// Todo: osu-framework bug (https://github.com/ppy/osu-framework/issues/4106)
|
||||
@ -51,19 +62,15 @@ namespace osu.Game.Online.Rooms
|
||||
}
|
||||
|
||||
[Cached]
|
||||
[JsonIgnore]
|
||||
public readonly Bindable<int?> MaxAttempts = new Bindable<int?>();
|
||||
|
||||
[Cached]
|
||||
[JsonIgnore]
|
||||
public readonly Bindable<RoomStatus> Status = new Bindable<RoomStatus>(new RoomStatusOpen());
|
||||
|
||||
[Cached]
|
||||
[JsonIgnore]
|
||||
public readonly Bindable<RoomAvailability> Availability = new Bindable<RoomAvailability>();
|
||||
|
||||
[Cached]
|
||||
[JsonIgnore]
|
||||
public readonly Bindable<MatchType> Type = new Bindable<MatchType>();
|
||||
|
||||
// Todo: osu-framework bug (https://github.com/ppy/osu-framework/issues/4106)
|
||||
@ -76,7 +83,6 @@ namespace osu.Game.Online.Rooms
|
||||
}
|
||||
|
||||
[Cached]
|
||||
[JsonIgnore]
|
||||
public readonly Bindable<QueueMode> QueueMode = new Bindable<QueueMode>();
|
||||
|
||||
[JsonConverter(typeof(SnakeCaseStringEnumConverter))]
|
||||
@ -88,7 +94,6 @@ namespace osu.Game.Online.Rooms
|
||||
}
|
||||
|
||||
[Cached]
|
||||
[JsonIgnore]
|
||||
public readonly Bindable<int?> MaxParticipants = new Bindable<int?>();
|
||||
|
||||
[Cached]
|
||||
@ -113,7 +118,6 @@ namespace osu.Game.Online.Rooms
|
||||
public readonly Bindable<string> Password = new Bindable<string>();
|
||||
|
||||
[Cached]
|
||||
[JsonIgnore]
|
||||
public readonly Bindable<TimeSpan?> Duration = new Bindable<TimeSpan?>();
|
||||
|
||||
[JsonProperty("duration")]
|
||||
@ -158,6 +162,8 @@ namespace osu.Game.Online.Rooms
|
||||
var copy = new Room();
|
||||
|
||||
copy.CopyFrom(this);
|
||||
|
||||
// ID must be unset as we use this as a marker for whether this is a client-side (not-yet-created) room or not.
|
||||
copy.RoomID.Value = null;
|
||||
|
||||
return copy;
|
||||
@ -183,6 +189,9 @@ namespace osu.Game.Online.Rooms
|
||||
EndDate.Value = other.EndDate.Value;
|
||||
UserScore.Value = other.UserScore.Value;
|
||||
QueueMode.Value = other.QueueMode.Value;
|
||||
DifficultyRange.Value = other.DifficultyRange.Value;
|
||||
PlaylistItemStats.Value = other.PlaylistItemStats.Value;
|
||||
CurrentPlaylistItem.Value = other.CurrentPlaylistItem.Value;
|
||||
|
||||
if (EndDate.Value != null && DateTimeOffset.Now >= EndDate.Value)
|
||||
Status.Value = new RoomStatusEnded();
|
||||
@ -211,21 +220,27 @@ namespace osu.Game.Online.Rooms
|
||||
Playlist.RemoveAll(i => i.Expired);
|
||||
}
|
||||
|
||||
#region Newtonsoft.Json implicit ShouldSerialize() methods
|
||||
[JsonObject(MemberSerialization.OptIn)]
|
||||
public class RoomPlaylistItemStats
|
||||
{
|
||||
[JsonProperty("count_active")]
|
||||
public int CountActive;
|
||||
|
||||
// The properties in this region are used implicitly by Newtonsoft.Json to not serialise certain fields in some cases.
|
||||
// They rely on being named exactly the same as the corresponding fields (casing included) and as such should NOT be renamed
|
||||
// unless the fields are also renamed.
|
||||
[JsonProperty("count_total")]
|
||||
public int CountTotal;
|
||||
|
||||
[UsedImplicitly]
|
||||
public bool ShouldSerializeRoomID() => false;
|
||||
[JsonProperty("ruleset_ids")]
|
||||
public int[] RulesetIDs;
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
public bool ShouldSerializeHost() => false;
|
||||
[JsonObject(MemberSerialization.OptIn)]
|
||||
public class RoomDifficultyRange
|
||||
{
|
||||
[JsonProperty("min")]
|
||||
public double Min;
|
||||
|
||||
[UsedImplicitly]
|
||||
public bool ShouldSerializeEndDate() => false;
|
||||
|
||||
#endregion
|
||||
[JsonProperty("max")]
|
||||
public double Max;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ namespace osu.Game.Online
|
||||
|
||||
// This should not be required. The fallback should work. But something is weird with the way caching is done.
|
||||
// For future adventurers, I would not advise looking into this further. It's likely not worth the effort.
|
||||
baseMap = baseMap.Concat(baseMap.Select(t => (t.baseType, t.baseType)));
|
||||
baseMap = baseMap.Concat(baseMap.Select(t => (t.baseType, t.baseType)).Distinct());
|
||||
|
||||
return new Dictionary<Type, IMessagePackFormatter>(baseMap.Select(t =>
|
||||
{
|
||||
|
@ -150,6 +150,7 @@ namespace osu.Game
|
||||
protected SettingsOverlay Settings;
|
||||
|
||||
private VolumeOverlay volume;
|
||||
|
||||
private OsuLogo osuLogo;
|
||||
|
||||
private MainMenu menuScreen;
|
||||
@ -837,7 +838,8 @@ namespace osu.Game
|
||||
channelManager.HighPollRate.Value =
|
||||
chatOverlay.State.Value == Visibility.Visible
|
||||
|| API.Activity.Value is UserActivity.InLobby
|
||||
|| API.Activity.Value is UserActivity.InMultiplayerGame;
|
||||
|| API.Activity.Value is UserActivity.InMultiplayerGame
|
||||
|| API.Activity.Value is UserActivity.SpectatingMultiplayerGame;
|
||||
}
|
||||
|
||||
Add(difficultyRecommender);
|
||||
@ -898,10 +900,22 @@ namespace osu.Game
|
||||
if (args?.Length > 0)
|
||||
{
|
||||
string[] paths = args.Where(a => !a.StartsWith('-')).ToArray();
|
||||
|
||||
if (paths.Length > 0)
|
||||
{
|
||||
string firstPath = paths.First();
|
||||
|
||||
if (firstPath.StartsWith(OSU_PROTOCOL, StringComparison.Ordinal))
|
||||
{
|
||||
HandleLink(firstPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
Task.Run(() => Import(paths));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void showOverlayAboveOthers(OverlayContainer overlay, OverlayContainer[] otherOverlays)
|
||||
{
|
||||
|
@ -22,6 +22,7 @@ using osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Formats;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Graphics;
|
||||
@ -52,6 +53,8 @@ namespace osu.Game
|
||||
/// </summary>
|
||||
public partial class OsuGameBase : Framework.Game, ICanAcceptFiles
|
||||
{
|
||||
public const string OSU_PROTOCOL = "osu://";
|
||||
|
||||
public const string CLIENT_STREAM_NAME = @"lazer";
|
||||
|
||||
public const int SAMPLE_CONCURRENCY = 6;
|
||||
@ -109,7 +112,7 @@ namespace osu.Game
|
||||
|
||||
protected SkinManager SkinManager { get; private set; }
|
||||
|
||||
protected RulesetStore RulesetStore { get; private set; }
|
||||
protected RealmRulesetStore RulesetStore { get; private set; }
|
||||
|
||||
protected RealmKeyBindingStore KeyBindingStore { get; private set; }
|
||||
|
||||
@ -200,9 +203,11 @@ namespace osu.Game
|
||||
|
||||
dependencies.Cache(realm = new RealmAccess(Storage, "client", EFContextFactory));
|
||||
|
||||
dependencies.Cache(RulesetStore = new RulesetStore(realm, Storage));
|
||||
dependencies.CacheAs<RulesetStore>(RulesetStore = new RealmRulesetStore(realm, Storage));
|
||||
dependencies.CacheAs<IRulesetStore>(RulesetStore);
|
||||
|
||||
Decoder.RegisterDependencies(RulesetStore);
|
||||
|
||||
// Backup is taken here rather than in EFToRealmMigrator to avoid recycling realm contexts
|
||||
// after initial usages below. It can be moved once a direction is established for handling re-subscription.
|
||||
// See https://github.com/ppy/osu/pull/16547 for more discussion.
|
||||
|
@ -183,7 +183,14 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
}
|
||||
|
||||
starRatingContainer.FadeOut(100);
|
||||
Beatmap.Value = Difficulties.FirstOrDefault()?.Beatmap;
|
||||
|
||||
// If a selection is already made, try and maintain it.
|
||||
if (Beatmap.Value != null)
|
||||
Beatmap.Value = Difficulties.FirstOrDefault(b => b.Beatmap.OnlineID == Beatmap.Value.OnlineID)?.Beatmap;
|
||||
|
||||
// Else just choose the first available difficulty for now.
|
||||
Beatmap.Value ??= Difficulties.FirstOrDefault()?.Beatmap;
|
||||
|
||||
plays.Value = BeatmapSet?.PlayCount ?? 0;
|
||||
favourites.Value = BeatmapSet?.FavouriteCount ?? 0;
|
||||
|
||||
|
@ -8,11 +8,7 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Utils;
|
||||
using osuTK;
|
||||
|
||||
@ -66,52 +62,5 @@ namespace osu.Game.Overlays.Mods
|
||||
}
|
||||
|
||||
public override ITooltip<Mod> GetCustomTooltip() => new IncompatibilityDisplayingTooltip();
|
||||
|
||||
private class IncompatibilityDisplayingTooltip : ModButtonTooltip
|
||||
{
|
||||
private readonly OsuSpriteText incompatibleText;
|
||||
|
||||
private readonly Bindable<IReadOnlyList<Mod>> incompatibleMods = new Bindable<IReadOnlyList<Mod>>();
|
||||
|
||||
[Resolved]
|
||||
private Bindable<RulesetInfo> ruleset { get; set; }
|
||||
|
||||
public IncompatibilityDisplayingTooltip()
|
||||
{
|
||||
AddRange(new Drawable[]
|
||||
{
|
||||
incompatibleText = new OsuSpriteText
|
||||
{
|
||||
Margin = new MarginPadding { Top = 5 },
|
||||
Font = OsuFont.GetFont(weight: FontWeight.Regular),
|
||||
Text = "Incompatible with:"
|
||||
},
|
||||
new ModDisplay
|
||||
{
|
||||
Current = incompatibleMods,
|
||||
ExpansionMode = ExpansionMode.AlwaysExpanded,
|
||||
Scale = new Vector2(0.7f)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
incompatibleText.Colour = colours.BlueLight;
|
||||
}
|
||||
|
||||
protected override void UpdateDisplay(Mod mod)
|
||||
{
|
||||
base.UpdateDisplay(mod);
|
||||
|
||||
var incompatibleTypes = mod.IncompatibleMods;
|
||||
|
||||
var allMods = ruleset.Value.CreateInstance().AllMods;
|
||||
|
||||
incompatibleMods.Value = allMods.Where(m => m.GetType() != mod.GetType() && incompatibleTypes.Any(t => t.IsInstanceOfType(m))).Select(m => m.CreateInstance()).ToList();
|
||||
incompatibleText.Text = incompatibleMods.Value.Any() ? "Incompatible with:" : "Compatible with all mods";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
88
osu.Game/Overlays/Mods/IncompatibilityDisplayingModPanel.cs
Normal file
88
osu.Game/Overlays/Mods/IncompatibilityDisplayingModPanel.cs
Normal file
@ -0,0 +1,88 @@
|
||||
// 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.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
public class IncompatibilityDisplayingModPanel : ModPanel, IHasCustomTooltip<Mod>
|
||||
{
|
||||
private readonly BindableBool incompatible = new BindableBool();
|
||||
|
||||
[Resolved]
|
||||
private Bindable<IReadOnlyList<Mod>> selectedMods { get; set; }
|
||||
|
||||
public IncompatibilityDisplayingModPanel(Mod mod)
|
||||
: base(mod)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
selectedMods.BindValueChanged(_ => updateIncompatibility(), true);
|
||||
incompatible.BindValueChanged(_ => Scheduler.AddOnce(UpdateState));
|
||||
// base call will run `UpdateState()` first time and finish transforms.
|
||||
base.LoadComplete();
|
||||
}
|
||||
|
||||
private void updateIncompatibility()
|
||||
{
|
||||
incompatible.Value = selectedMods.Value.Count > 0 && !selectedMods.Value.Contains(Mod) && !ModUtils.CheckCompatibleSet(selectedMods.Value.Append(Mod));
|
||||
}
|
||||
|
||||
protected override void UpdateState()
|
||||
{
|
||||
Action = incompatible.Value ? () => { } : (Action)Active.Toggle;
|
||||
|
||||
if (incompatible.Value)
|
||||
{
|
||||
Colour4 backgroundColour = ColourProvider.Background5;
|
||||
Colour4 textBackgroundColour = ColourProvider.Background4;
|
||||
|
||||
Content.TransformTo(nameof(BorderColour), ColourInfo.GradientVertical(backgroundColour, textBackgroundColour), TRANSITION_DURATION, Easing.OutQuint);
|
||||
Background.FadeColour(backgroundColour, TRANSITION_DURATION, Easing.OutQuint);
|
||||
|
||||
SwitchContainer.ResizeWidthTo(IDLE_SWITCH_WIDTH, TRANSITION_DURATION, Easing.OutQuint);
|
||||
SwitchContainer.FadeColour(Colour4.Gray, TRANSITION_DURATION, Easing.OutQuint);
|
||||
MainContentContainer.TransformTo(nameof(Padding), new MarginPadding
|
||||
{
|
||||
Left = IDLE_SWITCH_WIDTH,
|
||||
Right = CORNER_RADIUS
|
||||
}, TRANSITION_DURATION, Easing.OutQuint);
|
||||
|
||||
TextBackground.FadeColour(textBackgroundColour, TRANSITION_DURATION, Easing.OutQuint);
|
||||
TextFlow.FadeColour(Colour4.White.Opacity(0.5f), TRANSITION_DURATION, Easing.OutQuint);
|
||||
return;
|
||||
}
|
||||
|
||||
SwitchContainer.FadeColour(Colour4.White, TRANSITION_DURATION, Easing.OutQuint);
|
||||
base.UpdateState();
|
||||
}
|
||||
|
||||
protected override bool OnMouseDown(MouseDownEvent e)
|
||||
{
|
||||
if (incompatible.Value)
|
||||
return true; // bypasses base call purposely in order to not play out the intermediate state animation.
|
||||
|
||||
return base.OnMouseDown(e);
|
||||
}
|
||||
|
||||
#region IHasCustomTooltip
|
||||
|
||||
public ITooltip<Mod> GetCustomTooltip() => new IncompatibilityDisplayingTooltip();
|
||||
|
||||
public Mod TooltipContent => Mod;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
64
osu.Game/Overlays/Mods/IncompatibilityDisplayingTooltip.cs
Normal file
64
osu.Game/Overlays/Mods/IncompatibilityDisplayingTooltip.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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
internal class IncompatibilityDisplayingTooltip : ModButtonTooltip
|
||||
{
|
||||
private readonly OsuSpriteText incompatibleText;
|
||||
|
||||
private readonly Bindable<IReadOnlyList<Mod>> incompatibleMods = new Bindable<IReadOnlyList<Mod>>();
|
||||
|
||||
[Resolved]
|
||||
private Bindable<RulesetInfo> ruleset { get; set; }
|
||||
|
||||
public IncompatibilityDisplayingTooltip()
|
||||
{
|
||||
AddRange(new Drawable[]
|
||||
{
|
||||
incompatibleText = new OsuSpriteText
|
||||
{
|
||||
Margin = new MarginPadding { Top = 5 },
|
||||
Font = OsuFont.GetFont(weight: FontWeight.Regular),
|
||||
Text = "Incompatible with:"
|
||||
},
|
||||
new ModDisplay
|
||||
{
|
||||
Current = incompatibleMods,
|
||||
ExpansionMode = ExpansionMode.AlwaysExpanded,
|
||||
Scale = new Vector2(0.7f)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
incompatibleText.Colour = colours.BlueLight;
|
||||
}
|
||||
|
||||
protected override void UpdateDisplay(Mod mod)
|
||||
{
|
||||
base.UpdateDisplay(mod);
|
||||
|
||||
var incompatibleTypes = mod.IncompatibleMods;
|
||||
|
||||
var allMods = ruleset.Value.CreateInstance().AllMods;
|
||||
|
||||
incompatibleMods.Value = allMods.Where(m => m.GetType() != mod.GetType() && incompatibleTypes.Any(t => t.IsInstanceOfType(m))).Select(m => m.CreateInstance()).ToList();
|
||||
incompatibleText.Text = incompatibleMods.Value.Any() ? "Incompatible with:" : "Compatible with all mods";
|
||||
}
|
||||
}
|
||||
}
|
239
osu.Game/Overlays/Mods/ModPanel.cs
Normal file
239
osu.Game/Overlays/Mods/ModPanel.cs
Normal file
@ -0,0 +1,239 @@
|
||||
// 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.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
public class ModPanel : OsuClickableContainer
|
||||
{
|
||||
public Mod Mod { get; }
|
||||
public BindableBool Active { get; } = new BindableBool();
|
||||
|
||||
protected readonly Box Background;
|
||||
protected readonly Container SwitchContainer;
|
||||
protected readonly Container MainContentContainer;
|
||||
protected readonly Box TextBackground;
|
||||
protected readonly FillFlowContainer TextFlow;
|
||||
|
||||
[Resolved]
|
||||
protected OverlayColourProvider ColourProvider { get; private set; } = null!;
|
||||
|
||||
protected const double TRANSITION_DURATION = 150;
|
||||
|
||||
protected const float SHEAR_X = 0.2f;
|
||||
|
||||
protected const float HEIGHT = 42;
|
||||
protected const float CORNER_RADIUS = 7;
|
||||
protected const float IDLE_SWITCH_WIDTH = 54;
|
||||
protected const float EXPANDED_SWITCH_WIDTH = 70;
|
||||
|
||||
private Colour4 activeColour;
|
||||
|
||||
private Sample? sampleOff;
|
||||
private Sample? sampleOn;
|
||||
|
||||
public ModPanel(Mod mod)
|
||||
{
|
||||
Mod = mod;
|
||||
|
||||
RelativeSizeAxes = Axes.X;
|
||||
Height = 42;
|
||||
|
||||
// all below properties are applied to `Content` rather than the `ModPanel` in its entirety
|
||||
// to allow external components to set these properties on the panel without affecting
|
||||
// its "internal" appearance.
|
||||
Content.Masking = true;
|
||||
Content.CornerRadius = CORNER_RADIUS;
|
||||
Content.BorderThickness = 2;
|
||||
Content.Shear = new Vector2(SHEAR_X, 0);
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
Background = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
SwitchContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Child = new ModSwitchSmall(mod)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Active = { BindTarget = Active },
|
||||
Shear = new Vector2(-SHEAR_X, 0),
|
||||
Scale = new Vector2(HEIGHT / ModSwitchSmall.DEFAULT_SIZE)
|
||||
}
|
||||
},
|
||||
MainContentContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
CornerRadius = CORNER_RADIUS,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
TextBackground = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
TextFlow = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Horizontal = 17.5f,
|
||||
Vertical = 4
|
||||
},
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = mod.Name,
|
||||
Font = OsuFont.TorusAlternate.With(size: 18, weight: FontWeight.SemiBold),
|
||||
Shear = new Vector2(-SHEAR_X, 0),
|
||||
Margin = new MarginPadding
|
||||
{
|
||||
Left = -18 * SHEAR_X
|
||||
}
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = mod.Description,
|
||||
Font = OsuFont.Default.With(size: 12),
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Truncate = true,
|
||||
Shear = new Vector2(-SHEAR_X, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Action = Active.Toggle;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio, OsuColour colours)
|
||||
{
|
||||
sampleOn = audio.Samples.Get(@"UI/check-on");
|
||||
sampleOff = audio.Samples.Get(@"UI/check-off");
|
||||
|
||||
activeColour = colours.ForModType(Mod.Type);
|
||||
}
|
||||
|
||||
protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds(sampleSet);
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
Active.BindValueChanged(_ =>
|
||||
{
|
||||
playStateChangeSamples();
|
||||
UpdateState();
|
||||
});
|
||||
|
||||
UpdateState();
|
||||
FinishTransforms(true);
|
||||
}
|
||||
|
||||
private void playStateChangeSamples()
|
||||
{
|
||||
if (Active.Value)
|
||||
sampleOn?.Play();
|
||||
else
|
||||
sampleOff?.Play();
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
UpdateState();
|
||||
return base.OnHover(e);
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
UpdateState();
|
||||
base.OnHoverLost(e);
|
||||
}
|
||||
|
||||
private bool mouseDown;
|
||||
|
||||
protected override bool OnMouseDown(MouseDownEvent e)
|
||||
{
|
||||
if (e.Button == MouseButton.Left)
|
||||
mouseDown = true;
|
||||
|
||||
UpdateState();
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnMouseUp(MouseUpEvent e)
|
||||
{
|
||||
mouseDown = false;
|
||||
|
||||
UpdateState();
|
||||
base.OnMouseUp(e);
|
||||
}
|
||||
|
||||
protected virtual void UpdateState()
|
||||
{
|
||||
float targetWidth = Active.Value ? EXPANDED_SWITCH_WIDTH : IDLE_SWITCH_WIDTH;
|
||||
double transitionDuration = TRANSITION_DURATION;
|
||||
|
||||
Colour4 textBackgroundColour = Active.Value ? activeColour : (Colour4)ColourProvider.Background2;
|
||||
Colour4 mainBackgroundColour = Active.Value ? activeColour.Darken(0.3f) : (Colour4)ColourProvider.Background3;
|
||||
Colour4 textColour = Active.Value ? (Colour4)ColourProvider.Background6 : Colour4.White;
|
||||
|
||||
// Hover affects colour of button background
|
||||
if (IsHovered)
|
||||
{
|
||||
textBackgroundColour = textBackgroundColour.Lighten(0.1f);
|
||||
mainBackgroundColour = mainBackgroundColour.Lighten(0.1f);
|
||||
}
|
||||
|
||||
// Mouse down adds a halfway tween of the movement
|
||||
if (mouseDown)
|
||||
{
|
||||
targetWidth = (float)Interpolation.Lerp(IDLE_SWITCH_WIDTH, EXPANDED_SWITCH_WIDTH, 0.5f);
|
||||
transitionDuration *= 4;
|
||||
}
|
||||
|
||||
Content.TransformTo(nameof(BorderColour), ColourInfo.GradientVertical(mainBackgroundColour, textBackgroundColour), transitionDuration, Easing.OutQuint);
|
||||
Background.FadeColour(mainBackgroundColour, transitionDuration, Easing.OutQuint);
|
||||
SwitchContainer.ResizeWidthTo(targetWidth, transitionDuration, Easing.OutQuint);
|
||||
MainContentContainer.TransformTo(nameof(Padding), new MarginPadding
|
||||
{
|
||||
Left = targetWidth,
|
||||
Right = CORNER_RADIUS
|
||||
}, transitionDuration, Easing.OutQuint);
|
||||
TextBackground.FadeColour(textBackgroundColour, transitionDuration, Easing.OutQuint);
|
||||
TextFlow.FadeColour(textColour, transitionDuration, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
}
|
@ -30,17 +30,20 @@ namespace osu.Game.Overlays.Music
|
||||
[Resolved(canBeNull: true)]
|
||||
private OnScreenDisplay onScreenDisplay { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private OsuGame game { get; set; }
|
||||
|
||||
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||
{
|
||||
if (e.Repeat)
|
||||
return false;
|
||||
|
||||
if (beatmap.Disabled)
|
||||
return false;
|
||||
|
||||
switch (e.Action)
|
||||
{
|
||||
case GlobalAction.MusicPlay:
|
||||
if (game.LocalUserPlaying.Value)
|
||||
return false;
|
||||
|
||||
// use previous state as TogglePause may not update the track's state immediately (state update is run on the audio thread see https://github.com/ppy/osu/issues/9880#issuecomment-674668842)
|
||||
bool wasPlaying = musicController.IsPlaying;
|
||||
|
||||
@ -49,11 +52,17 @@ namespace osu.Game.Overlays.Music
|
||||
return true;
|
||||
|
||||
case GlobalAction.MusicNext:
|
||||
if (beatmap.Disabled)
|
||||
return false;
|
||||
|
||||
musicController.NextTrack(() => onScreenDisplay?.Display(new MusicActionToast(GlobalActionKeyBindingStrings.MusicNext, e.Action)));
|
||||
|
||||
return true;
|
||||
|
||||
case GlobalAction.MusicPrev:
|
||||
if (beatmap.Disabled)
|
||||
return false;
|
||||
|
||||
musicController.PreviousTrack(res =>
|
||||
{
|
||||
switch (res)
|
||||
|
51
osu.Game/Rulesets/AssemblyRulesetStore.cs
Normal file
51
osu.Game/Rulesets/AssemblyRulesetStore.cs
Normal file
@ -0,0 +1,51 @@
|
||||
// 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.ObjectExtensions;
|
||||
using osu.Framework.Platform;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace osu.Game.Rulesets
|
||||
{
|
||||
/// <summary>
|
||||
/// A ruleset store that populates from loaded assemblies (and optionally, assemblies in a storage).
|
||||
/// </summary>
|
||||
public class AssemblyRulesetStore : RulesetStore
|
||||
{
|
||||
public override IEnumerable<RulesetInfo> AvailableRulesets => availableRulesets;
|
||||
|
||||
private readonly List<RulesetInfo> availableRulesets = new List<RulesetInfo>();
|
||||
|
||||
/// <summary>
|
||||
/// Create an assembly ruleset store that populates from loaded assemblies and an external location.
|
||||
/// </summary>
|
||||
/// <param name="path">An path containing ruleset DLLs.</param>
|
||||
public AssemblyRulesetStore(string path)
|
||||
: this(new NativeStorage(path))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an assembly ruleset store that populates from loaded assemblies and an optional storage source.
|
||||
/// </summary>
|
||||
/// <param name="storage">An optional storage containing ruleset DLLs.</param>
|
||||
public AssemblyRulesetStore(Storage? storage = null)
|
||||
: base(storage)
|
||||
|
||||
{
|
||||
List<Ruleset> instances = LoadedAssemblies.Values
|
||||
.Select(r => Activator.CreateInstance(r) as Ruleset)
|
||||
.Where(r => r != null)
|
||||
.Select(r => r.AsNonNull())
|
||||
.ToList();
|
||||
|
||||
// add all legacy rulesets first to ensure they have exclusive choice of primary key.
|
||||
foreach (var r in instances.Where(r => r is ILegacyRuleset))
|
||||
availableRulesets.Add(new RulesetInfo(r.RulesetInfo.ShortName, r.RulesetInfo.Name, r.RulesetInfo.InstantiationInfo, r.RulesetInfo.OnlineID));
|
||||
}
|
||||
}
|
||||
}
|
101
osu.Game/Rulesets/RealmRulesetStore.cs
Normal file
101
osu.Game/Rulesets/RealmRulesetStore.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 System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.Database;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace osu.Game.Rulesets
|
||||
{
|
||||
public class RealmRulesetStore : RulesetStore
|
||||
{
|
||||
public override IEnumerable<RulesetInfo> AvailableRulesets => availableRulesets;
|
||||
|
||||
private readonly List<RulesetInfo> availableRulesets = new List<RulesetInfo>();
|
||||
|
||||
public RealmRulesetStore(RealmAccess realm, Storage? storage = null)
|
||||
: base(storage)
|
||||
{
|
||||
prepareDetachedRulesets(realm);
|
||||
}
|
||||
|
||||
private void prepareDetachedRulesets(RealmAccess realmAccess)
|
||||
{
|
||||
realmAccess.Write(realm =>
|
||||
{
|
||||
var rulesets = realm.All<RulesetInfo>();
|
||||
|
||||
List<Ruleset> instances = LoadedAssemblies.Values
|
||||
.Select(r => Activator.CreateInstance(r) as Ruleset)
|
||||
.Where(r => r != null)
|
||||
.Select(r => r.AsNonNull())
|
||||
.ToList();
|
||||
|
||||
// add all legacy rulesets first to ensure they have exclusive choice of primary key.
|
||||
foreach (var r in instances.Where(r => r is ILegacyRuleset))
|
||||
{
|
||||
if (realm.All<RulesetInfo>().FirstOrDefault(rr => rr.OnlineID == r.RulesetInfo.OnlineID) == null)
|
||||
realm.Add(new RulesetInfo(r.RulesetInfo.ShortName, r.RulesetInfo.Name, r.RulesetInfo.InstantiationInfo, r.RulesetInfo.OnlineID));
|
||||
}
|
||||
|
||||
// add any other rulesets which have assemblies present but are not yet in the database.
|
||||
foreach (var r in instances.Where(r => !(r is ILegacyRuleset)))
|
||||
{
|
||||
if (rulesets.FirstOrDefault(ri => ri.InstantiationInfo.Equals(r.RulesetInfo.InstantiationInfo, StringComparison.Ordinal)) == null)
|
||||
{
|
||||
var existingSameShortName = rulesets.FirstOrDefault(ri => ri.ShortName == r.RulesetInfo.ShortName);
|
||||
|
||||
if (existingSameShortName != null)
|
||||
{
|
||||
// even if a matching InstantiationInfo was not found, there may be an existing ruleset with the same ShortName.
|
||||
// this generally means the user or ruleset provider has renamed their dll but the underlying ruleset is *likely* the same one.
|
||||
// in such cases, update the instantiation info of the existing entry to point to the new one.
|
||||
existingSameShortName.InstantiationInfo = r.RulesetInfo.InstantiationInfo;
|
||||
}
|
||||
else
|
||||
realm.Add(new RulesetInfo(r.RulesetInfo.ShortName, r.RulesetInfo.Name, r.RulesetInfo.InstantiationInfo, r.RulesetInfo.OnlineID));
|
||||
}
|
||||
}
|
||||
|
||||
List<RulesetInfo> detachedRulesets = new List<RulesetInfo>();
|
||||
|
||||
// perform a consistency check and detach final rulesets from realm for cross-thread runtime usage.
|
||||
foreach (var r in rulesets.OrderBy(r => r.OnlineID))
|
||||
{
|
||||
try
|
||||
{
|
||||
var resolvedType = Type.GetType(r.InstantiationInfo)
|
||||
?? throw new RulesetLoadException(@"Type could not be resolved");
|
||||
|
||||
var instanceInfo = (Activator.CreateInstance(resolvedType) as Ruleset)?.RulesetInfo
|
||||
?? throw new RulesetLoadException(@"Instantiation failure");
|
||||
|
||||
// If a ruleset isn't up-to-date with the API, it could cause a crash at an arbitrary point of execution.
|
||||
// To eagerly handle cases of missing implementations, enumerate all types here and mark as non-available on throw.
|
||||
resolvedType.Assembly.GetTypes();
|
||||
|
||||
r.Name = instanceInfo.Name;
|
||||
r.ShortName = instanceInfo.ShortName;
|
||||
r.InstantiationInfo = instanceInfo.InstantiationInfo;
|
||||
r.Available = true;
|
||||
|
||||
detachedRulesets.Add(r.Clone());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
r.Available = false;
|
||||
Logger.Log($"Could not load ruleset {r}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
availableRulesets.AddRange(detachedRulesets.OrderBy(r => r));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -7,34 +7,26 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using osu.Framework;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.Database;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace osu.Game.Rulesets
|
||||
{
|
||||
public class RulesetStore : IDisposable, IRulesetStore
|
||||
public abstract class RulesetStore : IDisposable, IRulesetStore
|
||||
{
|
||||
private readonly RealmAccess realmAccess;
|
||||
|
||||
private const string ruleset_library_prefix = @"osu.Game.Rulesets";
|
||||
|
||||
private readonly Dictionary<Assembly, Type> loadedAssemblies = new Dictionary<Assembly, Type>();
|
||||
protected readonly Dictionary<Assembly, Type> LoadedAssemblies = new Dictionary<Assembly, Type>();
|
||||
|
||||
/// <summary>
|
||||
/// All available rulesets.
|
||||
/// </summary>
|
||||
public IEnumerable<RulesetInfo> AvailableRulesets => availableRulesets;
|
||||
public abstract IEnumerable<RulesetInfo> AvailableRulesets { get; }
|
||||
|
||||
private readonly List<RulesetInfo> availableRulesets = new List<RulesetInfo>();
|
||||
|
||||
public RulesetStore(RealmAccess realm, Storage? storage = null)
|
||||
protected RulesetStore(Storage? storage = null)
|
||||
{
|
||||
realmAccess = realm;
|
||||
|
||||
// On android in release configuration assemblies are loaded from the apk directly into memory.
|
||||
// We cannot read assemblies from cwd, so should check loaded assemblies instead.
|
||||
loadFromAppDomain();
|
||||
@ -53,8 +45,6 @@ namespace osu.Game.Rulesets
|
||||
var rulesetStorage = storage?.GetStorageForDirectory(@"rulesets");
|
||||
if (rulesetStorage != null)
|
||||
loadUserRulesets(rulesetStorage);
|
||||
|
||||
addMissingRulesets();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -95,80 +85,7 @@ namespace osu.Game.Rulesets
|
||||
if (domainAssembly != null)
|
||||
return domainAssembly;
|
||||
|
||||
return loadedAssemblies.Keys.FirstOrDefault(a => a.FullName == asm.FullName);
|
||||
}
|
||||
|
||||
private void addMissingRulesets()
|
||||
{
|
||||
realmAccess.Write(realm =>
|
||||
{
|
||||
var rulesets = realm.All<RulesetInfo>();
|
||||
|
||||
List<Ruleset> instances = loadedAssemblies.Values
|
||||
.Select(r => Activator.CreateInstance(r) as Ruleset)
|
||||
.Where(r => r != null)
|
||||
.Select(r => r.AsNonNull())
|
||||
.ToList();
|
||||
|
||||
// add all legacy rulesets first to ensure they have exclusive choice of primary key.
|
||||
foreach (var r in instances.Where(r => r is ILegacyRuleset))
|
||||
{
|
||||
if (realm.All<RulesetInfo>().FirstOrDefault(rr => rr.OnlineID == r.RulesetInfo.OnlineID) == null)
|
||||
realm.Add(new RulesetInfo(r.RulesetInfo.ShortName, r.RulesetInfo.Name, r.RulesetInfo.InstantiationInfo, r.RulesetInfo.OnlineID));
|
||||
}
|
||||
|
||||
// add any other rulesets which have assemblies present but are not yet in the database.
|
||||
foreach (var r in instances.Where(r => !(r is ILegacyRuleset)))
|
||||
{
|
||||
if (rulesets.FirstOrDefault(ri => ri.InstantiationInfo.Equals(r.RulesetInfo.InstantiationInfo, StringComparison.Ordinal)) == null)
|
||||
{
|
||||
var existingSameShortName = rulesets.FirstOrDefault(ri => ri.ShortName == r.RulesetInfo.ShortName);
|
||||
|
||||
if (existingSameShortName != null)
|
||||
{
|
||||
// even if a matching InstantiationInfo was not found, there may be an existing ruleset with the same ShortName.
|
||||
// this generally means the user or ruleset provider has renamed their dll but the underlying ruleset is *likely* the same one.
|
||||
// in such cases, update the instantiation info of the existing entry to point to the new one.
|
||||
existingSameShortName.InstantiationInfo = r.RulesetInfo.InstantiationInfo;
|
||||
}
|
||||
else
|
||||
realm.Add(new RulesetInfo(r.RulesetInfo.ShortName, r.RulesetInfo.Name, r.RulesetInfo.InstantiationInfo, r.RulesetInfo.OnlineID));
|
||||
}
|
||||
}
|
||||
|
||||
List<RulesetInfo> detachedRulesets = new List<RulesetInfo>();
|
||||
|
||||
// perform a consistency check and detach final rulesets from realm for cross-thread runtime usage.
|
||||
foreach (var r in rulesets.OrderBy(r => r.OnlineID))
|
||||
{
|
||||
try
|
||||
{
|
||||
var resolvedType = Type.GetType(r.InstantiationInfo)
|
||||
?? throw new RulesetLoadException(@"Type could not be resolved");
|
||||
|
||||
var instanceInfo = (Activator.CreateInstance(resolvedType) as Ruleset)?.RulesetInfo
|
||||
?? throw new RulesetLoadException(@"Instantiation failure");
|
||||
|
||||
// If a ruleset isn't up-to-date with the API, it could cause a crash at an arbitrary point of execution.
|
||||
// To eagerly handle cases of missing implementations, enumerate all types here and mark as non-available on throw.
|
||||
resolvedType.Assembly.GetTypes();
|
||||
|
||||
r.Name = instanceInfo.Name;
|
||||
r.ShortName = instanceInfo.ShortName;
|
||||
r.InstantiationInfo = instanceInfo.InstantiationInfo;
|
||||
r.Available = true;
|
||||
|
||||
detachedRulesets.Add(r.Clone());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
r.Available = false;
|
||||
Logger.Log($"Could not load ruleset {r}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
availableRulesets.AddRange(detachedRulesets.OrderBy(r => r));
|
||||
});
|
||||
return LoadedAssemblies.Keys.FirstOrDefault(a => a.FullName == asm.FullName);
|
||||
}
|
||||
|
||||
private void loadFromAppDomain()
|
||||
@ -214,7 +131,7 @@ namespace osu.Game.Rulesets
|
||||
{
|
||||
string? filename = Path.GetFileNameWithoutExtension(file);
|
||||
|
||||
if (loadedAssemblies.Values.Any(t => Path.GetFileNameWithoutExtension(t.Assembly.Location) == filename))
|
||||
if (LoadedAssemblies.Values.Any(t => Path.GetFileNameWithoutExtension(t.Assembly.Location) == filename))
|
||||
return;
|
||||
|
||||
try
|
||||
@ -229,17 +146,17 @@ namespace osu.Game.Rulesets
|
||||
|
||||
private void addRuleset(Assembly assembly)
|
||||
{
|
||||
if (loadedAssemblies.ContainsKey(assembly))
|
||||
if (LoadedAssemblies.ContainsKey(assembly))
|
||||
return;
|
||||
|
||||
// the same assembly may be loaded twice in the same AppDomain (currently a thing in certain Rider versions https://youtrack.jetbrains.com/issue/RIDER-48799).
|
||||
// as a failsafe, also compare by FullName.
|
||||
if (loadedAssemblies.Any(a => a.Key.FullName == assembly.FullName))
|
||||
if (LoadedAssemblies.Any(a => a.Key.FullName == assembly.FullName))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
loadedAssemblies[assembly] = assembly.GetTypes().First(t => t.IsPublic && t.IsSubclassOf(typeof(Ruleset)));
|
||||
LoadedAssemblies[assembly] = assembly.GetTypes().First(t => t.IsPublic && t.IsSubclassOf(typeof(Ruleset)));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
109
osu.Game/Rulesets/UI/ModSwitchSmall.cs
Normal file
109
osu.Game/Rulesets/UI/ModSwitchSmall.cs
Normal file
@ -0,0 +1,109 @@
|
||||
// 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.Graphics.Sprites;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace osu.Game.Rulesets.UI
|
||||
{
|
||||
public class ModSwitchSmall : CompositeDrawable
|
||||
{
|
||||
public BindableBool Active { get; } = new BindableBool();
|
||||
|
||||
public const float DEFAULT_SIZE = 60;
|
||||
|
||||
private readonly IMod mod;
|
||||
|
||||
private readonly SpriteIcon background;
|
||||
private readonly SpriteIcon? modIcon;
|
||||
|
||||
private Color4 activeForegroundColour;
|
||||
private Color4 inactiveForegroundColour;
|
||||
|
||||
private Color4 activeBackgroundColour;
|
||||
private Color4 inactiveBackgroundColour;
|
||||
|
||||
public ModSwitchSmall(IMod mod)
|
||||
{
|
||||
this.mod = mod;
|
||||
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
FillFlowContainer contentFlow;
|
||||
ModSwitchTiny tinySwitch;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
background = new SpriteIcon
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(DEFAULT_SIZE),
|
||||
Icon = OsuIcon.ModBg
|
||||
},
|
||||
contentFlow = new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Spacing = new Vector2(0, 4),
|
||||
Direction = FillDirection.Vertical,
|
||||
Child = tinySwitch = new ModSwitchTiny(mod)
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Scale = new Vector2(0.6f),
|
||||
Active = { BindTarget = Active }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (mod.Icon != null)
|
||||
{
|
||||
contentFlow.Insert(-1, modIcon = new SpriteIcon
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Size = new Vector2(21),
|
||||
Icon = mod.Icon.Value
|
||||
});
|
||||
tinySwitch.Scale = new Vector2(0.3f);
|
||||
}
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(OsuColour colours, OverlayColourProvider? colourProvider)
|
||||
{
|
||||
inactiveForegroundColour = colourProvider?.Background5 ?? colours.Gray3;
|
||||
activeForegroundColour = colours.ForModType(mod.Type);
|
||||
|
||||
inactiveBackgroundColour = colourProvider?.Background2 ?? colours.Gray5;
|
||||
activeBackgroundColour = Interpolation.ValueAt<Colour4>(0.1f, Colour4.Black, activeForegroundColour, 0, 1);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Active.BindValueChanged(_ => updateState(), true);
|
||||
FinishTransforms(true);
|
||||
}
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
modIcon?.FadeColour(Active.Value ? activeForegroundColour : inactiveForegroundColour, 200, Easing.OutQuint);
|
||||
background.FadeColour(Active.Value ? activeBackgroundColour : inactiveBackgroundColour, 200, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
}
|
93
osu.Game/Rulesets/UI/ModSwitchTiny.cs
Normal file
93
osu.Game/Rulesets/UI/ModSwitchTiny.cs
Normal file
@ -0,0 +1,93 @@
|
||||
// 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.Graphics.Shapes;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace osu.Game.Rulesets.UI
|
||||
{
|
||||
public class ModSwitchTiny : CompositeDrawable
|
||||
{
|
||||
public BindableBool Active { get; } = new BindableBool();
|
||||
|
||||
public const float DEFAULT_HEIGHT = 30;
|
||||
|
||||
private readonly IMod mod;
|
||||
|
||||
private readonly Box background;
|
||||
private readonly OsuSpriteText acronymText;
|
||||
|
||||
private Color4 activeForegroundColour;
|
||||
private Color4 inactiveForegroundColour;
|
||||
|
||||
private Color4 activeBackgroundColour;
|
||||
private Color4 inactiveBackgroundColour;
|
||||
|
||||
public ModSwitchTiny(IMod mod)
|
||||
{
|
||||
this.mod = mod;
|
||||
Size = new Vector2(73, DEFAULT_HEIGHT);
|
||||
|
||||
InternalChild = new CircularContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
background = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
acronymText = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Shadow = false,
|
||||
Font = OsuFont.Numeric.With(size: 24, weight: FontWeight.Black),
|
||||
Text = mod.Acronym,
|
||||
Margin = new MarginPadding
|
||||
{
|
||||
Top = 4
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(OsuColour colours, OverlayColourProvider? colourProvider)
|
||||
{
|
||||
inactiveBackgroundColour = colourProvider?.Background5 ?? colours.Gray3;
|
||||
activeBackgroundColour = colours.ForModType(mod.Type);
|
||||
|
||||
inactiveForegroundColour = colourProvider?.Background2 ?? colours.Gray5;
|
||||
activeForegroundColour = Interpolation.ValueAt<Colour4>(0.1f, Colour4.Black, activeForegroundColour, 0, 1);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Active.BindValueChanged(_ => updateState(), true);
|
||||
FinishTransforms(true);
|
||||
}
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
acronymText.FadeColour(Active.Value ? activeForegroundColour : inactiveForegroundColour, 200, Easing.OutQuint);
|
||||
background.FadeColour(Active.Value ? activeBackgroundColour : inactiveBackgroundColour, 200, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
}
|
@ -2,15 +2,19 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Overlays;
|
||||
@ -25,7 +29,7 @@ using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.Menu
|
||||
{
|
||||
public class MainMenu : OsuScreen, IHandlePresentBeatmap
|
||||
public class MainMenu : OsuScreen, IHandlePresentBeatmap, IKeyBindingHandler<GlobalAction>
|
||||
{
|
||||
public const float FADE_IN_DURATION = 300;
|
||||
|
||||
@ -297,5 +301,26 @@ namespace osu.Game.Screens.Menu
|
||||
|
||||
Schedule(loadSoloSongSelect);
|
||||
}
|
||||
|
||||
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||
{
|
||||
if (e.Repeat)
|
||||
return false;
|
||||
|
||||
switch (e.Action)
|
||||
{
|
||||
case GlobalAction.Back:
|
||||
// In the case of a host being able to exit, the back action is handled by ExitConfirmOverlay.
|
||||
Debug.Assert(!host.CanExit);
|
||||
|
||||
return host.SuspendToBackground();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnReleased(KeyBindingReleaseEvent<GlobalAction> e)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
|
||||
{
|
||||
InternalChild = sprite = CreateBackgroundSprite();
|
||||
|
||||
CurrentPlaylistItem.BindValueChanged(_ => updateBeatmap());
|
||||
Playlist.CollectionChanged += (_, __) => updateBeatmap();
|
||||
|
||||
updateBeatmap();
|
||||
@ -30,7 +31,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
|
||||
|
||||
private void updateBeatmap()
|
||||
{
|
||||
sprite.Beatmap.Value = Playlist.GetCurrentItem()?.Beatmap;
|
||||
sprite.Beatmap.Value = CurrentPlaylistItem.Value?.Beatmap ?? Playlist.GetCurrentItem()?.Beatmap;
|
||||
}
|
||||
|
||||
protected virtual UpdateableBeatmapBackgroundSprite CreateBackgroundSprite() => new UpdateableBeatmapBackgroundSprite(BeatmapSetCoverType) { RelativeSizeAxes = Axes.Both };
|
||||
|
@ -2,7 +2,6 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
@ -75,15 +74,29 @@ namespace osu.Game.Screens.OnlinePlay.Components
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Playlist.BindCollectionChanged(updateRange, true);
|
||||
DifficultyRange.BindValueChanged(_ => updateRange());
|
||||
Playlist.BindCollectionChanged((_, __) => updateRange(), true);
|
||||
}
|
||||
|
||||
private void updateRange(object sender, NotifyCollectionChangedEventArgs e)
|
||||
private void updateRange()
|
||||
{
|
||||
StarDifficulty minDifficulty;
|
||||
StarDifficulty maxDifficulty;
|
||||
|
||||
if (DifficultyRange.Value != null)
|
||||
{
|
||||
minDifficulty = new StarDifficulty(DifficultyRange.Value.Min, 0);
|
||||
maxDifficulty = new StarDifficulty(DifficultyRange.Value.Max, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// In multiplayer rooms, the beatmaps of playlist items will not be populated to a point this can be correct.
|
||||
// Either populating them via BeatmapLookupCache or polling the API for the room's DifficultyRange will be required.
|
||||
var orderedDifficulties = Playlist.Select(p => p.Beatmap).OrderBy(b => b.StarRating).ToArray();
|
||||
|
||||
StarDifficulty minDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[0].StarRating : 0, 0);
|
||||
StarDifficulty maxDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[^1].StarRating : 0, 0);
|
||||
minDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[0].StarRating : 0, 0);
|
||||
maxDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[^1].StarRating : 0, 0);
|
||||
}
|
||||
|
||||
minDisplay.Current.Value = minDifficulty;
|
||||
maxDisplay.Current.Value = maxDifficulty;
|
||||
|
@ -388,7 +388,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
SelectedItem.BindValueChanged(onSelectedItemChanged, true);
|
||||
CurrentPlaylistItem.BindValueChanged(onSelectedItemChanged, true);
|
||||
}
|
||||
|
||||
private CancellationTokenSource beatmapLookupCancellation;
|
||||
|
@ -1,7 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using Humanizer;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.LocalisationExtensions;
|
||||
@ -41,15 +41,22 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Playlist.BindCollectionChanged(updateCount, true);
|
||||
PlaylistItemStats.BindValueChanged(_ => updateCount());
|
||||
Playlist.BindCollectionChanged((_, __) => updateCount(), true);
|
||||
}
|
||||
|
||||
private void updateCount(object sender, NotifyCollectionChangedEventArgs e)
|
||||
private void updateCount()
|
||||
{
|
||||
int activeItems = Playlist.Count > 0 || PlaylistItemStats.Value == null
|
||||
// For now, use the playlist as the source of truth if it has any items.
|
||||
// This allows the count to display correctly on the room screen (after joining a room).
|
||||
? Playlist.Count(i => !i.Expired)
|
||||
: PlaylistItemStats.Value.CountActive;
|
||||
|
||||
count.Clear();
|
||||
count.AddText(Playlist.Count.ToLocalisableString(), s => s.Font = s.Font.With(weight: FontWeight.Bold));
|
||||
count.AddText(activeItems.ToLocalisableString(), s => s.Font = s.Font.With(weight: FontWeight.Bold));
|
||||
count.AddText(" ");
|
||||
count.AddText("Beatmap".ToQuantity(Playlist.Count, ShowQuantityAs.None));
|
||||
count.AddText("Beatmap".ToQuantity(activeItems, ShowQuantityAs.None));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -77,7 +77,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
{
|
||||
bool matchingFilter = true;
|
||||
|
||||
matchingFilter &= r.Room.Playlist.Count == 0 || criteria.Ruleset == null || r.Room.Playlist.Any(i => i.RulesetID == criteria.Ruleset.OnlineID);
|
||||
matchingFilter &= criteria.Ruleset == null || r.Room.PlaylistItemStats.Value?.RulesetIDs.Any(id => id == criteria.Ruleset.OnlineID) != false;
|
||||
|
||||
if (!string.IsNullOrEmpty(criteria.SearchString))
|
||||
matchingFilter &= r.FilterTerms.Any(term => term.Contains(criteria.SearchString, StringComparison.InvariantCultureIgnoreCase));
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
@ -15,10 +16,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
{
|
||||
public class GameplayChatDisplay : MatchChatDisplay, IKeyBindingHandler<GlobalAction>
|
||||
{
|
||||
[Resolved]
|
||||
[Resolved(CanBeNull = true)]
|
||||
[CanBeNull]
|
||||
private ILocalUserPlayInfo localUserInfo { get; set; }
|
||||
|
||||
private IBindable<bool> localUserPlaying = new Bindable<bool>();
|
||||
private readonly IBindable<bool> localUserPlaying = new Bindable<bool>();
|
||||
|
||||
public override bool PropagatePositionalInputSubTree => !localUserPlaying.Value;
|
||||
|
||||
@ -46,7 +48,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
localUserPlaying = localUserInfo.IsPlaying.GetBoundCopy();
|
||||
if (localUserInfo != null)
|
||||
localUserPlaying.BindTo(localUserInfo.IsPlaying);
|
||||
|
||||
localUserPlaying.BindValueChanged(playing =>
|
||||
{
|
||||
// for now let's never hold focus. this avoid misdirected gameplay keys entering chat.
|
||||
|
@ -343,7 +343,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
base.LoadComplete();
|
||||
|
||||
drawablePlaylist.Items.BindTo(Playlist);
|
||||
drawablePlaylist.SelectedItem.BindTo(SelectedItem);
|
||||
drawablePlaylist.SelectedItem.BindTo(CurrentPlaylistItem);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
@ -419,7 +419,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
if (text.StartsWith(not_found_prefix, StringComparison.Ordinal))
|
||||
{
|
||||
ErrorText.Text = "The selected beatmap is not available online.";
|
||||
SelectedItem.Value.MarkInvalid();
|
||||
CurrentPlaylistItem.Value.MarkInvalid();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -67,7 +67,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
SelectedItem.BindValueChanged(_ => updateState());
|
||||
CurrentPlaylistItem.BindValueChanged(_ => updateState());
|
||||
}
|
||||
|
||||
protected override void OnRoomUpdated()
|
||||
@ -111,7 +111,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
|
||||
bool enableButton =
|
||||
Room?.State == MultiplayerRoomState.Open
|
||||
&& SelectedItem.Value?.ID == Room.Settings.PlaylistItemId
|
||||
&& CurrentPlaylistItem.Value?.ID == Room.Settings.PlaylistItemId
|
||||
&& !Room.Playlist.Single(i => i.ID == Room.Settings.PlaylistItemId).Expired
|
||||
&& !operationInProgress.Value;
|
||||
|
||||
|
@ -52,14 +52,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist
|
||||
queueList = new MultiplayerQueueList
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
SelectedItem = { BindTarget = SelectedItem },
|
||||
SelectedItem = { BindTarget = CurrentPlaylistItem },
|
||||
RequestEdit = item => RequestEdit?.Invoke(item)
|
||||
},
|
||||
historyList = new MultiplayerHistoryList
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0,
|
||||
SelectedItem = { BindTarget = SelectedItem }
|
||||
SelectedItem = { BindTarget = CurrentPlaylistItem }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -449,7 +449,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
switch (client.LocalUser.State)
|
||||
{
|
||||
case MultiplayerUserState.Spectating:
|
||||
return new MultiSpectatorScreen(users.Take(PlayerGrid.MAX_PLAYERS).ToArray());
|
||||
return new MultiSpectatorScreen(Room, users.Take(PlayerGrid.MAX_PLAYERS).ToArray());
|
||||
|
||||
default:
|
||||
return new MultiplayerPlayerLoader(() => new MultiplayerPlayer(Room, SelectedItem.Value, users));
|
||||
|
@ -11,10 +11,13 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Online.Spectator;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Screens.Spectate;
|
||||
using osu.Game.Users;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
||||
{
|
||||
@ -34,6 +37,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
||||
/// </summary>
|
||||
public bool AllPlayersLoaded => instances.All(p => p?.PlayerLoaded == true);
|
||||
|
||||
protected override UserActivity InitialActivity => new UserActivity.SpectatingMultiplayerGame(Beatmap.Value.BeatmapInfo, Ruleset.Value);
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; }
|
||||
|
||||
@ -48,15 +53,18 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
||||
private PlayerArea currentAudioSource;
|
||||
private bool canStartMasterClock;
|
||||
|
||||
private readonly Room room;
|
||||
private readonly MultiplayerRoomUser[] users;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="MultiSpectatorScreen"/>.
|
||||
/// </summary>
|
||||
/// <param name="room">The room.</param>
|
||||
/// <param name="users">The players to spectate.</param>
|
||||
public MultiSpectatorScreen(MultiplayerRoomUser[] users)
|
||||
public MultiSpectatorScreen(Room room, MultiplayerRoomUser[] users)
|
||||
: base(users.Select(u => u.UserID).ToArray())
|
||||
{
|
||||
this.room = room;
|
||||
this.users = users;
|
||||
|
||||
instances = new PlayerArea[Users.Count];
|
||||
@ -65,7 +73,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Container leaderboardContainer;
|
||||
FillFlowContainer leaderboardFlow;
|
||||
Container scoreDisplayContainer;
|
||||
|
||||
masterClockContainer = CreateMasterGameplayClockContainer(Beatmap.Value);
|
||||
@ -97,10 +105,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
leaderboardContainer = new Container
|
||||
leaderboardFlow = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
AutoSizeAxes = Axes.X
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(5)
|
||||
},
|
||||
grid = new PlayerGrid { RelativeSizeAxes = Axes.Both }
|
||||
}
|
||||
@ -125,14 +136,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
||||
LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(scoreProcessor, users)
|
||||
{
|
||||
Expanded = { Value = true },
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
}, l =>
|
||||
{
|
||||
foreach (var instance in instances)
|
||||
leaderboard.AddClock(instance.UserId, instance.GameplayClock);
|
||||
|
||||
leaderboardContainer.Add(leaderboard);
|
||||
leaderboardFlow.Insert(0, leaderboard);
|
||||
|
||||
if (leaderboard.TeamScores.Count == 2)
|
||||
{
|
||||
@ -143,6 +152,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
||||
}, scoreDisplayContainer.Add);
|
||||
}
|
||||
});
|
||||
|
||||
LoadComponentAsync(new GameplayChatDisplay(room)
|
||||
{
|
||||
Expanded = { Value = true },
|
||||
}, chat => leaderboardFlow.Insert(1, chat));
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
|
@ -32,9 +32,22 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
[Resolved(typeof(Room))]
|
||||
protected Bindable<MatchType> Type { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The currently selected item in the <see cref="RoomSubScreen"/>, or the current item from <see cref="Playlist"/>
|
||||
/// if this <see cref="OnlinePlayComposite"/> is not within a <see cref="RoomSubScreen"/>.
|
||||
/// </summary>
|
||||
[Resolved(typeof(Room))]
|
||||
protected Bindable<PlaylistItem> CurrentPlaylistItem { get; private set; }
|
||||
|
||||
[Resolved(typeof(Room))]
|
||||
protected Bindable<Room.RoomPlaylistItemStats> PlaylistItemStats { get; private set; }
|
||||
|
||||
[Resolved(typeof(Room))]
|
||||
protected BindableList<PlaylistItem> Playlist { get; private set; }
|
||||
|
||||
[Resolved(typeof(Room))]
|
||||
protected Bindable<Room.RoomDifficultyRange> DifficultyRange { get; private set; }
|
||||
|
||||
[Resolved(typeof(Room))]
|
||||
protected Bindable<RoomCategory> Category { get; private set; }
|
||||
|
||||
@ -71,12 +84,6 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
[Resolved(CanBeNull = true)]
|
||||
private IBindable<PlaylistItem> subScreenSelectedItem { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The currently selected item in the <see cref="RoomSubScreen"/>, or the current item from <see cref="Playlist"/>
|
||||
/// if this <see cref="OnlinePlayComposite"/> is not within a <see cref="RoomSubScreen"/>.
|
||||
/// </summary>
|
||||
protected readonly Bindable<PlaylistItem> SelectedItem = new Bindable<PlaylistItem>();
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
@ -85,9 +92,13 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
Playlist.BindCollectionChanged((_, __) => UpdateSelectedItem(), true);
|
||||
}
|
||||
|
||||
protected virtual void UpdateSelectedItem()
|
||||
=> SelectedItem.Value = RoomID.Value == null || subScreenSelectedItem == null
|
||||
? Playlist.GetCurrentItem()
|
||||
: subScreenSelectedItem.Value;
|
||||
protected void UpdateSelectedItem()
|
||||
{
|
||||
// null room ID means this is a room in the process of being created.
|
||||
if (RoomID.Value == null)
|
||||
CurrentPlaylistItem.Value = Playlist.GetCurrentItem();
|
||||
else if (subScreenSelectedItem != null)
|
||||
CurrentPlaylistItem.Value = subScreenSelectedItem.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Scoring;
|
||||
@ -40,8 +39,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
if (ruleset.Value.OnlineID != PlaylistItem.RulesetID)
|
||||
throw new InvalidOperationException("Current Ruleset does not match PlaylistItem's Ruleset");
|
||||
|
||||
var localMods = Mods.Value.Select(m => new APIMod(m)).ToArray();
|
||||
if (!PlaylistItem.RequiredMods.All(m => localMods.Any(m.Equals)))
|
||||
var requiredLocalMods = PlaylistItem.RequiredMods.Select(m => m.ToMod(GameplayState.Ruleset));
|
||||
if (!requiredLocalMods.All(m => Mods.Value.Any(m.Equals)))
|
||||
throw new InvalidOperationException("Current Mods do not match PlaylistItem's RequiredMods");
|
||||
}
|
||||
|
||||
|
@ -43,6 +43,11 @@ namespace osu.Game.Tests.Visual.OnlinePlay
|
||||
|
||||
if (ruleset != null)
|
||||
{
|
||||
room.PlaylistItemStats.Value = new Room.RoomPlaylistItemStats
|
||||
{
|
||||
RulesetIDs = new[] { ruleset.OnlineID },
|
||||
};
|
||||
|
||||
room.Playlist.Add(new PlaylistItem(new BeatmapInfo { Metadata = new BeatmapMetadata() })
|
||||
{
|
||||
RulesetID = ruleset.OnlineID,
|
||||
|
@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
var dllStore = new DllResourceStore(DynamicCompilationOriginal.GetType().Assembly);
|
||||
var dllStore = new DllResourceStore(GetType().Assembly);
|
||||
|
||||
metricsSkin = new TestLegacySkin(new SkinInfo { Name = "metrics-skin" }, new NamespacedResourceStore<byte[]>(dllStore, "Resources/metrics_skin"), this, true);
|
||||
defaultSkin = new DefaultLegacySkin(this);
|
||||
|
@ -50,6 +50,16 @@ namespace osu.Game.Users
|
||||
public override string Status => $@"{base.Status} with others";
|
||||
}
|
||||
|
||||
public class SpectatingMultiplayerGame : InGame
|
||||
{
|
||||
public SpectatingMultiplayerGame(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset)
|
||||
: base(beatmapInfo, ruleset)
|
||||
{
|
||||
}
|
||||
|
||||
public override string Status => $"Watching others {base.Status.ToLowerInvariant()}";
|
||||
}
|
||||
|
||||
public class InPlaylistGame : InGame
|
||||
{
|
||||
public InPlaylistGame(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset)
|
||||
|
@ -36,7 +36,7 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Realm" Version="10.9.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2022.217.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2022.223.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.211.0" />
|
||||
<PackageReference Include="Sentry" Version="3.14.0" />
|
||||
<PackageReference Include="SharpCompress" Version="0.30.1" />
|
||||
|
@ -60,7 +60,7 @@
|
||||
<Reference Include="System.Net.Http" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2022.217.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2022.223.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.211.0" />
|
||||
</ItemGroup>
|
||||
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net6.0) -->
|
||||
@ -83,7 +83,7 @@
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.14" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="5.0.14" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2022.217.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2022.223.0" />
|
||||
<PackageReference Include="SharpCompress" Version="0.30.1" />
|
||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||
|
Loading…
Reference in New Issue
Block a user