1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 20:13:21 +08:00

Merge branch 'master' into move-difficulty-graph-toggle

This commit is contained in:
Bartłomiej Dach 2022-05-02 16:38:25 +02:00 committed by GitHub
commit 2b4a49e17f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 568 additions and 220 deletions

View File

@ -52,7 +52,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.422.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.428.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.430.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. -->

View File

@ -16,31 +16,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
{
public class TestSceneOsuModAlternate : OsuModTestScene
{
[Test]
public void TestInputAtIntro() => CreateModTest(new ModTestData
{
Mod = new OsuModAlternate(),
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>
{
new OsuReplayFrame(500, new Vector2(200), OsuAction.LeftButton),
new OsuReplayFrame(501, new Vector2(200)),
new OsuReplayFrame(1000, new Vector2(100), OsuAction.LeftButton),
}
});
[Test]
public void TestInputAlternating() => CreateModTest(new ModTestData
{
@ -116,17 +91,50 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
}
});
/// <summary>
/// Ensures alternation is reset before the first hitobject after intro.
/// </summary>
[Test]
public void TestInputSingularAtIntro() => CreateModTest(new ModTestData
{
Mod = new OsuModAlternate(),
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 same key at hitobject and ensure it has been hit.
new OsuReplayFrame(1000, new Vector2(100), OsuAction.LeftButton),
}
});
/// <summary>
/// Ensures alternation is reset before the first hitobject after a break.
/// </summary>
[Test]
public void TestInputSingularWithBreak() => CreateModTest(new ModTestData
{
Mod = new OsuModAlternate(),
PassCondition = () => Player.ScoreProcessor.Combo.Value == 2,
PassCondition = () => Player.ScoreProcessor.Combo.Value == 0 && Player.ScoreProcessor.HighestCombo.Value == 2,
Autoplay = false,
Beatmap = new Beatmap
{
Breaks = new List<BreakPeriod>
{
new BreakPeriod(500, 2250),
new BreakPeriod(500, 2000),
},
HitObjects = new List<HitObject>
{
@ -138,16 +146,29 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
new HitCircle
{
StartTime = 2500,
Position = new Vector2(100),
}
Position = new Vector2(500, 100),
},
new HitCircle
{
StartTime = 3000,
Position = new Vector2(500, 100),
},
}
},
ReplayFrames = new List<ReplayFrame>
{
// first press to start alternate lock.
new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton),
new OsuReplayFrame(501, new Vector2(100)),
new OsuReplayFrame(2500, new Vector2(100), OsuAction.LeftButton),
new OsuReplayFrame(2501, new Vector2(100)),
// press same key after break but before hit object.
new OsuReplayFrame(2250, new Vector2(300, 100), OsuAction.LeftButton),
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 same key at third hitobject and ensure it has been missed.
new OsuReplayFrame(3000, new Vector2(500, 100), OsuAction.LeftButton),
new OsuReplayFrame(3001, new Vector2(500, 100)),
}
});
}

View File

@ -2,21 +2,24 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Bindables;
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>, IApplicableToPlayer
public class OsuModAlternate : Mod, IApplicableToDrawableRuleset<OsuHitObject>
{
public override string Name => @"Alternate";
public override string Acronym => @"AL";
@ -26,9 +29,16 @@ namespace osu.Game.Rulesets.Osu.Mods
public override ModType Type => ModType.Conversion;
public override IconUsage? Icon => FontAwesome.Solid.Keyboard;
private double firstObjectValidJudgementTime;
private IBindable<bool> isBreakTime;
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;
@ -39,29 +49,30 @@ namespace osu.Game.Rulesets.Osu.Mods
ruleset = drawableRuleset;
drawableRuleset.KeyBindingInputManager.Add(new InputInterceptor(this));
var firstHitObject = ruleset.Objects.FirstOrDefault();
firstObjectValidJudgementTime = (firstHitObject?.StartTime ?? 0) - (firstHitObject?.HitWindows.WindowFor(HitResult.Meh) ?? 0);
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;
}
public void ApplyToPlayer(Player player)
{
isBreakTime = player.IsBreakTime.GetBoundCopy();
isBreakTime.ValueChanged += e =>
{
if (e.NewValue)
lastActionPressed = null;
};
}
private bool checkCorrectAction(OsuAction action)
{
if (isBreakTime.Value)
return true;
if (gameplayClock.CurrentTime < firstObjectValidJudgementTime)
if (nonGameplayPeriods.IsInAny(gameplayClock.CurrentTime))
{
lastActionPressed = null;
return true;
}
switch (action)
{

View File

@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public class OsuModClassic : ModClassic, IApplicableToHitObject, IApplicableToDrawableHitObject, IApplicableToDrawableRuleset<OsuHitObject>
{
public override Type[] IncompatibleMods => new[] { typeof(OsuModStrictTracking) };
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModStrictTracking)).ToArray();
[SettingSource("No slider head accuracy requirement", "Scores sliders proportionally to the number of ticks hit.")]
public Bindable<bool> NoSliderHeadAccuracy { get; } = new BindableBool(true);

View File

@ -22,6 +22,8 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public override string Description => "It never gets boring!";
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModTarget)).ToArray();
private static readonly float playfield_diagonal = OsuPlayfield.BASE_SIZE.LengthFast;
private Random? rng;

View File

