mirror of
https://github.com/ppy/osu.git
synced 2025-01-12 20:22:55 +08:00
Merge branch 'master' into stable-slider-followcircle-anims
This commit is contained in:
commit
23a0e25c8c
@ -52,7 +52,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.702.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.707.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.713.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. -->
|
||||
|
175
osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs
Normal file
175
osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs
Normal file
@ -0,0 +1,175 @@
|
||||
// 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 NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Replays;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
{
|
||||
public class TestSceneOsuModSingleTap : OsuModTestScene
|
||||
{
|
||||
[Test]
|
||||
public void TestInputSingular() => CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new OsuModSingleTap(),
|
||||
PassCondition = () => Player.ScoreProcessor.Combo.Value == 2,
|
||||
Autoplay = false,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new HitCircle
|
||||
{
|
||||
StartTime = 500,
|
||||
Position = new Vector2(100),
|
||||
},
|
||||
new HitCircle
|
||||
{
|
||||
StartTime = 1000,
|
||||
Position = new Vector2(200, 100),
|
||||
},
|
||||
new HitCircle
|
||||
{
|
||||
StartTime = 1500,
|
||||
Position = new Vector2(300, 100),
|
||||
},
|
||||
new HitCircle
|
||||
{
|
||||
StartTime = 2000,
|
||||
Position = new Vector2(400, 100),
|
||||
},
|
||||
},
|
||||
},
|
||||
ReplayFrames = new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton),
|
||||
new OsuReplayFrame(501, new Vector2(100)),
|
||||
new OsuReplayFrame(1000, new Vector2(200, 100), OsuAction.LeftButton),
|
||||
}
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestInputAlternating() => CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new OsuModSingleTap(),
|
||||
PassCondition = () => Player.ScoreProcessor.Combo.Value == 0 && Player.ScoreProcessor.HighestCombo.Value == 1,
|
||||
Autoplay = false,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new HitCircle
|
||||
{
|
||||
StartTime = 500,
|
||||
Position = new Vector2(100),
|
||||
},
|
||||
new HitCircle
|
||||
{
|
||||
StartTime = 1000,
|
||||
Position = new Vector2(200, 100),
|
||||
},
|
||||
},
|
||||
},
|
||||
ReplayFrames = new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton),
|
||||
new OsuReplayFrame(501, new Vector2(100)),
|
||||
new OsuReplayFrame(1000, new Vector2(200, 100), OsuAction.RightButton),
|
||||
new OsuReplayFrame(1001, new Vector2(200, 100)),
|
||||
new OsuReplayFrame(1500, new Vector2(300, 100), OsuAction.LeftButton),
|
||||
new OsuReplayFrame(1501, new Vector2(300, 100)),
|
||||
new OsuReplayFrame(2000, new Vector2(400, 100), OsuAction.RightButton),
|
||||
new OsuReplayFrame(2001, new Vector2(400, 100)),
|
||||
}
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Ensures singletapping is reset before the first hitobject after intro.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestInputAlternatingAtIntro() => CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new OsuModSingleTap(),
|
||||
PassCondition = () => Player.ScoreProcessor.Combo.Value == 1,
|
||||
Autoplay = false,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new HitCircle
|
||||
{
|
||||
StartTime = 1000,
|
||||
Position = new Vector2(100),
|
||||
},
|
||||
},
|
||||
},
|
||||
ReplayFrames = new List<ReplayFrame>
|
||||
{
|
||||
// first press during intro.
|
||||
new OsuReplayFrame(500, new Vector2(200), OsuAction.LeftButton),
|
||||
new OsuReplayFrame(501, new Vector2(200)),
|
||||
// press different key at hitobject and ensure it has been hit.
|
||||
new OsuReplayFrame(1000, new Vector2(100), OsuAction.RightButton),
|
||||
}
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Ensures singletapping is reset before the first hitobject after a break.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestInputAlternatingWithBreak() => CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new OsuModSingleTap(),
|
||||
PassCondition = () => Player.ScoreProcessor.Combo.Value == 0 && Player.ScoreProcessor.HighestCombo.Value == 2,
|
||||
Autoplay = false,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
Breaks = new List<BreakPeriod>
|
||||
{
|
||||
new BreakPeriod(500, 2000),
|
||||
},
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new HitCircle
|
||||
{
|
||||
StartTime = 500,
|
||||
Position = new Vector2(100),
|
||||
},
|
||||
new HitCircle
|
||||
{
|
||||
StartTime = 2500,
|
||||
Position = new Vector2(500, 100),
|
||||
},
|
||||
new HitCircle
|
||||
{
|
||||
StartTime = 3000,
|
||||
Position = new Vector2(500, 100),
|
||||
},
|
||||
}
|
||||
},
|
||||
ReplayFrames = new List<ReplayFrame>
|
||||
{
|
||||
// first press to start singletap lock.
|
||||
new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton),
|
||||
new OsuReplayFrame(501, new Vector2(100)),
|
||||
// press different key after break but before hit object.
|
||||
new OsuReplayFrame(2250, new Vector2(300, 100), OsuAction.RightButton),
|
||||
new OsuReplayFrame(2251, new Vector2(300, 100)),
|
||||
// press same key at second hitobject and ensure it has been hit.
|
||||
new OsuReplayFrame(2500, new Vector2(500, 100), OsuAction.LeftButton),
|
||||
new OsuReplayFrame(2501, new Vector2(500, 100)),
|
||||
// press different key at third hitobject and ensure it has been missed.
|
||||
new OsuReplayFrame(3000, new Vector2(500, 100), OsuAction.RightButton),
|
||||
new OsuReplayFrame(3001, new Vector2(500, 100)),
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
114
osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs
Normal file
114
osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs
Normal file
@ -0,0 +1,114 @@
|
||||
// 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.Graphics;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public abstract class InputBlockingMod : Mod, IApplicableToDrawableRuleset<OsuHitObject>
|
||||
{
|
||||
public override double ScoreMultiplier => 1.0;
|
||||
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModRelax), typeof(OsuModCinema) };
|
||||
public override ModType Type => ModType.Conversion;
|
||||
|
||||
private const double flash_duration = 1000;
|
||||
|
||||
private DrawableRuleset<OsuHitObject> ruleset = null!;
|
||||
|
||||
protected OsuAction? LastAcceptedAction { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// A tracker for periods where alternate should not be forced (i.e. non-gameplay periods).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is different from <see cref="Player.IsBreakTime"/> in that the periods here end strictly at the first object after the break, rather than the break's end time.
|
||||
/// </remarks>
|
||||
private PeriodTracker nonGameplayPeriods = null!;
|
||||
|
||||
private IFrameStableClock gameplayClock = null!;
|
||||
|
||||
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
||||
{
|
||||
ruleset = drawableRuleset;
|
||||
drawableRuleset.KeyBindingInputManager.Add(new InputInterceptor(this));
|
||||
|
||||
var periods = new List<Period>();
|
||||
|
||||
if (drawableRuleset.Objects.Any())
|
||||
{
|
||||
periods.Add(new Period(int.MinValue, getValidJudgementTime(ruleset.Objects.First()) - 1));
|
||||
|
||||
foreach (BreakPeriod b in drawableRuleset.Beatmap.Breaks)
|
||||
periods.Add(new Period(b.StartTime, getValidJudgementTime(ruleset.Objects.First(h => h.StartTime >= b.EndTime)) - 1));
|
||||
|
||||
static double getValidJudgementTime(HitObject hitObject) => hitObject.StartTime - hitObject.HitWindows.WindowFor(HitResult.Meh);
|
||||
}
|
||||
|
||||
nonGameplayPeriods = new PeriodTracker(periods);
|
||||
|
||||
gameplayClock = drawableRuleset.FrameStableClock;
|
||||
}
|
||||
|
||||
protected abstract bool CheckValidNewAction(OsuAction action);
|
||||
|
||||
private bool checkCorrectAction(OsuAction action)
|
||||
{
|
||||
if (nonGameplayPeriods.IsInAny(gameplayClock.CurrentTime))
|
||||
{
|
||||
LastAcceptedAction = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (action)
|
||||
{
|
||||
case OsuAction.LeftButton:
|
||||
case OsuAction.RightButton:
|
||||
break;
|
||||
|
||||
// Any action which is not left or right button should be ignored.
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
|
||||
if (CheckValidNewAction(action))
|
||||
{
|
||||
LastAcceptedAction = action;
|
||||
return true;
|
||||
}
|
||||
|
||||
ruleset.Cursor.FlashColour(Colour4.Red, flash_duration, Easing.OutQuint);
|
||||
return false;
|
||||
}
|
||||
|
||||
private class InputInterceptor : Component, IKeyBindingHandler<OsuAction>
|
||||
{
|
||||
private readonly InputBlockingMod mod;
|
||||
|
||||
public InputInterceptor(InputBlockingMod mod)
|
||||
{
|
||||
this.mod = mod;
|
||||
}
|
||||
|
||||
public bool OnPressed(KeyBindingPressEvent<OsuAction> e)
|
||||
// if the pressed action is incorrect, block it from reaching gameplay.
|
||||
=> !mod.checkCorrectAction(e.Action);
|
||||
|
||||
public void OnReleased(KeyBindingReleaseEvent<OsuAction> e)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,119 +1,20 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public class OsuModAlternate : Mod, IApplicableToDrawableRuleset<OsuHitObject>
|
||||
public class OsuModAlternate : InputBlockingMod
|
||||
{
|
||||
public override string Name => @"Alternate";
|
||||
public override string Acronym => @"AL";
|
||||
public override string Description => @"Don't use the same key twice in a row!";
|
||||
public override double ScoreMultiplier => 1.0;
|
||||
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModRelax) };
|
||||
public override ModType Type => ModType.Conversion;
|
||||
public override IconUsage? Icon => FontAwesome.Solid.Keyboard;
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModSingleTap) }).ToArray();
|
||||
|
||||
private const double flash_duration = 1000;
|
||||
|
||||
/// <summary>
|
||||
/// A tracker for periods where alternate should not be forced (i.e. non-gameplay periods).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is different from <see cref="Player.IsBreakTime"/> in that the periods here end strictly at the first object after the break, rather than the break's end time.
|
||||
/// </remarks>
|
||||
private PeriodTracker nonGameplayPeriods;
|
||||
|
||||
private OsuAction? lastActionPressed;
|
||||
private DrawableRuleset<OsuHitObject> ruleset;
|
||||
|
||||
private IFrameStableClock gameplayClock;
|
||||
|
||||
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
||||
{
|
||||
ruleset = drawableRuleset;
|
||||
drawableRuleset.KeyBindingInputManager.Add(new InputInterceptor(this));
|
||||
|
||||
var periods = new List<Period>();
|
||||
|
||||
if (drawableRuleset.Objects.Any())
|
||||
{
|
||||
periods.Add(new Period(int.MinValue, getValidJudgementTime(ruleset.Objects.First()) - 1));
|
||||
|
||||
foreach (BreakPeriod b in drawableRuleset.Beatmap.Breaks)
|
||||
periods.Add(new Period(b.StartTime, getValidJudgementTime(ruleset.Objects.First(h => h.StartTime >= b.EndTime)) - 1));
|
||||
|
||||
static double getValidJudgementTime(HitObject hitObject) => hitObject.StartTime - hitObject.HitWindows.WindowFor(HitResult.Meh);
|
||||
}
|
||||
|
||||
nonGameplayPeriods = new PeriodTracker(periods);
|
||||
|
||||
gameplayClock = drawableRuleset.FrameStableClock;
|
||||
}
|
||||
|
||||
private bool checkCorrectAction(OsuAction action)
|
||||
{
|
||||
if (nonGameplayPeriods.IsInAny(gameplayClock.CurrentTime))
|
||||
{
|
||||
lastActionPressed = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (action)
|
||||
{
|
||||
case OsuAction.LeftButton:
|
||||
case OsuAction.RightButton:
|
||||
break;
|
||||
|
||||
// Any action which is not left or right button should be ignored.
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
|
||||
if (lastActionPressed != action)
|
||||
{
|
||||
// User alternated correctly.
|
||||
lastActionPressed = action;
|
||||
return true;
|
||||
}
|
||||
|
||||
ruleset.Cursor.FlashColour(Colour4.Red, flash_duration, Easing.OutQuint);
|
||||
return false;
|
||||
}
|
||||
|
||||
private class InputInterceptor : Component, IKeyBindingHandler<OsuAction>
|
||||
{
|
||||
private readonly OsuModAlternate mod;
|
||||
|
||||
public InputInterceptor(OsuModAlternate mod)
|
||||
{
|
||||
this.mod = mod;
|
||||
}
|
||||
|
||||
public bool OnPressed(KeyBindingPressEvent<OsuAction> e)
|
||||
// if the pressed action is incorrect, block it from reaching gameplay.
|
||||
=> !mod.checkCorrectAction(e.Action);
|
||||
|
||||
public void OnReleased(KeyBindingReleaseEvent<OsuAction> e)
|
||||
{
|
||||
}
|
||||
}
|
||||
protected override bool CheckValidNewAction(OsuAction action) => LastAcceptedAction != action;
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public class OsuModAutoplay : ModAutoplay
|
||||
{
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModMagnetised), typeof(OsuModRepel), typeof(OsuModAutopilot), typeof(OsuModSpunOut), typeof(OsuModAlternate) }).ToArray();
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModMagnetised), typeof(OsuModRepel), typeof(OsuModAutopilot), typeof(OsuModSpunOut), typeof(OsuModAlternate), typeof(OsuModSingleTap) }).ToArray();
|
||||
|
||||
public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList<Mod> mods)
|
||||
=> new ModReplayData(new OsuAutoGenerator(beatmap, mods).Generate(), new ModCreatedUser { Username = "Autoplay" });
|
||||
|
@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public class OsuModCinema : ModCinema<OsuHitObject>
|
||||
{
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModMagnetised), typeof(OsuModAutopilot), typeof(OsuModSpunOut), typeof(OsuModAlternate) }).ToArray();
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModMagnetised), typeof(OsuModAutopilot), typeof(OsuModSpunOut), typeof(OsuModAlternate), typeof(OsuModSingleTap) }).ToArray();
|
||||
|
||||
public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList<Mod> mods)
|
||||
=> new ModReplayData(new OsuAutoGenerator(beatmap, mods).Generate(), new ModCreatedUser { Username = "Autoplay" });
|
||||
|
@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public class OsuModRelax : ModRelax, IUpdatableByPlayfield, IApplicableToDrawableRuleset<OsuHitObject>, IApplicableToPlayer
|
||||
{
|
||||
public override string Description => @"You don't need to click. Give your clicking/tapping fingers a break from the heat of things.";
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot), typeof(OsuModMagnetised), typeof(OsuModAlternate) }).ToArray();
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot), typeof(OsuModMagnetised), typeof(OsuModAlternate), typeof(OsuModSingleTap) }).ToArray();
|
||||
|
||||
/// <summary>
|
||||
/// How early before a hitobject's start time to trigger a hit.
|
||||
|
18
osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs
Normal file
18
osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs
Normal file
@ -0,0 +1,18 @@
|
||||
// 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;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public class OsuModSingleTap : InputBlockingMod
|
||||
{
|
||||
public override string Name => @"Single Tap";
|
||||
public override string Acronym => @"ST";
|
||||
public override string Description => @"You must only use one key!";
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAlternate) }).ToArray();
|
||||
|
||||
protected override bool CheckValidNewAction(OsuAction action) => LastAcceptedAction == null || LastAcceptedAction == action;
|
||||
}
|
||||
}
|
@ -172,7 +172,7 @@ namespace osu.Game.Rulesets.Osu
|
||||
new OsuModClassic(),
|
||||
new OsuModRandom(),
|
||||
new OsuModMirror(),
|
||||
new OsuModAlternate(),
|
||||
new MultiMod(new OsuModAlternate(), new OsuModSingleTap())
|
||||
};
|
||||
|
||||
case ModType.Automation:
|
||||
|
@ -18,6 +18,7 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Models;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Rooms;
|
||||
@ -195,12 +196,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[Test]
|
||||
public void TestDownloadButtonHiddenWhenBeatmapExists()
|
||||
{
|
||||
var beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo;
|
||||
Live<BeatmapSetInfo> imported = null;
|
||||
|
||||
Debug.Assert(beatmap.BeatmapSet != null);
|
||||
AddStep("import beatmap", () =>
|
||||
{
|
||||
var beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo;
|
||||
|
||||
AddStep("import beatmap", () => imported = manager.Import(beatmap.BeatmapSet));
|
||||
Debug.Assert(beatmap.BeatmapSet != null);
|
||||
imported = manager.Import(beatmap.BeatmapSet);
|
||||
});
|
||||
|
||||
createPlaylistWithBeatmaps(() => imported.PerformRead(s => s.Beatmaps.Detach()));
|
||||
|
||||
@ -245,40 +249,35 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[Test]
|
||||
public void TestExpiredItems()
|
||||
{
|
||||
AddStep("create playlist", () =>
|
||||
createPlaylist(p =>
|
||||
{
|
||||
Child = playlist = new TestPlaylist
|
||||
p.Items.Clear();
|
||||
p.Items.AddRange(new[]
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(500, 300),
|
||||
Items =
|
||||
new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
|
||||
{
|
||||
new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
|
||||
ID = 0,
|
||||
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
|
||||
Expired = true,
|
||||
RequiredMods = new[]
|
||||
{
|
||||
ID = 0,
|
||||
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
|
||||
Expired = true,
|
||||
RequiredMods = new[]
|
||||
{
|
||||
new APIMod(new OsuModHardRock()),
|
||||
new APIMod(new OsuModDoubleTime()),
|
||||
new APIMod(new OsuModAutoplay())
|
||||
}
|
||||
},
|
||||
new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
|
||||
new APIMod(new OsuModHardRock()),
|
||||
new APIMod(new OsuModDoubleTime()),
|
||||
new APIMod(new OsuModAutoplay())
|
||||
}
|
||||
},
|
||||
new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
|
||||
{
|
||||
ID = 1,
|
||||
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
|
||||
RequiredMods = new[]
|
||||
{
|
||||
ID = 1,
|
||||
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
|
||||
RequiredMods = new[]
|
||||
{
|
||||
new APIMod(new OsuModHardRock()),
|
||||
new APIMod(new OsuModDoubleTime()),
|
||||
new APIMod(new OsuModAutoplay())
|
||||
}
|
||||
new APIMod(new OsuModHardRock()),
|
||||
new APIMod(new OsuModDoubleTime()),
|
||||
new APIMod(new OsuModAutoplay())
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded));
|
||||
@ -321,19 +320,44 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
=> AddAssert($"delete button {index} {(visible ? "is" : "is not")} visible",
|
||||
() => (playlist.ChildrenOfType<DrawableRoomPlaylistItem.PlaylistRemoveButton>().ElementAt(2 + index * 2).Alpha > 0) == visible);
|
||||
|
||||
private void createPlaylistWithBeatmaps(Func<IEnumerable<IBeatmapInfo>> beatmaps) => createPlaylist(p =>
|
||||
{
|
||||
int index = 0;
|
||||
|
||||
p.Items.Clear();
|
||||
|
||||
foreach (var b in beatmaps())
|
||||
{
|
||||
p.Items.Add(new PlaylistItem(b)
|
||||
{
|
||||
ID = index++,
|
||||
OwnerID = 2,
|
||||
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
|
||||
RequiredMods = new[]
|
||||
{
|
||||
new APIMod(new OsuModHardRock()),
|
||||
new APIMod(new OsuModDoubleTime()),
|
||||
new APIMod(new OsuModAutoplay())
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
private void createPlaylist(Action<TestPlaylist> setupPlaylist = null)
|
||||
{
|
||||
AddStep("create playlist", () =>
|
||||
{
|
||||
Child = playlist = new TestPlaylist
|
||||
Child = new OsuContextMenuContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(500, 300)
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = playlist = new TestPlaylist
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(500, 300)
|
||||
}
|
||||
};
|
||||
|
||||
setupPlaylist?.Invoke(playlist);
|
||||
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
playlist.Items.Add(new PlaylistItem(i % 2 == 1
|
||||
@ -360,39 +384,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded));
|
||||
}
|
||||
|
||||
private void createPlaylistWithBeatmaps(Func<IEnumerable<IBeatmapInfo>> beatmaps)
|
||||
{
|
||||
AddStep("create playlist", () =>
|
||||
{
|
||||
Child = playlist = new TestPlaylist
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(500, 300)
|
||||
};
|
||||
|
||||
int index = 0;
|
||||
|
||||
foreach (var b in beatmaps())
|
||||
{
|
||||
playlist.Items.Add(new PlaylistItem(b)
|
||||
{
|
||||
ID = index++,
|
||||
OwnerID = 2,
|
||||
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
|
||||
RequiredMods = new[]
|
||||
{
|
||||
new APIMod(new OsuModHardRock()),
|
||||
new APIMod(new OsuModDoubleTime()),
|
||||
new APIMod(new OsuModAutoplay())
|
||||
}
|
||||
});
|
||||
}
|
||||
setupPlaylist?.Invoke(playlist);
|
||||
});
|
||||
|
||||
AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded));
|
||||
|
@ -9,6 +9,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
@ -368,12 +369,16 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
ParticipantsList? participantsList = null;
|
||||
|
||||
AddStep("create new list", () => Child = participantsList = new ParticipantsList
|
||||
AddStep("create new list", () => Child = new OsuContextMenuContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Size = new Vector2(380, 0.7f)
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = participantsList = new ParticipantsList
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Size = new Vector2(380, 0.7f)
|
||||
}
|
||||
});
|
||||
|
||||
AddUntilStep("wait for list to load", () => participantsList?.IsLoaded == true);
|
||||
|
@ -18,6 +18,7 @@ using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.BeatmapSet.Scores;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Users;
|
||||
using osuTK.Graphics;
|
||||
@ -146,12 +147,12 @@ namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
var scores = new APIScoresCollection
|
||||
{
|
||||
Scores = new List<APIScore>
|
||||
Scores = new List<SoloScoreInfo>
|
||||
{
|
||||
new APIScore
|
||||
new SoloScoreInfo
|
||||
{
|
||||
Date = DateTimeOffset.Now,
|
||||
OnlineID = onlineID++,
|
||||
EndedAt = DateTimeOffset.Now,
|
||||
ID = onlineID++,
|
||||
User = new APIUser
|
||||
{
|
||||
Id = 6602580,
|
||||
@ -175,10 +176,10 @@ namespace osu.Game.Tests.Visual.Online
|
||||
TotalScore = 1234567890,
|
||||
Accuracy = 1,
|
||||
},
|
||||
new APIScore
|
||||
new SoloScoreInfo
|
||||
{
|
||||
Date = DateTimeOffset.Now,
|
||||
OnlineID = onlineID++,
|
||||
EndedAt = DateTimeOffset.Now,
|
||||
ID = onlineID++,
|
||||
User = new APIUser
|
||||
{
|
||||
Id = 4608074,
|
||||
@ -201,10 +202,10 @@ namespace osu.Game.Tests.Visual.Online
|
||||
TotalScore = 1234789,
|
||||
Accuracy = 0.9997,
|
||||
},
|
||||
new APIScore
|
||||
new SoloScoreInfo
|
||||
{
|
||||
Date = DateTimeOffset.Now,
|
||||
OnlineID = onlineID++,
|
||||
EndedAt = DateTimeOffset.Now,
|
||||
ID = onlineID++,
|
||||
User = new APIUser
|
||||
{
|
||||
Id = 1014222,
|
||||
@ -226,10 +227,10 @@ namespace osu.Game.Tests.Visual.Online
|
||||
TotalScore = 12345678,
|
||||
Accuracy = 0.9854,
|
||||
},
|
||||
new APIScore
|
||||
new SoloScoreInfo
|
||||
{
|
||||
Date = DateTimeOffset.Now,
|
||||
OnlineID = onlineID++,
|
||||
EndedAt = DateTimeOffset.Now,
|
||||
ID = onlineID++,
|
||||
User = new APIUser
|
||||
{
|
||||
Id = 1541390,
|
||||
@ -250,10 +251,10 @@ namespace osu.Game.Tests.Visual.Online
|
||||
TotalScore = 1234567,
|
||||
Accuracy = 0.8765,
|
||||
},
|
||||
new APIScore
|
||||
new SoloScoreInfo
|
||||
{
|
||||
Date = DateTimeOffset.Now,
|
||||
OnlineID = onlineID++,
|
||||
EndedAt = DateTimeOffset.Now,
|
||||
ID = onlineID++,
|
||||
User = new APIUser
|
||||
{
|
||||
Id = 7151382,
|
||||
@ -275,12 +276,12 @@ namespace osu.Game.Tests.Visual.Online
|
||||
|
||||
foreach (var s in scores.Scores)
|
||||
{
|
||||
s.Statistics = new Dictionary<string, int>
|
||||
s.Statistics = new Dictionary<HitResult, int>
|
||||
{
|
||||
{ "count_300", RNG.Next(2000) },
|
||||
{ "count_100", RNG.Next(2000) },
|
||||
{ "count_50", RNG.Next(2000) },
|
||||
{ "count_miss", RNG.Next(2000) }
|
||||
{ HitResult.Great, RNG.Next(2000) },
|
||||
{ HitResult.Ok, RNG.Next(2000) },
|
||||
{ HitResult.Meh, RNG.Next(2000) },
|
||||
{ HitResult.Miss, RNG.Next(2000) }
|
||||
};
|
||||
}
|
||||
|
||||
@ -289,10 +290,10 @@ namespace osu.Game.Tests.Visual.Online
|
||||
|
||||
private APIScoreWithPosition createUserBest() => new APIScoreWithPosition
|
||||
{
|
||||
Score = new APIScore
|
||||
Score = new SoloScoreInfo
|
||||
{
|
||||
Date = DateTimeOffset.Now,
|
||||
OnlineID = onlineID++,
|
||||
EndedAt = DateTimeOffset.Now,
|
||||
ID = onlineID++,
|
||||
User = new APIUser
|
||||
{
|
||||
Id = 7151382,
|
||||
|
101
osu.Game.Tournament/SaveChangesOverlay.cs
Normal file
101
osu.Game.Tournament/SaveChangesOverlay.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.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tournament
|
||||
{
|
||||
internal class SaveChangesOverlay : CompositeDrawable
|
||||
{
|
||||
[Resolved]
|
||||
private TournamentGame tournamentGame { get; set; } = null!;
|
||||
|
||||
private string? lastSerialisedLadder;
|
||||
private readonly TourneyButton saveChangesButton;
|
||||
|
||||
public SaveChangesOverlay()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
InternalChild = new Container
|
||||
{
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
Position = new Vector2(5),
|
||||
CornerRadius = 10,
|
||||
Masking = true,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Colour = OsuColour.Gray(0.2f),
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
saveChangesButton = new TourneyButton
|
||||
{
|
||||
Text = "Save Changes",
|
||||
Width = 140,
|
||||
Height = 50,
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Top = 10,
|
||||
Left = 10,
|
||||
},
|
||||
Margin = new MarginPadding
|
||||
{
|
||||
Right = 10,
|
||||
Bottom = 10,
|
||||
},
|
||||
Action = saveChanges,
|
||||
// Enabled = { Value = false },
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
scheduleNextCheck();
|
||||
}
|
||||
|
||||
private async Task checkForChanges()
|
||||
{
|
||||
string serialisedLadder = await Task.Run(() => tournamentGame.GetSerialisedLadder());
|
||||
|
||||
// If a save hasn't been triggered by the user yet, populate the initial value
|
||||
lastSerialisedLadder ??= serialisedLadder;
|
||||
|
||||
if (lastSerialisedLadder != serialisedLadder && !saveChangesButton.Enabled.Value)
|
||||
{
|
||||
saveChangesButton.Enabled.Value = true;
|
||||
saveChangesButton.Background
|
||||
.FadeColour(saveChangesButton.BackgroundColour.Lighten(0.5f), 500, Easing.In).Then()
|
||||
.FadeColour(saveChangesButton.BackgroundColour, 500, Easing.Out)
|
||||
.Loop();
|
||||
}
|
||||
|
||||
scheduleNextCheck();
|
||||
}
|
||||
|
||||
private void scheduleNextCheck() => Scheduler.AddDelayed(() => checkForChanges().FireAndForget(), 1000);
|
||||
|
||||
private void saveChanges()
|
||||
{
|
||||
tournamentGame.SaveChanges();
|
||||
lastSerialisedLadder = tournamentGame.GetSerialisedLadder();
|
||||
|
||||
saveChangesButton.Enabled.Value = false;
|
||||
saveChangesButton.Background.FadeColour(saveChangesButton.BackgroundColour, 500);
|
||||
}
|
||||
}
|
||||
}
|
@ -11,8 +11,6 @@ using osu.Framework.Configuration;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Handlers.Mouse;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
@ -20,11 +18,11 @@ using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Tournament.Models;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Tournament
|
||||
{
|
||||
[Cached]
|
||||
public class TournamentGame : TournamentGameBase
|
||||
{
|
||||
public static ColourInfo GetTeamColour(TeamColour teamColour) => teamColour == TeamColour.Red ? COLOUR_RED : COLOUR_BLUE;
|
||||
@ -78,40 +76,9 @@ namespace osu.Game.Tournament
|
||||
|
||||
LoadComponentsAsync(new[]
|
||||
{
|
||||
new Container
|
||||
new SaveChangesOverlay
|
||||
{
|
||||
CornerRadius = 10,
|
||||
Depth = float.MinValue,
|
||||
Position = new Vector2(5),
|
||||
Masking = true,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Colour = OsuColour.Gray(0.2f),
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new TourneyButton
|
||||
{
|
||||
Text = "Save Changes",
|
||||
Width = 140,
|
||||
Height = 50,
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Top = 10,
|
||||
Left = 10,
|
||||
},
|
||||
Margin = new MarginPadding
|
||||
{
|
||||
Right = 10,
|
||||
Bottom = 10,
|
||||
},
|
||||
Action = SaveChanges,
|
||||
},
|
||||
}
|
||||
},
|
||||
heightWarning = new WarningBox("Please make the window wider")
|
||||
{
|
||||
|
@ -295,7 +295,7 @@ namespace osu.Game.Tournament
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void SaveChanges()
|
||||
public void SaveChanges()
|
||||
{
|
||||
if (!bracketLoadTaskCompletionSource.Task.IsCompletedSuccessfully)
|
||||
{
|
||||
@ -311,7 +311,16 @@ namespace osu.Game.Tournament
|
||||
.ToList();
|
||||
|
||||
// Serialise before opening stream for writing, so if there's a failure it will leave the file in the previous state.
|
||||
string serialisedLadder = JsonConvert.SerializeObject(ladder,
|
||||
string serialisedLadder = GetSerialisedLadder();
|
||||
|
||||
using (var stream = storage.CreateFileSafely(BRACKET_FILENAME))
|
||||
using (var sw = new StreamWriter(stream))
|
||||
sw.Write(serialisedLadder);
|
||||
}
|
||||
|
||||
public string GetSerialisedLadder()
|
||||
{
|
||||
return JsonConvert.SerializeObject(ladder,
|
||||
new JsonSerializerSettings
|
||||
{
|
||||
Formatting = Formatting.Indented,
|
||||
@ -319,10 +328,6 @@ namespace osu.Game.Tournament
|
||||
DefaultValueHandling = DefaultValueHandling.Ignore,
|
||||
Converters = new JsonConverter[] { new JsonPointConverter() }
|
||||
});
|
||||
|
||||
using (var stream = storage.CreateFileSafely(BRACKET_FILENAME))
|
||||
using (var sw = new StreamWriter(stream))
|
||||
sw.Write(serialisedLadder);
|
||||
}
|
||||
|
||||
protected override UserInputManager CreateUserInputManager() => new TournamentInputManager();
|
||||
|
@ -3,12 +3,15 @@
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
|
||||
namespace osu.Game.Tournament
|
||||
{
|
||||
public class TourneyButton : OsuButton
|
||||
{
|
||||
public new Box Background => base.Background;
|
||||
|
||||
public TourneyButton()
|
||||
: base(null)
|
||||
{
|
||||
|
@ -80,9 +80,8 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
if (beatmapSet.OnlineID > 0)
|
||||
{
|
||||
var existingSetWithSameOnlineID = realm.All<BeatmapSetInfo>().SingleOrDefault(b => b.OnlineID == beatmapSet.OnlineID);
|
||||
|
||||
if (existingSetWithSameOnlineID != null)
|
||||
// OnlineID should really be unique, but to avoid catastrophic failure let's iterate just to be sure.
|
||||
foreach (var existingSetWithSameOnlineID in realm.All<BeatmapSetInfo>().Where(b => b.OnlineID == beatmapSet.OnlineID))
|
||||
{
|
||||
existingSetWithSameOnlineID.DeletePending = true;
|
||||
existingSetWithSameOnlineID.OnlineID = -1;
|
||||
@ -90,7 +89,7 @@ namespace osu.Game.Beatmaps
|
||||
foreach (var b in existingSetWithSameOnlineID.Beatmaps)
|
||||
b.OnlineID = -1;
|
||||
|
||||
LogForModel(beatmapSet, $"Found existing beatmap set with same OnlineID ({beatmapSet.OnlineID}). It will be deleted.");
|
||||
LogForModel(beatmapSet, $"Found existing beatmap set with same OnlineID ({beatmapSet.OnlineID}). It will be disassociated and marked for deletion.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
23
osu.Game/Collections/CollectionToggleMenuItem.cs
Normal file
23
osu.Game/Collections/CollectionToggleMenuItem.cs
Normal file
@ -0,0 +1,23 @@
|
||||
// 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.Game.Beatmaps;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
|
||||
namespace osu.Game.Collections
|
||||
{
|
||||
public class CollectionToggleMenuItem : ToggleMenuItem
|
||||
{
|
||||
public CollectionToggleMenuItem(BeatmapCollection collection, IBeatmapInfo beatmap)
|
||||
: base(collection.Name.Value, MenuItemType.Standard, state =>
|
||||
{
|
||||
if (state)
|
||||
collection.BeatmapHashes.Add(beatmap.MD5Hash);
|
||||
else
|
||||
collection.BeatmapHashes.Remove(beatmap.MD5Hash);
|
||||
})
|
||||
{
|
||||
State.Value = collection.BeatmapHashes.Contains(beatmap.MD5Hash);
|
||||
}
|
||||
}
|
||||
}
|
@ -38,7 +38,7 @@ 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 int APIVersion => 20220705; // We may want to pull this from the game version eventually.
|
||||
|
||||
public Exception LastLoginError { get; private set; }
|
||||
|
||||
|
@ -16,11 +16,11 @@ namespace osu.Game.Online.API.Requests.Responses
|
||||
public int? Position;
|
||||
|
||||
[JsonProperty(@"score")]
|
||||
public APIScore Score;
|
||||
public SoloScoreInfo Score;
|
||||
|
||||
public ScoreInfo CreateScoreInfo(RulesetStore rulesets, BeatmapInfo beatmap = null)
|
||||
{
|
||||
var score = Score.CreateScoreInfo(rulesets, beatmap);
|
||||
var score = Score.ToScoreInfo(rulesets, beatmap);
|
||||
score.Position = Position;
|
||||
return score;
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ namespace osu.Game.Online.API.Requests.Responses
|
||||
public class APIScoresCollection
|
||||
{
|
||||
[JsonProperty(@"scores")]
|
||||
public List<APIScore> Scores;
|
||||
public List<SoloScoreInfo> Scores;
|
||||
|
||||
[JsonProperty(@"userScore")]
|
||||
public APIScoreWithPosition UserScore;
|
||||
|
143
osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs
Normal file
143
osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs
Normal file
@ -0,0 +1,143 @@
|
||||
// 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 Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
|
||||
namespace osu.Game.Online.API.Requests.Responses
|
||||
{
|
||||
[Serializable]
|
||||
public class SoloScoreInfo : IHasOnlineID<long>
|
||||
{
|
||||
[JsonProperty("replay")]
|
||||
public bool HasReplay { get; set; }
|
||||
|
||||
[JsonProperty("beatmap_id")]
|
||||
public int BeatmapID { get; set; }
|
||||
|
||||
[JsonProperty("ruleset_id")]
|
||||
public int RulesetID { get; set; }
|
||||
|
||||
[JsonProperty("build_id")]
|
||||
public int? BuildID { get; set; }
|
||||
|
||||
[JsonProperty("passed")]
|
||||
public bool Passed { get; set; }
|
||||
|
||||
[JsonProperty("total_score")]
|
||||
public int TotalScore { get; set; }
|
||||
|
||||
[JsonProperty("accuracy")]
|
||||
public double Accuracy { get; set; }
|
||||
|
||||
[JsonProperty("user_id")]
|
||||
public int UserID { get; set; }
|
||||
|
||||
// TODO: probably want to update this column to match user stats (short)?
|
||||
[JsonProperty("max_combo")]
|
||||
public int MaxCombo { get; set; }
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
[JsonProperty("rank")]
|
||||
public ScoreRank Rank { get; set; }
|
||||
|
||||
[JsonProperty("started_at")]
|
||||
public DateTimeOffset? StartedAt { get; set; }
|
||||
|
||||
[JsonProperty("ended_at")]
|
||||
public DateTimeOffset? EndedAt { get; set; }
|
||||
|
||||
[JsonProperty("mods")]
|
||||
public APIMod[] Mods { get; set; } = Array.Empty<APIMod>();
|
||||
|
||||
[JsonIgnore]
|
||||
[JsonProperty("created_at")]
|
||||
public DateTimeOffset CreatedAt { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
[JsonProperty("updated_at")]
|
||||
public DateTimeOffset UpdatedAt { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
[JsonProperty("deleted_at")]
|
||||
public DateTimeOffset? DeletedAt { get; set; }
|
||||
|
||||
[JsonProperty("statistics")]
|
||||
public Dictionary<HitResult, int> Statistics { get; set; } = new Dictionary<HitResult, int>();
|
||||
|
||||
#region osu-web API additions (not stored to database).
|
||||
|
||||
[JsonProperty("id")]
|
||||
public long? ID { get; set; }
|
||||
|
||||
[JsonProperty("user")]
|
||||
public APIUser? User { get; set; }
|
||||
|
||||
[JsonProperty("pp")]
|
||||
public double? PP { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
public override string ToString() => $"score_id: {ID} user_id: {UserID}";
|
||||
|
||||
/// <summary>
|
||||
/// Create a <see cref="ScoreInfo"/> from an API score instance.
|
||||
/// </summary>
|
||||
/// <param name="rulesets">A ruleset store, used to populate a ruleset instance in the returned score.</param>
|
||||
/// <param name="beatmap">An optional beatmap, copied into the returned score (for cases where the API does not populate the beatmap).</param>
|
||||
/// <returns></returns>
|
||||
public ScoreInfo ToScoreInfo(RulesetStore rulesets, BeatmapInfo? beatmap = null)
|
||||
{
|
||||
var ruleset = rulesets.GetRuleset(RulesetID) ?? throw new InvalidOperationException($"Ruleset with ID of {RulesetID} not found locally");
|
||||
|
||||
var rulesetInstance = ruleset.CreateInstance();
|
||||
|
||||
var mods = Mods.Select(apiMod => rulesetInstance.CreateModFromAcronym(apiMod.Acronym)).Where(m => m != null).ToArray();
|
||||
|
||||
// all API scores provided by this class are considered to be legacy.
|
||||
mods = mods.Append(rulesetInstance.CreateMod<ModClassic>()).ToArray();
|
||||
|
||||
var scoreInfo = ToScoreInfo(mods);
|
||||
|
||||
scoreInfo.Ruleset = ruleset;
|
||||
if (beatmap != null) scoreInfo.BeatmapInfo = beatmap;
|
||||
|
||||
return scoreInfo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a <see cref="ScoreInfo"/> from an API score instance.
|
||||
/// </summary>
|
||||
/// <param name="mods">The mod instances, resolved from a ruleset.</param>
|
||||
/// <returns></returns>
|
||||
public ScoreInfo ToScoreInfo(Mod[] mods) => new ScoreInfo
|
||||
{
|
||||
OnlineID = OnlineID,
|
||||
User = User ?? new APIUser { Id = UserID },
|
||||
BeatmapInfo = new BeatmapInfo { OnlineID = BeatmapID },
|
||||
Ruleset = new RulesetInfo { OnlineID = RulesetID },
|
||||
Passed = Passed,
|
||||
TotalScore = TotalScore,
|
||||
Accuracy = Accuracy,
|
||||
MaxCombo = MaxCombo,
|
||||
Rank = Rank,
|
||||
Statistics = Statistics,
|
||||
Date = EndedAt ?? DateTimeOffset.Now,
|
||||
Hash = "online", // TODO: temporary?
|
||||
HasReplay = HasReplay,
|
||||
Mods = mods,
|
||||
PP = PP,
|
||||
};
|
||||
|
||||
public long OnlineID => ID ?? -1;
|
||||
}
|
||||
}
|
@ -87,7 +87,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
||||
MD5Hash = apiBeatmap.MD5Hash
|
||||
};
|
||||
|
||||
scoreManager.OrderByTotalScoreAsync(value.Scores.Select(s => s.CreateScoreInfo(rulesets, beatmapInfo)).ToArray(), loadCancellationSource.Token)
|
||||
scoreManager.OrderByTotalScoreAsync(value.Scores.Select(s => s.ToScoreInfo(rulesets, beatmapInfo)).ToArray(), loadCancellationSource.Token)
|
||||
.ContinueWith(task => Schedule(() =>
|
||||
{
|
||||
if (loadCancellationSource.IsCancellationRequested)
|
||||
@ -101,7 +101,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
||||
scoreTable.Show();
|
||||
|
||||
var userScore = value.UserScore;
|
||||
var userScoreInfo = userScore?.Score.CreateScoreInfo(rulesets, beatmapInfo);
|
||||
var userScoreInfo = userScore?.Score.ToScoreInfo(rulesets, beatmapInfo);
|
||||
|
||||
topScoresContainer.Add(new DrawableTopScore(topScore));
|
||||
|
||||
|
@ -49,43 +49,54 @@ namespace osu.Game.Overlays
|
||||
|
||||
public void Push(PopupDialog dialog)
|
||||
{
|
||||
if (dialog == CurrentDialog || dialog.State.Value != Visibility.Visible) return;
|
||||
|
||||
var lastDialog = CurrentDialog;
|
||||
if (dialog == CurrentDialog || dialog.State.Value == Visibility.Hidden) return;
|
||||
|
||||
// Immediately update the externally accessible property as this may be used for checks even before
|
||||
// a DialogOverlay instance has finished loading.
|
||||
var lastDialog = CurrentDialog;
|
||||
CurrentDialog = dialog;
|
||||
|
||||
Scheduler.Add(() =>
|
||||
Schedule(() =>
|
||||
{
|
||||
// if any existing dialog is being displayed, dismiss it before showing a new one.
|
||||
lastDialog?.Hide();
|
||||
dialog.State.ValueChanged += state => onDialogOnStateChanged(dialog, state.NewValue);
|
||||
dialogContainer.Add(dialog);
|
||||
|
||||
// if the new dialog is hidden before added to the dialogContainer, bypass any further operations.
|
||||
if (dialog.State.Value == Visibility.Hidden)
|
||||
{
|
||||
dismiss();
|
||||
return;
|
||||
}
|
||||
|
||||
dialogContainer.Add(dialog);
|
||||
Show();
|
||||
}, false);
|
||||
|
||||
dialog.State.BindValueChanged(state =>
|
||||
{
|
||||
if (state.NewValue != Visibility.Hidden) return;
|
||||
|
||||
// Trigger the demise of the dialog as soon as it hides.
|
||||
dialog.Delay(PopupDialog.EXIT_DURATION).Expire();
|
||||
|
||||
dismiss();
|
||||
});
|
||||
});
|
||||
|
||||
void dismiss()
|
||||
{
|
||||
if (dialog != CurrentDialog) return;
|
||||
|
||||
// Handle the case where the dialog is the currently displayed dialog.
|
||||
// In this scenario, the overlay itself should also be hidden.
|
||||
Hide();
|
||||
CurrentDialog = null;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool IsPresent => Scheduler.HasPendingTasks || dialogContainer.Children.Count > 0;
|
||||
|
||||
protected override bool BlockNonPositionalInput => true;
|
||||
|
||||
private void onDialogOnStateChanged(VisibilityContainer dialog, Visibility v)
|
||||
{
|
||||
if (v != Visibility.Hidden) return;
|
||||
|
||||
// handle the dialog being dismissed.
|
||||
dialog.Delay(PopupDialog.EXIT_DURATION).Expire();
|
||||
|
||||
if (dialog == CurrentDialog)
|
||||
{
|
||||
Hide();
|
||||
CurrentDialog = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void PopIn()
|
||||
{
|
||||
base.PopIn();
|
||||
@ -97,7 +108,8 @@ namespace osu.Game.Overlays
|
||||
base.PopOut();
|
||||
lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, 100, Easing.InCubic);
|
||||
|
||||
if (CurrentDialog?.State.Value == Visibility.Visible)
|
||||
// PopOut gets called initially, but we only want to hide dialog when we have been loaded and are present.
|
||||
if (IsLoaded && CurrentDialog?.State.Value == Visibility.Visible)
|
||||
CurrentDialog.Hide();
|
||||
}
|
||||
|
||||
|
@ -1,14 +1,24 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Localisation;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Overlays.FirstRunSetup
|
||||
{
|
||||
@ -20,13 +30,175 @@ namespace osu.Game.Overlays.FirstRunSetup
|
||||
{
|
||||
Content.Children = new Drawable[]
|
||||
{
|
||||
new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: CONTENT_FONT_SIZE))
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
RowDimensions = new[]
|
||||
{
|
||||
// Avoid height changes when changing language.
|
||||
new Dimension(GridSizeMode.AutoSize, minSize: 100),
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: CONTENT_FONT_SIZE))
|
||||
{
|
||||
Text = FirstRunSetupOverlayStrings.WelcomeDescription,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
new LanguageSelectionFlow
|
||||
{
|
||||
Text = FirstRunSetupOverlayStrings.WelcomeDescription,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private class LanguageSelectionFlow : FillFlowContainer
|
||||
{
|
||||
private Bindable<string> frameworkLocale = null!;
|
||||
|
||||
private ScheduledDelegate? updateSelectedDelegate;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(FrameworkConfigManager frameworkConfig)
|
||||
{
|
||||
Direction = FillDirection.Full;
|
||||
Spacing = new Vector2(5);
|
||||
|
||||
ChildrenEnumerable = Enum.GetValues(typeof(Language))
|
||||
.Cast<Language>()
|
||||
.Select(l => new LanguageButton(l)
|
||||
{
|
||||
Action = () => frameworkLocale.Value = l.ToCultureCode()
|
||||
});
|
||||
|
||||
frameworkLocale = frameworkConfig.GetBindable<string>(FrameworkSetting.Locale);
|
||||
frameworkLocale.BindValueChanged(locale =>
|
||||
{
|
||||
if (!LanguageExtensions.TryParseCultureCode(locale.NewValue, out var language))
|
||||
language = Language.en;
|
||||
|
||||
// Changing language may cause a short period of blocking the UI thread while the new glyphs are loaded.
|
||||
// Scheduling ensures the button animation plays smoothly after any blocking operation completes.
|
||||
// Note that a delay is required (the alternative would be a double-schedule; delay feels better).
|
||||
updateSelectedDelegate?.Cancel();
|
||||
updateSelectedDelegate = Scheduler.AddDelayed(() => updateSelectedStates(language), 50);
|
||||
}, true);
|
||||
}
|
||||
|
||||
private void updateSelectedStates(Language language)
|
||||
{
|
||||
foreach (var c in Children.OfType<LanguageButton>())
|
||||
c.Selected = c.Language == language;
|
||||
}
|
||||
|
||||
private class LanguageButton : OsuClickableContainer
|
||||
{
|
||||
public readonly Language Language;
|
||||
|
||||
private Box backgroundBox = null!;
|
||||
|
||||
private OsuSpriteText text = null!;
|
||||
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||
|
||||
private bool selected;
|
||||
|
||||
public bool Selected
|
||||
{
|
||||
get => selected;
|
||||
set
|
||||
{
|
||||
if (selected == value)
|
||||
return;
|
||||
|
||||
selected = value;
|
||||
|
||||
if (IsLoaded)
|
||||
updateState();
|
||||
}
|
||||
}
|
||||
|
||||
public LanguageButton(Language language)
|
||||
{
|
||||
Language = language;
|
||||
|
||||
Size = new Vector2(160, 50);
|
||||
Masking = true;
|
||||
CornerRadius = 10;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
backgroundBox = new Box
|
||||
{
|
||||
Alpha = 0,
|
||||
Colour = colourProvider.Background5,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
text = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Colour = colourProvider.Light1,
|
||||
Text = Language.GetDescription(),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
updateState();
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
if (!selected)
|
||||
updateState();
|
||||
return base.OnHover(e);
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
if (!selected)
|
||||
updateState();
|
||||
base.OnHoverLost(e);
|
||||
}
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
if (selected)
|
||||
{
|
||||
const double selected_duration = 1000;
|
||||
|
||||
backgroundBox.FadeTo(1, selected_duration, Easing.OutQuint);
|
||||
backgroundBox.FadeColour(colourProvider.Background2, selected_duration, Easing.OutQuint);
|
||||
text.FadeColour(colourProvider.Content1, selected_duration, Easing.OutQuint);
|
||||
text.ScaleTo(1.2f, selected_duration, Easing.OutQuint);
|
||||
}
|
||||
else
|
||||
{
|
||||
const double duration = 500;
|
||||
|
||||
backgroundBox.FadeTo(IsHovered ? 1 : 0, duration, Easing.OutQuint);
|
||||
backgroundBox.FadeColour(colourProvider.Background5, duration, Easing.OutQuint);
|
||||
text.FadeColour(colourProvider.Light1, duration, Easing.OutQuint);
|
||||
text.ScaleTo(1, duration, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -35,7 +35,13 @@ namespace osu.Game.Screens.Edit.GameplayTest
|
||||
ScoreProcessor.HasCompleted.BindValueChanged(completed =>
|
||||
{
|
||||
if (completed.NewValue)
|
||||
Scheduler.AddDelayed(this.Exit, RESULTS_DISPLAY_DELAY);
|
||||
{
|
||||
Scheduler.AddDelayed(() =>
|
||||
{
|
||||
if (this.IsCurrentScreen())
|
||||
this.Exit();
|
||||
}, RESULTS_DISPLAY_DELAY);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -9,17 +9,20 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Collections;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
@ -27,6 +30,7 @@ using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online;
|
||||
using osu.Game.Online.Chat;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.BeatmapSet;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using osu.Game.Rulesets;
|
||||
@ -38,7 +42,7 @@ using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay
|
||||
{
|
||||
public class DrawableRoomPlaylistItem : OsuRearrangeableListItem<PlaylistItem>
|
||||
public class DrawableRoomPlaylistItem : OsuRearrangeableListItem<PlaylistItem>, IHasContextMenu
|
||||
{
|
||||
public const float HEIGHT = 50;
|
||||
|
||||
@ -93,6 +97,9 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
[Resolved]
|
||||
private RulesetStore rulesets { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private BeatmapManager beatmaps { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; }
|
||||
|
||||
@ -102,6 +109,15 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
[Resolved]
|
||||
private BeatmapLookupCache beatmapLookupCache { get; set; }
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
private BeatmapSetOverlay beatmapOverlay { get; set; }
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
private CollectionManager collectionManager { get; set; }
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
private ManageCollectionsDialog manageCollectionsDialog { get; set; }
|
||||
|
||||
protected override bool ShouldBeConsideredForInput(Drawable child) => AllowReordering || AllowDeletion || !AllowSelection || SelectedItem.Value == Model;
|
||||
|
||||
public DrawableRoomPlaylistItem(PlaylistItem item)
|
||||
@ -433,7 +449,7 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -470,6 +486,31 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
return true;
|
||||
}
|
||||
|
||||
public MenuItem[] ContextMenuItems
|
||||
{
|
||||
get
|
||||
{
|
||||
List<MenuItem> items = new List<MenuItem>();
|
||||
|
||||
if (beatmapOverlay != null)
|
||||
items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => beatmapOverlay.FetchAndShowBeatmap(Item.Beatmap.OnlineID)));
|
||||
|
||||
if (collectionManager != null && beatmap != null)
|
||||
{
|
||||
if (beatmaps.QueryBeatmap(b => b.OnlineID == beatmap.OnlineID) is BeatmapInfo local && !local.BeatmapSet.AsNonNull().DeletePending)
|
||||
{
|
||||
var collectionItems = collectionManager.Collections.Select(c => new CollectionToggleMenuItem(c, beatmap)).Cast<OsuMenuItem>().ToList();
|
||||
if (manageCollectionsDialog != null)
|
||||
collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show));
|
||||
|
||||
items.Add(new OsuMenuItem("Collections") { Items = collectionItems });
|
||||
}
|
||||
}
|
||||
|
||||
return items.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public class PlaylistEditButton : GrayButton
|
||||
{
|
||||
public PlaylistEditButton()
|
||||
|
@ -15,6 +15,7 @@ using osu.Framework.Screens;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Online;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Rooms;
|
||||
@ -81,134 +82,138 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Horizontal = 5, Vertical = 10 },
|
||||
Child = new GridContainer
|
||||
Child = new OsuContextMenuContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ColumnDimensions = new[]
|
||||
Child = new GridContainer
|
||||
{
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.Absolute, 10),
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.Absolute, 10),
|
||||
new Dimension(),
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
// Participants column
|
||||
new GridContainer
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.Absolute, 10),
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.Absolute, 10),
|
||||
new Dimension(),
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
RowDimensions = new[]
|
||||
// Participants column
|
||||
new GridContainer
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize)
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[] { new ParticipantsListHeader() },
|
||||
new Drawable[]
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new ParticipantsList
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
// Spacer
|
||||
null,
|
||||
// Beatmap column
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[] { new OverlinedHeader("Beatmap") },
|
||||
new Drawable[]
|
||||
{
|
||||
addItemButton = new AddItemButton
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 40,
|
||||
Text = "Add item",
|
||||
Action = () => OpenSongSelection()
|
||||
},
|
||||
new Dimension(GridSizeMode.AutoSize)
|
||||
},
|
||||
null,
|
||||
new Drawable[]
|
||||
Content = new[]
|
||||
{
|
||||
new MultiplayerPlaylist
|
||||
new Drawable[] { new ParticipantsListHeader() },
|
||||
new Drawable[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
RequestEdit = item => OpenSongSelection(item.ID)
|
||||
}
|
||||
},
|
||||
new[]
|
||||
{
|
||||
UserModsSection = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Margin = new MarginPadding { Top = 10 },
|
||||
Alpha = 0,
|
||||
Children = new Drawable[]
|
||||
new ParticipantsList
|
||||
{
|
||||
new OverlinedHeader("Extra mods"),
|
||||
new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(10, 0),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new UserModSelectButton
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Width = 90,
|
||||
Text = "Select",
|
||||
Action = ShowUserModSelect,
|
||||
},
|
||||
new ModDisplay
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Current = UserMods,
|
||||
Scale = new Vector2(0.8f),
|
||||
},
|
||||
}
|
||||
},
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
// Spacer
|
||||
null,
|
||||
// Beatmap column
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[] { new OverlinedHeader("Beatmap") },
|
||||
new Drawable[]
|
||||
{
|
||||
addItemButton = new AddItemButton
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 40,
|
||||
Text = "Add item",
|
||||
Action = () => OpenSongSelection()
|
||||
},
|
||||
},
|
||||
null,
|
||||
new Drawable[]
|
||||
{
|
||||
new MultiplayerPlaylist
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
RequestEdit = item => OpenSongSelection(item.ID)
|
||||
}
|
||||
},
|
||||
new[]
|
||||
{
|
||||
UserModsSection = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Margin = new MarginPadding { Top = 10 },
|
||||
Alpha = 0,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OverlinedHeader("Extra mods"),
|
||||
new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(10, 0),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new UserModSelectButton
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Width = 90,
|
||||
Text = "Select",
|
||||
Action = ShowUserModSelect,
|
||||
},
|
||||
new ModDisplay
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Current = UserMods,
|
||||
Scale = new Vector2(0.8f),
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(GridSizeMode.Absolute, 5),
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
}
|
||||
},
|
||||
RowDimensions = new[]
|
||||
// Spacer
|
||||
null,
|
||||
// Main right column
|
||||
new GridContainer
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(GridSizeMode.Absolute, 5),
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
}
|
||||
},
|
||||
// Spacer
|
||||
null,
|
||||
// Main right column
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[] { new OverlinedHeader("Chat") },
|
||||
new Drawable[] { new MatchChatDisplay(Room) { RelativeSizeAxes = Axes.Both } }
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[] { new OverlinedHeader("Chat") },
|
||||
new Drawable[] { new MatchChatDisplay(Room) { RelativeSizeAxes = Axes.Both } }
|
||||
},
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(),
|
||||
}
|
||||
},
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
||||
@ -24,20 +23,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
InternalChild = new OsuContextMenuContainer
|
||||
InternalChild = new OsuScrollContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new OsuScrollContainer
|
||||
ScrollbarVisible = false,
|
||||
Child = panels = new FillFlowContainer<ParticipantPanel>
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ScrollbarVisible = false,
|
||||
Child = panels = new FillFlowContainer<ParticipantPanel>
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0, 2)
|
||||
}
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0, 2)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Input;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Screens.OnlinePlay.Components;
|
||||
@ -75,151 +76,155 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Horizontal = 5, Vertical = 10 },
|
||||
Child = new GridContainer
|
||||
Child = new OsuContextMenuContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ColumnDimensions = new[]
|
||||
Child = new GridContainer
|
||||
{
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.Absolute, 10),
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.Absolute, 10),
|
||||
new Dimension(),
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
// Playlist items column
|
||||
new Container
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.Absolute, 10),
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.Absolute, 10),
|
||||
new Dimension(),
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Right = 5 },
|
||||
Child = new GridContainer
|
||||
// Playlist items column
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Right = 5 },
|
||||
Child = new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[] { new OverlinedPlaylistHeader(), },
|
||||
new Drawable[]
|
||||
{
|
||||
new DrawableRoomPlaylist
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Items = { BindTarget = Room.Playlist },
|
||||
SelectedItem = { BindTarget = SelectedItem },
|
||||
AllowSelection = true,
|
||||
AllowShowingResults = true,
|
||||
RequestResults = item =>
|
||||
{
|
||||
Debug.Assert(RoomId.Value != null);
|
||||
ParentScreen?.Push(new PlaylistsResultsScreen(null, RoomId.Value.Value, item, false));
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(),
|
||||
}
|
||||
}
|
||||
},
|
||||
// Spacer
|
||||
null,
|
||||
// Middle column (mods and leaderboard)
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[] { new OverlinedPlaylistHeader(), },
|
||||
new[]
|
||||
{
|
||||
UserModsSection = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Alpha = 0,
|
||||
Margin = new MarginPadding { Bottom = 10 },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OverlinedHeader("Extra mods"),
|
||||
new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(10, 0),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new UserModSelectButton
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Width = 90,
|
||||
Text = "Select",
|
||||
Action = ShowUserModSelect,
|
||||
},
|
||||
new ModDisplay
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Current = UserMods,
|
||||
Scale = new Vector2(0.8f),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
new Drawable[]
|
||||
{
|
||||
new DrawableRoomPlaylist
|
||||
progressSection = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Items = { BindTarget = Room.Playlist },
|
||||
SelectedItem = { BindTarget = SelectedItem },
|
||||
AllowSelection = true,
|
||||
AllowShowingResults = true,
|
||||
RequestResults = item =>
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Alpha = 0,
|
||||
Margin = new MarginPadding { Bottom = 10 },
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
Debug.Assert(RoomId.Value != null);
|
||||
ParentScreen?.Push(new PlaylistsResultsScreen(null, RoomId.Value.Value, item, false));
|
||||
new OverlinedHeader("Progress"),
|
||||
new RoomLocalUserInfo(),
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
new Drawable[]
|
||||
{
|
||||
new OverlinedHeader("Leaderboard")
|
||||
},
|
||||
new Drawable[] { leaderboard = new MatchLeaderboard { RelativeSizeAxes = Axes.Both }, },
|
||||
},
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(),
|
||||
}
|
||||
},
|
||||
// Spacer
|
||||
null,
|
||||
// Main right column
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[] { new OverlinedHeader("Chat") },
|
||||
new Drawable[] { new MatchChatDisplay(Room) { RelativeSizeAxes = Axes.Both } }
|
||||
},
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(),
|
||||
}
|
||||
}
|
||||
},
|
||||
// Spacer
|
||||
null,
|
||||
// Middle column (mods and leaderboard)
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Content = new[]
|
||||
{
|
||||
new[]
|
||||
{
|
||||
UserModsSection = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Alpha = 0,
|
||||
Margin = new MarginPadding { Bottom = 10 },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OverlinedHeader("Extra mods"),
|
||||
new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(10, 0),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new UserModSelectButton
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Width = 90,
|
||||
Text = "Select",
|
||||
Action = ShowUserModSelect,
|
||||
},
|
||||
new ModDisplay
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Current = UserMods,
|
||||
Scale = new Vector2(0.8f),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
new Drawable[]
|
||||
{
|
||||
progressSection = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Alpha = 0,
|
||||
Margin = new MarginPadding { Bottom = 10 },
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OverlinedHeader("Progress"),
|
||||
new RoomLocalUserInfo(),
|
||||
}
|
||||
},
|
||||
},
|
||||
new Drawable[]
|
||||
{
|
||||
new OverlinedHeader("Leaderboard")
|
||||
},
|
||||
new Drawable[] { leaderboard = new MatchLeaderboard { RelativeSizeAxes = Axes.Both }, },
|
||||
},
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(),
|
||||
}
|
||||
},
|
||||
// Spacer
|
||||
null,
|
||||
// Main right column
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[] { new OverlinedHeader("Chat") },
|
||||
new Drawable[] { new MatchChatDisplay(Room) { RelativeSizeAxes = Axes.Both } }
|
||||
},
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(),
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -33,7 +33,7 @@ namespace osu.Game.Screens.Ranking
|
||||
return null;
|
||||
|
||||
getScoreRequest = new GetScoresRequest(Score.BeatmapInfo, Score.Ruleset);
|
||||
getScoreRequest.Success += r => scoresCallback?.Invoke(r.Scores.Where(s => s.OnlineID != Score.OnlineID).Select(s => s.CreateScoreInfo(rulesets, Beatmap.Value.BeatmapInfo)));
|
||||
getScoreRequest.Success += r => scoresCallback?.Invoke(r.Scores.Where(s => s.OnlineID != Score.OnlineID).Select(s => s.ToScoreInfo(rulesets, Beatmap.Value.BeatmapInfo)));
|
||||
return getScoreRequest;
|
||||
}
|
||||
|
||||
|
@ -244,7 +244,7 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
|
||||
if (collectionManager != null)
|
||||
{
|
||||
var collectionItems = collectionManager.Collections.Select(createCollectionMenuItem).ToList();
|
||||
var collectionItems = collectionManager.Collections.Select(c => new CollectionToggleMenuItem(c, beatmapInfo)).Cast<OsuMenuItem>().ToList();
|
||||
if (manageCollectionsDialog != null)
|
||||
collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show));
|
||||
|
||||
@ -258,20 +258,6 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
}
|
||||
}
|
||||
|
||||
private MenuItem createCollectionMenuItem(BeatmapCollection collection)
|
||||
{
|
||||
return new ToggleMenuItem(collection.Name.Value, MenuItemType.Standard, s =>
|
||||
{
|
||||
if (s)
|
||||
collection.BeatmapHashes.Add(beatmapInfo.MD5Hash);
|
||||
else
|
||||
collection.BeatmapHashes.Remove(beatmapInfo.MD5Hash);
|
||||
})
|
||||
{
|
||||
State = { Value = collection.BeatmapHashes.Contains(beatmapInfo.MD5Hash) }
|
||||
};
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
@ -152,7 +152,7 @@ namespace osu.Game.Screens.Select.Leaderboards
|
||||
|
||||
req.Success += r =>
|
||||
{
|
||||
scoreManager.OrderByTotalScoreAsync(r.Scores.Select(s => s.CreateScoreInfo(rulesets, fetchBeatmapInfo)).ToArray(), cancellationToken)
|
||||
scoreManager.OrderByTotalScoreAsync(r.Scores.Select(s => s.ToScoreInfo(rulesets, fetchBeatmapInfo)).ToArray(), cancellationToken)
|
||||
.ContinueWith(task => Schedule(() =>
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
|
@ -136,6 +136,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay
|
||||
return true;
|
||||
|
||||
case GetBeatmapsRequest getBeatmapsRequest:
|
||||
{
|
||||
var result = new List<APIBeatmap>();
|
||||
|
||||
foreach (int id in getBeatmapsRequest.BeatmapIds)
|
||||
@ -154,6 +155,24 @@ namespace osu.Game.Tests.Visual.OnlinePlay
|
||||
|
||||
getBeatmapsRequest.TriggerSuccess(new GetBeatmapsResponse { Beatmaps = result });
|
||||
return true;
|
||||
}
|
||||
|
||||
case GetBeatmapSetRequest getBeatmapSetRequest:
|
||||
{
|
||||
var baseBeatmap = getBeatmapSetRequest.Type == BeatmapSetLookupType.BeatmapId
|
||||
? beatmapManager.QueryBeatmap(b => b.OnlineID == getBeatmapSetRequest.ID)
|
||||
: beatmapManager.QueryBeatmap(b => b.BeatmapSet.OnlineID == getBeatmapSetRequest.ID);
|
||||
|
||||
if (baseBeatmap == null)
|
||||
{
|
||||
baseBeatmap = new TestBeatmap(new RulesetInfo { OnlineID = 0 }).BeatmapInfo;
|
||||
baseBeatmap.OnlineID = getBeatmapSetRequest.ID;
|
||||
baseBeatmap.BeatmapSet!.OnlineID = getBeatmapSetRequest.ID;
|
||||
}
|
||||
|
||||
getBeatmapSetRequest.TriggerSuccess(OsuTestScene.CreateAPIBeatmapSet(baseBeatmap));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -36,7 +36,7 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Realm" Version="10.14.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2022.707.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2022.713.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.702.0" />
|
||||
<PackageReference Include="Sentry" Version="3.19.0" />
|
||||
<PackageReference Include="SharpCompress" Version="0.32.1" />
|
||||
|
@ -61,7 +61,7 @@
|
||||
<Reference Include="System.Net.Http" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2022.707.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2022.713.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.702.0" />
|
||||
</ItemGroup>
|
||||
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net6.0) -->
|
||||
@ -84,7 +84,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.707.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2022.713.0" />
|
||||
<PackageReference Include="SharpCompress" Version="0.32.1" />
|
||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||
|
Loading…
Reference in New Issue
Block a user