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:
commit
2b4a49e17f
@ -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. -->
|
||||
|
@ -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)),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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?>
|
||||
|
@ -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]
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
{
|
||||
|
17
osu.Game/Online/Multiplayer/ForceGameplayStartCountdown.cs
Normal file
17
osu.Game/Online/Multiplayer/ForceGameplayStartCountdown.cs
Normal 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
|
||||
{
|
||||
}
|
||||
}
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
|
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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(),
|
||||
},
|
||||
}
|
||||
});
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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()
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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" />
|
||||
|
@ -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" />
|
||||
|
Loading…
Reference in New Issue
Block a user