@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override ModType Type => ModType.Automation;
public override string Description => @"Spinners will be automatically completed.";
public override double ScoreMultiplier => 0.9;
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(OsuModAutopilot) };
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(OsuModAutopilot), typeof(OsuModTarget) };
public void ApplyToDrawableHitObject(DrawableHitObject hitObject)
{

View File

@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override ModType Type => ModType.DifficultyIncrease;
public override string Description => @"Follow circles just got serious...";
public override double ScoreMultiplier => 1.0;
public override Type[] IncompatibleMods => new[] { typeof(ModClassic) };
public override Type[] IncompatibleMods => new[] { typeof(ModClassic), typeof(OsuModTarget) };
public void ApplyToDrawableHitObject(DrawableHitObject drawable)
{

View File

@ -42,7 +42,14 @@ namespace osu.Game.Rulesets.Osu.Mods
public override string Description => @"Practice keeping up with the beat of the song.";
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSuddenDeath) };
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[]
{
typeof(IRequiresApproachCircles),
typeof(OsuModRandom),
typeof(OsuModSpunOut),
typeof(OsuModStrictTracking),
typeof(OsuModSuddenDeath)
}).ToArray();
[SettingSource("Seed", "Use a custom seed instead of a random one", SettingControlType = typeof(SettingsNumberBox))]
public Bindable<int?> Seed { get; } = new Bindable<int?>

View File

@ -35,7 +35,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
});
AddUntilStep("wait for player to be current", () => player.IsCurrentScreen() && player.IsLoaded);
AddStep("start gameplay", () => ((IMultiplayerClient)MultiplayerClient).MatchStarted());
AddStep("start gameplay", () => ((IMultiplayerClient)MultiplayerClient).GameplayStarted());
}
[Test]

View File

@ -4,11 +4,11 @@
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
using osu.Game.Overlays.BeatmapSet;
using osu.Game.Rulesets;
namespace osu.Game.Tests.Visual.Online
{
@ -17,79 +17,86 @@ namespace osu.Game.Tests.Visual.Online
[Cached]
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
private readonly TestRulesetSelector selector;
private BeatmapRulesetSelector selector;
public TestSceneBeatmapRulesetSelector()
[SetUp]
public void SetUp() => Schedule(() => Child = selector = new BeatmapRulesetSelector
{
Add(selector = new TestRulesetSelector());
}
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
BeatmapSet = new APIBeatmapSet(),
});
[Resolved]
private IRulesetStore rulesets { get; set; }
[Test]
public void TestDisplay()
{
AddSliderStep("osu", 0, 100, 0, v => updateBeatmaps(0, v));
AddSliderStep("taiko", 0, 100, 0, v => updateBeatmaps(1, v));
AddSliderStep("fruits", 0, 100, 0, v => updateBeatmaps(2, v));
AddSliderStep("mania", 0, 100, 0, v => updateBeatmaps(3, v));
void updateBeatmaps(int ruleset, int count)
{
if (selector == null)
return;
selector.BeatmapSet = new APIBeatmapSet
{
Beatmaps = selector.BeatmapSet.Beatmaps
.Where(b => b.Ruleset.OnlineID != ruleset)
.Concat(Enumerable.Range(0, count).Select(_ => new APIBeatmap { RulesetID = ruleset }))
.ToArray(),
};
}
}
[Test]
public void TestMultipleRulesetsBeatmapSet()
{
var enabledRulesets = rulesets.AvailableRulesets.Skip(1).Take(2);
AddStep("load multiple rulesets beatmapset", () =>
{
selector.BeatmapSet = new APIBeatmapSet
{
Beatmaps = enabledRulesets.Select(r => new APIBeatmap { RulesetID = r.OnlineID }).ToArray()
};
});
var tabItems = selector.TabContainer.TabItems;
AddAssert("other rulesets disabled", () => tabItems.Except(tabItems.Where(t => enabledRulesets.Any(r => r.Equals(t.Value)))).All(t => !t.Enabled.Value));
AddAssert("left-most ruleset selected", () => tabItems.First(t => t.Enabled.Value).Active.Value);
}
[Test]
public void TestSingleRulesetBeatmapSet()
{
var enabledRuleset = rulesets.AvailableRulesets.Last();
AddStep("load single ruleset beatmapset", () =>
{
selector.BeatmapSet = new APIBeatmapSet
{
Beatmaps = new[]
{
new APIBeatmap
{
RulesetID = enabledRuleset.OnlineID
}
new APIBeatmap { RulesetID = 1 },
new APIBeatmap { RulesetID = 2 },
}
};
});
AddAssert("single ruleset selected", () => selector.SelectedTab.Value.Equals(enabledRuleset));
AddAssert("osu disabled", () => !selector.ChildrenOfType<BeatmapRulesetTabItem>().Single(t => t.Value.OnlineID == 0).Enabled.Value);
AddAssert("mania disabled", () => !selector.ChildrenOfType<BeatmapRulesetTabItem>().Single(t => t.Value.OnlineID == 3).Enabled.Value);
AddAssert("taiko selected", () => selector.ChildrenOfType<BeatmapRulesetTabItem>().Single(t => t.Active.Value).Value.OnlineID == 1);
}
[Test]
public void TestSingleRulesetBeatmapSet()
{
AddStep("load single ruleset beatmapset", () =>
{
selector.BeatmapSet = new APIBeatmapSet
{
Beatmaps = new[] { new APIBeatmap { RulesetID = 3 } }
};
});
AddAssert("single ruleset selected", () => selector.ChildrenOfType<BeatmapRulesetTabItem>().Single(t => t.Active.Value).Value.OnlineID == 3);
}
[Test]
public void TestEmptyBeatmapSet()
{
AddStep("load empty beatmapset", () => selector.BeatmapSet = new APIBeatmapSet());
AddAssert("no ruleset selected", () => selector.SelectedTab == null);
AddAssert("all rulesets disabled", () => selector.TabContainer.TabItems.All(t => !t.Enabled.Value));
AddAssert("all rulesets disabled", () => selector.ChildrenOfType<BeatmapRulesetTabItem>().All(t => !t.Active.Value && !t.Enabled.Value));
}
[Test]
public void TestNullBeatmapSet()
{
AddStep("load null beatmapset", () => selector.BeatmapSet = null);
AddAssert("no ruleset selected", () => selector.SelectedTab == null);
AddAssert("all rulesets disabled", () => selector.TabContainer.TabItems.All(t => !t.Enabled.Value));
}
private class TestRulesetSelector : BeatmapRulesetSelector
{
public new TabItem<RulesetInfo> SelectedTab => base.SelectedTab;
public new TabFillFlowContainer TabContainer => base.TabContainer;
AddAssert("all rulesets disabled", () => selector.ChildrenOfType<BeatmapRulesetTabItem>().All(t => !t.Active.Value && !t.Enabled.Value));
}
}
}

View File

@ -17,7 +17,7 @@ namespace osu.Game.Tests.Visual.Online
public class TestSceneProfileRulesetSelector : OsuTestScene
{
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green);
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Pink);
public TestSceneProfileRulesetSelector()
{
@ -32,14 +32,14 @@ namespace osu.Game.Tests.Visual.Online
};
AddStep("set osu! as default", () => selector.SetDefaultRuleset(new OsuRuleset().RulesetInfo));
AddStep("set mania as default", () => selector.SetDefaultRuleset(new ManiaRuleset().RulesetInfo));
AddStep("set taiko as default", () => selector.SetDefaultRuleset(new TaikoRuleset().RulesetInfo));
AddStep("set catch as default", () => selector.SetDefaultRuleset(new CatchRuleset().RulesetInfo));
AddStep("set mania as default", () => selector.SetDefaultRuleset(new ManiaRuleset().RulesetInfo));
AddStep("User with osu as default", () => user.Value = new APIUser { PlayMode = "osu" });
AddStep("User with mania as default", () => user.Value = new APIUser { PlayMode = "mania" });
AddStep("User with taiko as default", () => user.Value = new APIUser { PlayMode = "taiko" });
AddStep("User with catch as default", () => user.Value = new APIUser { PlayMode = "fruits" });
AddStep("User with osu as default", () => user.Value = new APIUser { Id = 0, PlayMode = "osu" });
AddStep("User with taiko as default", () => user.Value = new APIUser { Id = 1, PlayMode = "taiko" });
AddStep("User with catch as default", () => user.Value = new APIUser { Id = 2, PlayMode = "fruits" });
AddStep("User with mania as default", () => user.Value = new APIUser { Id = 3, PlayMode = "mania" });
AddStep("null user", () => user.Value = null);
}
}

View File

@ -1,35 +1,99 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Game.Screens.Select;
using osuTK;
using osuTK.Input;
namespace osu.Game.Tests.Visual.SongSelect
{
public class TestSceneSongSelectFooter : OsuManualInputManagerTestScene
{
public TestSceneSongSelectFooter()
{
AddStep("Create footer", () =>
{
Footer footer;
AddRange(new Drawable[]
{
footer = new Footer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
}
});
private FooterButtonRandom randomButton;
footer.AddButton(new FooterButtonMods(), null);
footer.AddButton(new FooterButtonRandom
{
NextRandom = () => { },
PreviousRandom = () => { },
}, null);
footer.AddButton(new FooterButtonOptions(), null);
private bool nextRandomCalled;
private bool previousRandomCalled;
[SetUp]
public void SetUp() => Schedule(() =>
{
nextRandomCalled = false;
previousRandomCalled = false;
Footer footer;
Child = footer = new Footer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
};
footer.AddButton(new FooterButtonMods(), null);
footer.AddButton(randomButton = new FooterButtonRandom
{
NextRandom = () => nextRandomCalled = true,
PreviousRandom = () => previousRandomCalled = true,
}, null);
footer.AddButton(new FooterButtonOptions(), null);
InputManager.MoveMouseTo(Vector2.Zero);
});
[Test]
public void TestFooterRandom()
{
AddStep("press F2", () => InputManager.Key(Key.F2));
AddAssert("next random invoked", () => nextRandomCalled && !previousRandomCalled);
}
[Test]
public void TestFooterRandomViaMouse()
{
AddStep("click button", () =>
{
InputManager.MoveMouseTo(randomButton);
InputManager.Click(MouseButton.Left);
});
AddAssert("next random invoked", () => nextRandomCalled && !previousRandomCalled);
}
[Test]
public void TestFooterRewind()
{
AddStep("press Shift+F2", () =>
{
InputManager.PressKey(Key.LShift);
InputManager.PressKey(Key.F2);
InputManager.ReleaseKey(Key.F2);
InputManager.ReleaseKey(Key.LShift);
});
AddAssert("previous random invoked", () => previousRandomCalled && !nextRandomCalled);
}
[Test]
public void TestFooterRewindViaShiftMouseLeft()
{
AddStep("shift + click button", () =>
{
InputManager.PressKey(Key.LShift);
InputManager.MoveMouseTo(randomButton);
InputManager.Click(MouseButton.Left);
InputManager.ReleaseKey(Key.LShift);
});
AddAssert("previous random invoked", () => previousRandomCalled && !nextRandomCalled);
}
[Test]
public void TestFooterRewindViaMouseRight()
{
AddStep("right click button", () =>
{
InputManager.MoveMouseTo(randomButton);
InputManager.Click(MouseButton.Right);
});
AddAssert("previous random invoked", () => previousRandomCalled && !nextRandomCalled);
}
}
}

View File

@ -65,6 +65,12 @@ namespace osu.Game.Tests.Visual.UserInterface
});
}
[Test]
public void TestBasic()
{
AddAssert("overlay visible", () => overlay.State.Value == Visibility.Visible);
}
[Test]
[Ignore("Enable when first run setup is being displayed on first run.")]
public void TestDoesntOpenOnSecondRun()

View File

@ -158,7 +158,7 @@ namespace osu.Game.Graphics.UserInterface
protected override bool OnMouseDown(MouseDownEvent e)
{
Content.ScaleTo(0.8f, 2000, Easing.OutQuint);
Content.ScaleTo(0.9f, 2000, Easing.OutQuint);
return base.OnMouseDown(e);
}
@ -176,8 +176,8 @@ namespace osu.Game.Graphics.UserInterface
if (!Enabled.Value)
{
colourDark = colourDark.Darken(0.3f);
colourLight = colourLight.Darken(0.3f);
colourDark = colourDark.Darken(1f);
colourLight = colourLight.Darken(1f);
}
else if (IsHovered)
{

View File

@ -0,0 +1,17 @@
// 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 MessagePack;
namespace osu.Game.Online.Multiplayer
{
/// <summary>
/// A <see cref="MultiplayerCountdown"/> started by the server when clients being to load.
/// Indicates how long until gameplay will forcefully start, excluding any users which have not completed loading,
/// and forcing progression of any clients that are blocking load due to user interaction.
/// </summary>
[MessagePackObject]
public class ForceGameplayStartCountdown : MultiplayerCountdown
{
}
}

View File

@ -93,14 +93,20 @@ namespace osu.Game.Online.Multiplayer
Task UserModsChanged(int userId, IEnumerable<APIMod> mods);
/// <summary>
/// Signals that a match is to be started. This will *only* be sent to clients which are to begin loading at this point.
/// Signals that the match is starting and the loading of gameplay should be started. This will *only* be sent to clients which are to begin loading at this point.
/// </summary>
Task LoadRequested();
/// <summary>
/// Signals that a match has started. All users in the <see cref="MultiplayerUserState.Loaded"/> state should begin gameplay as soon as possible.
/// Signals that loading of gameplay is to be aborted.
/// </summary>
Task MatchStarted();
Task LoadAborted();
/// <summary>
/// Signals that gameplay has started.
/// All users in the <see cref="MultiplayerUserState.Loaded"/> or <see cref="MultiplayerUserState.ReadyForGameplay"/> states should begin gameplay as soon as possible.
/// </summary>
Task GameplayStarted();
/// <summary>
/// Signals that the match has ended, all players have finished and results are ready to be displayed.

View File

@ -69,10 +69,15 @@ namespace osu.Game.Online.Multiplayer
/// </summary>
public virtual event Action? LoadRequested;
/// <summary>
/// Invoked when the multiplayer server requests loading of play to be aborted.
/// </summary>
public event Action? LoadAborted;
/// <summary>
/// Invoked when the multiplayer server requests gameplay to be started.
/// </summary>
public event Action? MatchStarted;
public event Action? GameplayStarted;
/// <summary>
/// Invoked when the multiplayer server has finished collating results.
@ -604,14 +609,27 @@ namespace osu.Game.Online.Multiplayer
return Task.CompletedTask;
}
Task IMultiplayerClient.MatchStarted()
Task IMultiplayerClient.LoadAborted()
{
Scheduler.Add(() =>
{
if (Room == null)
return;
MatchStarted?.Invoke();
LoadAborted?.Invoke();
}, false);
return Task.CompletedTask;
}
Task IMultiplayerClient.GameplayStarted()
{
Scheduler.Add(() =>
{
if (Room == null)
return;
GameplayStarted?.Invoke();
}, false);
return Task.CompletedTask;

View File

@ -14,6 +14,7 @@ namespace osu.Game.Online.Multiplayer
/// </summary>
[MessagePackObject]
[Union(0, typeof(MatchStartCountdown))] // IMPORTANT: Add rules to SignalRUnionWorkaroundResolver for new derived types.
[Union(1, typeof(ForceGameplayStartCountdown))]
public abstract class MultiplayerCountdown
{
/// <summary>

View File

@ -65,5 +65,21 @@ namespace osu.Game.Online.Multiplayer
}
public override int GetHashCode() => UserID.GetHashCode();
/// <summary>
/// Whether this user has finished loading and can start gameplay.
/// </summary>
public bool CanStartGameplay()
{
switch (State)
{
case MultiplayerUserState.Loaded:
case MultiplayerUserState.ReadyForGameplay:
return true;
default:
return false;
}
}
}
}

View File

@ -29,10 +29,16 @@ namespace osu.Game.Online.Multiplayer
WaitingForLoad,
/// <summary>
/// The user's client has marked itself as loaded and ready to begin gameplay.
/// The user has marked itself as loaded, but may still be adjusting settings prior to being ready for gameplay.
/// Players remaining in this state for an extended period of time will be automatically transitioned to the <see cref="Playing"/> state by the server.
/// </summary>
Loaded,
/// <summary>
/// The user has finished adjusting settings and is ready to start gameplay.
/// </summary>
ReadyForGameplay,
/// <summary>
/// The user is currently playing in a game. This is a reserved state, and is set by the server.
/// </summary>

View File

@ -54,7 +54,8 @@ namespace osu.Game.Online.Multiplayer
connection.On<MultiplayerRoomSettings>(nameof(IMultiplayerClient.SettingsChanged), ((IMultiplayerClient)this).SettingsChanged);
connection.On<int, MultiplayerUserState>(nameof(IMultiplayerClient.UserStateChanged), ((IMultiplayerClient)this).UserStateChanged);
connection.On(nameof(IMultiplayerClient.LoadRequested), ((IMultiplayerClient)this).LoadRequested);
connection.On(nameof(IMultiplayerClient.MatchStarted), ((IMultiplayerClient)this).MatchStarted);
connection.On(nameof(IMultiplayerClient.GameplayStarted), ((IMultiplayerClient)this).GameplayStarted);
connection.On(nameof(IMultiplayerClient.LoadAborted), ((IMultiplayerClient)this).LoadAborted);
connection.On(nameof(IMultiplayerClient.ResultsReady), ((IMultiplayerClient)this).ResultsReady);
connection.On<int, IEnumerable<APIMod>>(nameof(IMultiplayerClient.UserModsChanged), ((IMultiplayerClient)this).UserModsChanged);
connection.On<int, BeatmapAvailability>(nameof(IMultiplayerClient.UserBeatmapAvailabilityChanged), ((IMultiplayerClient)this).UserBeatmapAvailabilityChanged);

View File

@ -10,8 +10,8 @@ namespace osu.Game.Online
WebsiteRootUrl = APIEndpointUrl = @"https://osu.ppy.sh";
APIClientSecret = @"FGc9GAtyHzeQDshWP5Ah7dega8hJACAJpQtw6OXk";
APIClientID = "5";
SpectatorEndpointUrl = "https://spectator.ppy.sh/spectator";
MultiplayerEndpointUrl = "https://spectator.ppy.sh/multiplayer";
SpectatorEndpointUrl = "https://spectator2.ppy.sh/spectator";
MultiplayerEndpointUrl = "https://spectator2.ppy.sh/multiplayer";
}
}
}

View File

@ -24,7 +24,8 @@ namespace osu.Game.Online
(typeof(CountdownChangedEvent), typeof(MatchServerEvent)),
(typeof(TeamVersusRoomState), typeof(MatchRoomState)),
(typeof(TeamVersusUserState), typeof(MatchUserState)),
(typeof(MatchStartCountdown), typeof(MultiplayerCountdown))
(typeof(MatchStartCountdown), typeof(MultiplayerCountdown)),
(typeof(ForceGameplayStartCountdown), typeof(MultiplayerCountdown))
};
}
}

View File

@ -5,7 +5,6 @@ using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays.BeatmapSet;
@ -24,9 +23,6 @@ namespace osu.Game.Overlays
private readonly Bindable<APIBeatmapSet> beatmapSet = new Bindable<APIBeatmapSet>();
// receive input outside our bounds so we can trigger a close event on ourselves.
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
public BeatmapSetOverlay()
: base(OverlayColourScheme.Blue)
{
@ -71,12 +67,6 @@ namespace osu.Game.Overlays
beatmapSet.Value = null;
}
protected override bool OnClick(ClickEvent e)
{
Hide();
return true;
}
public void FetchAndShowBeatmap(int beatmapId)
{
beatmapSet.Value = null;

View File

@ -99,6 +99,8 @@ namespace osu.Game.Overlays.FirstRunSetup
private class NestedSongSelect : PlaySongSelect
{
protected override bool ControlGlobalMusic => false;
public override bool? AllowTrackAdjustments => false;
}
private class PinnedMainMenu : MainMenu

View File

@ -25,7 +25,6 @@ using osu.Game.Overlays.Mods;
using osu.Game.Overlays.Notifications;
using osu.Game.Screens;
using osu.Game.Screens.Menu;
using osu.Game.Screens.OnlinePlay.Match.Components;
namespace osu.Game.Overlays
{
@ -45,8 +44,8 @@ namespace osu.Game.Overlays
private ScreenStack? stack;
public PurpleTriangleButton NextButton = null!;
public DangerousTriangleButton BackButton = null!;
public ShearedButton NextButton = null!;
public ShearedButton BackButton = null!;
private readonly Bindable<bool> showFirstRunSetup = new Bindable<bool>();
@ -72,7 +71,7 @@ namespace osu.Game.Overlays
private Container content = null!;
[BackgroundDependencyLoader]
private void load()
private void load(OsuColour colours)
{
Header.Title = FirstRunSetupOverlayStrings.FirstRunSetupTitle;
Header.Description = FirstRunSetupOverlayStrings.FirstRunSetupDescription;
@ -84,7 +83,11 @@ namespace osu.Game.Overlays
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Horizontal = 70 * 1.2f },
Padding = new MarginPadding
{
Horizontal = 70 * 1.2f,
Bottom = 20,
},
Child = new InputBlockingContainer
{
Masking = true,
@ -117,14 +120,15 @@ namespace osu.Game.Overlays
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Width = 0.98f,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Margin = new MarginPadding { Vertical = PADDING },
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
ColumnDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize),
new Dimension(GridSizeMode.Absolute, 10),
new Dimension(GridSizeMode.AutoSize),
new Dimension(),
new Dimension(GridSizeMode.Absolute, 10),
},
RowDimensions = new[]
{
@ -134,21 +138,25 @@ namespace osu.Game.Overlays
{
new[]
{
BackButton = new DangerousTriangleButton
Empty(),
BackButton = new ShearedButton(300)
{
Width = 300,
Text = CommonStrings.Back,
Action = showPreviousStep,
Enabled = { Value = false },
DarkerColour = colours.Pink2,
LighterColour = colours.Pink1,
},
Empty(),
NextButton = new PurpleTriangleButton
NextButton = new ShearedButton(0)
{
RelativeSizeAxes = Axes.X,
Width = 1,
Text = FirstRunSetupOverlayStrings.GetStarted,
DarkerColour = ColourProvider.Colour2,
LighterColour = ColourProvider.Colour1,
Action = showNextStep
}
},
Empty(),
},
}
});

View File

@ -5,18 +5,18 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets;
using osuTK.Graphics;
using osuTK;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Localisation;
using osu.Game.Graphics.Containers;
namespace osu.Game.Overlays
{
public class OverlayRulesetTabItem : TabItem<RulesetInfo>
public class OverlayRulesetTabItem : TabItem<RulesetInfo>, IHasTooltip
{
private Color4 accentColour;
@ -26,7 +26,7 @@ namespace osu.Game.Overlays
set
{
accentColour = value;
text.FadeColour(value, 120, Easing.OutQuint);
icon.FadeColour(value, 120, Easing.OutQuint);
}
}
@ -35,7 +35,9 @@ namespace osu.Game.Overlays
[Resolved]
private OverlayColourProvider colourProvider { get; set; }
private readonly OsuSpriteText text;
private readonly Drawable icon;
public LocalisableString TooltipText => Value.Name;
public OverlayRulesetTabItem(RulesetInfo value)
: base(value)
@ -48,15 +50,14 @@ namespace osu.Game.Overlays
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(3, 0),
Child = text = new OsuSpriteText
Spacing = new Vector2(4, 0),
Child = icon = new ConstrainedIconContainer
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Text = value.Name,
Font = OsuFont.GetFont(size: 14),
ShadowColour = Color4.Black.Opacity(0.75f)
}
Origin = Anchor.Centre,
Size = new Vector2(20f),
Icon = value.CreateInstance().CreateIcon(),
},
},
new HoverClickSounds()
});
@ -70,7 +71,7 @@ namespace osu.Game.Overlays
Enabled.BindValueChanged(_ => updateState(), true);
}
public override bool PropagatePositionalInputSubTree => Enabled.Value && !Active.Value && base.PropagatePositionalInputSubTree;
public override bool PropagatePositionalInputSubTree => Enabled.Value && base.PropagatePositionalInputSubTree;
protected override bool OnHover(HoverEvent e)
{
@ -91,7 +92,6 @@ namespace osu.Game.Overlays
private void updateState()
{
text.Font = text.Font.With(weight: Active.Value ? FontWeight.Bold : FontWeight.Medium);
AccentColour = Enabled.Value ? getActiveColour() : colourProvider.Foreground1;
}

View File

@ -2,7 +2,10 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Rulesets;
using osuTK;
using osuTK.Graphics;
@ -23,7 +26,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
isDefault = value;
icon.FadeTo(isDefault ? 1 : 0, 200, Easing.OutQuint);
icon.Alpha = isDefault ? 1 : 0;
}
}
@ -42,15 +45,20 @@ namespace osu.Game.Overlays.Profile.Header.Components
public ProfileRulesetTabItem(RulesetInfo value)
: base(value)
{
Add(icon = new SpriteIcon
Add(icon = new DefaultRulesetIcon { Alpha = 0 });
}
public class DefaultRulesetIcon : SpriteIcon, IHasTooltip
{
public LocalisableString TooltipText => UsersStrings.ShowEditDefaultPlaymodeIsDefaultTooltip;
public DefaultRulesetIcon()
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Alpha = 0,
AlwaysPresent = true,
Icon = FontAwesome.Solid.Star,
Size = new Vector2(12),
});
Origin = Anchor.Centre;
Anchor = Anchor.Centre;
Icon = FontAwesome.Solid.Star;
Size = new Vector2(12);
}
}
}
}

View File

@ -31,7 +31,9 @@ namespace osu.Game.Overlays.Profile
User.ValueChanged += e => updateDisplay(e.NewValue);
TabControl.AddItem(LayoutStrings.HeaderUsersShow);
TabControl.AddItem(LayoutStrings.HeaderUsersModding);
// todo: pending implementation.
// TabControl.AddItem(LayoutStrings.HeaderUsersModding);
centreHeaderContainer.DetailsVisible.BindValueChanged(visible => detailHeaderContainer.Expanded = visible.NewValue, true);
}

View File

@ -77,7 +77,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
private void onRoomUpdated() => Scheduler.AddOnce(() =>
{
bool countdownActive = multiplayerClient.Room?.Countdown != null;
bool countdownActive = multiplayerClient.Room?.Countdown is MatchStartCountdown;
if (countdownActive)
{

View File

@ -55,7 +55,21 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
private void onRoomUpdated() => Scheduler.AddOnce(() =>
{
if (countdown != room?.Countdown)
MultiplayerCountdown newCountdown;
switch (room?.Countdown)
{
case MatchStartCountdown _:
newCountdown = room.Countdown;
break;
// Clear the countdown with any other (including non-null) countdown values.
default:
newCountdown = null;
break;
}
if (newCountdown != countdown)
{
countdown = room?.Countdown;
countdownChangeTime = Time.Current;

View File

@ -3,6 +3,7 @@
using System.Diagnostics;
using osu.Framework.Allocation;
using osu.Framework.Logging;
using osu.Framework.Screens;
using osu.Game.Online.Multiplayer;
using osu.Game.Screens.OnlinePlay.Components;
@ -20,6 +21,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
base.LoadComplete();
client.RoomUpdated += onRoomUpdated;
client.LoadAborted += onLoadAborted;
onRoomUpdated();
}
@ -35,6 +37,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
transitionFromResults();
}
private void onLoadAborted()
{
// If the server aborts gameplay for this user (due to loading too slow), exit gameplay screens.
if (!this.IsCurrentScreen())
{
Logger.Log("Gameplay aborted because loading the beatmap took too long.", LoggingTarget.Runtime, LogLevel.Important);
this.MakeCurrent();
}
}
public override void OnResuming(ScreenTransitionEvent e)
{
base.OnResuming(e);
@ -42,9 +54,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
if (client.Room == null)
return;
Debug.Assert(client.LocalUser != null);
if (!(e.Last is MultiplayerPlayerLoader playerLoader))
return;
// Nothing needs to be done if already in the idle state (e.g. via load being aborted by the server).
if (client.LocalUser.State == MultiplayerUserState.Idle)
return;
// If gameplay wasn't finished, then we have a simple path back to the idle state by aborting gameplay.
if (!playerLoader.GameplayPassed)
{

View File

@ -115,7 +115,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
if (!ValidForResume)
return; // token retrieval may have failed.
client.MatchStarted += onMatchStarted;
client.GameplayStarted += onGameplayStarted;
client.ResultsReady += onResultsReady;
ScoreProcessor.HasCompleted.BindValueChanged(completed =>
@ -144,10 +144,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
protected override void StartGameplay()
{
// block base call, but let the server know we are ready to start.
loadingDisplay.Show();
client.ChangeState(MultiplayerUserState.Loaded).ContinueWith(task => failAndBail(task.Exception?.Message ?? "Server error"), TaskContinuationOptions.NotOnRanToCompletion);
if (client.LocalUser?.State == MultiplayerUserState.Loaded)
{
// block base call, but let the server know we are ready to start.
loadingDisplay.Show();
client.ChangeState(MultiplayerUserState.ReadyForGameplay);
}
}
private void failAndBail(string message = null)
@ -175,7 +177,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
leaderboardFlow.Position = new Vector2(padding, padding + HUDOverlay.TopScoringElementsHeight);
}
private void onMatchStarted() => Scheduler.Add(() =>
private void onGameplayStarted() => Scheduler.Add(() =>
{
if (!this.IsCurrentScreen())
return;
@ -223,7 +225,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
if (client != null)
{
client.MatchStarted -= onMatchStarted;
client.GameplayStarted -= onGameplayStarted;
client.ResultsReady -= onResultsReady;
}
}

View File

@ -2,7 +2,11 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Logging;
using osu.Framework.Screens;
using osu.Game.Online.Multiplayer;
using osu.Game.Screens.Play;
namespace osu.Game.Screens.OnlinePlay.Multiplayer
@ -11,6 +15,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
{
public bool GameplayPassed => player?.GameplayState.HasPassed == true;
[Resolved]
private MultiplayerClient multiplayerClient { get; set; }
private Player player;
public MultiplayerPlayerLoader(Func<Player> createPlayer)
@ -18,6 +25,31 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
{
}
protected override bool ReadyForGameplay =>
base.ReadyForGameplay
// The server is forcefully starting gameplay.
|| multiplayerClient.LocalUser?.State == MultiplayerUserState.Playing;
protected override void OnPlayerLoaded()
{
base.OnPlayerLoaded();
multiplayerClient.ChangeState(MultiplayerUserState.Loaded)
.ContinueWith(task => failAndBail(task.Exception?.Message ?? "Server error"), TaskContinuationOptions.NotOnRanToCompletion);
}
private void failAndBail(string message = null)
{
if (!string.IsNullOrEmpty(message))
Logger.Log(message, LoggingTarget.Runtime, LogLevel.Important);
Schedule(() =>
{
if (this.IsCurrentScreen())
this.Exit();
});
}
public override void OnSuspending(ScreenTransitionEvent e)
{
base.OnSuspending(e);

View File

@ -112,6 +112,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
break;
case MultiplayerUserState.Loaded:
case MultiplayerUserState.ReadyForGameplay:
text.Text = "loaded";
icon.Icon = FontAwesome.Solid.DotCircle;
icon.Colour = colours.YellowLight;

View File

@ -92,11 +92,15 @@ namespace osu.Game.Screens.Play
!playerConsumed
// don't push unless the player is completely loaded
&& CurrentPlayer?.LoadState == LoadState.Ready
// don't push if the user is hovering one of the panes, unless they are idle.
&& (IsHovered || idleTracker.IsIdle.Value)
// don't push if the user is dragging a slider or otherwise.
// don't push unless the player is ready to start gameplay
&& ReadyForGameplay;
protected virtual bool ReadyForGameplay =>
// not ready if the user is hovering one of the panes, unless they are idle.
(IsHovered || idleTracker.IsIdle.Value)
// not ready if the user is dragging a slider or otherwise.
&& inputManager.DraggedDrawable == null
// don't push if a focused overlay is visible, like settings.
// not ready if a focused overlay is visible, like settings.
&& inputManager.FocusedDrawable == null;
private readonly Func<Player> createPlayer;
@ -364,7 +368,15 @@ namespace osu.Game.Screens.Play
CurrentPlayer.RestartCount = restartCount++;
CurrentPlayer.RestartRequested = restartRequested;
LoadTask = LoadComponentAsync(CurrentPlayer, _ => MetadataInfo.Loading = false);
LoadTask = LoadComponentAsync(CurrentPlayer, _ =>
{
MetadataInfo.Loading = false;
OnPlayerLoaded();
});
}
protected virtual void OnPlayerLoaded()
{
}
private void restartRequested()

View File

@ -27,8 +27,9 @@ namespace osu.Game.Screens.Select.Filter
[LocalisableDescription(typeof(SortStrings), nameof(SortStrings.ArtistTracksLength))]
Length,
[LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.ListingSearchFiltersRank))]
RankAchieved,
// todo: pending support (https://github.com/ppy/osu/issues/4917)
// [LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.ListingSearchFiltersRank))]
// RankAchieved,
[LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowInfoSource))]
Source,

View File

@ -5,11 +5,13 @@ using System;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Input.Bindings;
using osuTK;
using osuTK.Input;
namespace osu.Game.Screens.Select
{
@ -18,6 +20,9 @@ namespace osu.Game.Screens.Select
public Action NextRandom { get; set; }
public Action PreviousRandom { get; set; }
private Container persistentText;
private OsuSpriteText randomSpriteText;
private OsuSpriteText rewindSpriteText;
private bool rewindSearch;
[BackgroundDependencyLoader]
@ -25,7 +30,32 @@ namespace osu.Game.Screens.Select
{
SelectedColour = colours.Green;
DeselectedColour = SelectedColour.Opacity(0.5f);
Text = @"random";
TextContainer.Add(persistentText = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AlwaysPresent = true,
AutoSizeAxes = Axes.Both,
Children = new[]
{
randomSpriteText = new OsuSpriteText
{
AlwaysPresent = true,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Text = "random",
},
rewindSpriteText = new OsuSpriteText
{
AlwaysPresent = true,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Text = "rewind",
Alpha = 0f,
}
}
});
Action = () =>
{
@ -33,22 +63,22 @@ namespace osu.Game.Screens.Select
{
const double fade_time = 500;
OsuSpriteText rewindSpriteText;
OsuSpriteText fallingRewind;
TextContainer.Add(rewindSpriteText = new OsuSpriteText
TextContainer.Add(fallingRewind = new OsuSpriteText
{
Alpha = 0,
Text = @"rewind",
Text = rewindSpriteText.Text,
AlwaysPresent = true, // make sure the button is sized large enough to always show this
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
});
rewindSpriteText.FadeOutFromOne(fade_time, Easing.In);
rewindSpriteText.MoveTo(Vector2.Zero).MoveTo(new Vector2(0, 10), fade_time, Easing.In);
rewindSpriteText.Expire();
fallingRewind.FadeOutFromOne(fade_time, Easing.In);
fallingRewind.MoveTo(Vector2.Zero).MoveTo(new Vector2(0, 10), fade_time, Easing.In);
fallingRewind.Expire();
SpriteText.FadeInFromZero(fade_time, Easing.In);
persistentText.FadeInFromZero(fade_time, Easing.In);
PreviousRandom.Invoke();
}
@ -59,6 +89,44 @@ namespace osu.Game.Screens.Select
};
}
protected override bool OnKeyDown(KeyDownEvent e)
{
updateText(e.ShiftPressed);
return base.OnKeyDown(e);
}
protected override void OnKeyUp(KeyUpEvent e)
{
updateText(e.ShiftPressed);
base.OnKeyUp(e);
}
protected override bool OnClick(ClickEvent e)
{
try
{
// this uses OR to handle rewinding when clicks are triggered by other sources (i.e. right button in OnMouseUp).
rewindSearch |= e.ShiftPressed;
return base.OnClick(e);
}
finally
{
rewindSearch = false;
}
}
protected override void OnMouseUp(MouseUpEvent e)
{
if (e.Button == MouseButton.Right)
{
rewindSearch = true;
TriggerClick();
return;
}
base.OnMouseUp(e);
}
public override bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
{
rewindSearch = e.Action == GlobalAction.SelectPreviousRandom;
@ -79,5 +147,11 @@ namespace osu.Game.Screens.Select
rewindSearch = false;
}
}
private void updateText(bool rewind = false)
{
randomSpriteText.Alpha = rewind ? 0 : 1;
rewindSpriteText.Alpha = rewind ? 1 : 0;
}
}
}

View File

@ -155,7 +155,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
foreach (var u in Room.Users.Where(u => u.State == MultiplayerUserState.Loaded))
ChangeUserState(u.UserID, MultiplayerUserState.Playing);
((IMultiplayerClient)this).MatchStarted();
((IMultiplayerClient)this).GameplayStarted();
ChangeRoomState(MultiplayerRoomState.Playing);
}

View File

@ -35,7 +35,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Realm" Version="10.10.0" />
<PackageReference Include="ppy.osu.Framework" Version="2022.428.0" />
<PackageReference Include="ppy.osu.Framework" Version="2022.430.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.422.0" />
<PackageReference Include="Sentry" Version="3.14.1" />
<PackageReference Include="SharpCompress" Version="0.30.1" />

View File

@ -61,7 +61,7 @@
<Reference Include="System.Net.Http" />
</ItemGroup>
<ItemGroup Label="Package References">
<PackageReference Include="ppy.osu.Framework.iOS" Version="2022.428.0" />
<PackageReference Include="ppy.osu.Framework.iOS" Version="2022.430.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.422.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.428.0" />
<PackageReference Include="ppy.osu.Framework" Version="2022.430.0" />
<PackageReference Include="SharpCompress" Version="0.30.1" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />