From 6c28fd21c7bb7b61676c21c9ec11c8a84ef9e629 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 24 Feb 2020 20:52:15 +0900 Subject: [PATCH 01/85] osu-side changes --- .../Mods/ManiaModFlashlight.cs | 14 +++----------- .../Objects/Drawables/Pieces/BodyPiece.cs | 14 ++++---------- osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs | 14 ++++---------- .../Mods/TaikoModFlashlight.cs | 16 ++++------------ osu.Game/Graphics/UserInterface/LineGraph.cs | 14 ++++---------- .../UI/Scrolling/ScrollingHitObjectContainer.cs | 13 ++++--------- .../Edit/Compose/Components/DistanceSnapGrid.cs | 14 ++++---------- osu.Game/Screens/Play/SquareGraph.cs | 16 +++++++--------- 8 files changed, 34 insertions(+), 81 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs index 6893e1e73b..86a00271e9 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs @@ -3,8 +3,8 @@ using System; using osu.Framework.Bindables; -using osu.Framework.Caching; using osu.Framework.Graphics; +using osu.Framework.Layout; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mods; using osuTK; @@ -22,21 +22,13 @@ namespace osu.Game.Rulesets.Mania.Mods private class ManiaFlashlight : Flashlight { - private readonly Cached flashlightProperties = new Cached(); + private readonly LayoutValue flashlightProperties = new LayoutValue(Invalidation.DrawSize); public ManiaFlashlight() { FlashlightSize = new Vector2(0, default_flashlight_size); - } - public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) - { - if ((invalidation & Invalidation.DrawSize) > 0) - { - flashlightProperties.Invalidate(); - } - - return base.Invalidate(invalidation, source, shallPropagate); + AddLayout(flashlightProperties); } protected override void Update() diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs index 31a4857805..43f9ae2783 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs @@ -2,13 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Caching; using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; +using osu.Framework.Layout; using osu.Game.Graphics; namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces @@ -65,6 +65,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces } } }; + + AddLayout(subtractionCache); } protected override void LoadComplete() @@ -100,15 +102,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces } } - private readonly Cached subtractionCache = new Cached(); - - public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) - { - if ((invalidation & Invalidation.DrawSize) > 0) - subtractionCache.Invalidate(); - - return base.Invalidate(invalidation, source, shallPropagate); - } + private readonly LayoutValue subtractionCache = new LayoutValue(Invalidation.DrawSize); protected override void Update() { diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index 4e86662ec6..9a23a2be30 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -5,7 +5,6 @@ using System; using System.Diagnostics; using System.Runtime.InteropServices; using osu.Framework.Allocation; -using osu.Framework.Caching; using osu.Framework.Graphics; using osu.Framework.Graphics.Batches; using osu.Framework.Graphics.OpenGL.Vertices; @@ -14,6 +13,7 @@ using osu.Framework.Graphics.Shaders; using osu.Framework.Graphics.Textures; using osu.Framework.Input; using osu.Framework.Input.Events; +using osu.Framework.Layout; using osu.Framework.Timing; using osuTK; using osuTK.Graphics; @@ -43,6 +43,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor // -1 signals that the part is unusable, and should not be drawn parts[i].InvalidationID = -1; } + + AddLayout(partSizeCache); } [BackgroundDependencyLoader] @@ -72,20 +74,12 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor } } - private readonly Cached partSizeCache = new Cached(); + private readonly LayoutValue partSizeCache = new LayoutValue(Invalidation.DrawInfo | Invalidation.RequiredParentSizeToFit | Invalidation.Presence); private Vector2 partSize => partSizeCache.IsValid ? partSizeCache.Value : (partSizeCache.Value = new Vector2(Texture.DisplayWidth, Texture.DisplayHeight) * DrawInfo.Matrix.ExtractScale().Xy); - public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) - { - if ((invalidation & (Invalidation.DrawInfo | Invalidation.RequiredParentSizeToFit | Invalidation.Presence)) > 0) - partSizeCache.Invalidate(); - - return base.Invalidate(invalidation, source, shallPropagate); - } - /// /// The amount of time to fade the cursor trail pieces. /// diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index b7db3307ad..1253b7c8ae 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -2,8 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; -using osu.Framework.Caching; using osu.Framework.Graphics; +using osu.Framework.Layout; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.UI; @@ -30,13 +30,15 @@ namespace osu.Game.Rulesets.Taiko.Mods private class TaikoFlashlight : Flashlight { - private readonly Cached flashlightProperties = new Cached(); + private readonly LayoutValue flashlightProperties = new LayoutValue(Invalidation.DrawSize); private readonly TaikoPlayfield taikoPlayfield; public TaikoFlashlight(TaikoPlayfield taikoPlayfield) { this.taikoPlayfield = taikoPlayfield; FlashlightSize = new Vector2(0, getSizeFor(0)); + + AddLayout(flashlightProperties); } private float getSizeFor(int combo) @@ -56,16 +58,6 @@ namespace osu.Game.Rulesets.Taiko.Mods protected override string FragmentShader => "CircularFlashlight"; - public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) - { - if ((invalidation & Invalidation.DrawSize) > 0) - { - flashlightProperties.Invalidate(); - } - - return base.Invalidate(invalidation, source, shallPropagate); - } - protected override void Update() { base.Update(); diff --git a/osu.Game/Graphics/UserInterface/LineGraph.cs b/osu.Game/Graphics/UserInterface/LineGraph.cs index 6d65b77cbf..42b523fc5c 100644 --- a/osu.Game/Graphics/UserInterface/LineGraph.cs +++ b/osu.Game/Graphics/UserInterface/LineGraph.cs @@ -4,11 +4,11 @@ using System; using System.Collections.Generic; using System.Linq; -using osu.Framework.Caching; using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Lines; +using osu.Framework.Layout; using osuTK.Graphics; namespace osu.Game.Graphics.UserInterface @@ -83,17 +83,11 @@ namespace osu.Game.Graphics.UserInterface PathRadius = 1 } }); + + AddLayout(pathCached); } - public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) - { - if ((invalidation & Invalidation.DrawSize) > 0) - pathCached.Invalidate(); - - return base.Invalidate(invalidation, source, shallPropagate); - } - - private readonly Cached pathCached = new Cached(); + private readonly LayoutValue pathCached = new LayoutValue(Invalidation.DrawSize); protected override void Update() { diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 83a7f7289f..108f98d5fc 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Caching; using osu.Framework.Graphics; +using osu.Framework.Layout; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; @@ -19,11 +20,13 @@ namespace osu.Game.Rulesets.UI.Scrolling [Resolved] private IScrollingInfo scrollingInfo { get; set; } - private readonly Cached initialStateCache = new Cached(); + private readonly LayoutValue initialStateCache = new LayoutValue(Invalidation.RequiredParentSizeToFit | Invalidation.DrawInfo); public ScrollingHitObjectContainer() { RelativeSizeAxes = Axes.Both; + + AddLayout(initialStateCache); } [BackgroundDependencyLoader] @@ -55,14 +58,6 @@ namespace osu.Game.Rulesets.UI.Scrolling return result; } - public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) - { - if ((invalidation & (Invalidation.RequiredParentSizeToFit | Invalidation.DrawInfo)) > 0) - initialStateCache.Invalidate(); - - return base.Invalidate(invalidation, source, shallPropagate); - } - private float scrollLength; protected override void Update() diff --git a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs index 479de64eab..3a42938fc1 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs @@ -3,10 +3,10 @@ using System; using osu.Framework.Allocation; -using osu.Framework.Caching; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; +using osu.Framework.Layout; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osuTK; @@ -51,7 +51,7 @@ namespace osu.Game.Screens.Edit.Compose.Components [Resolved] private BindableBeatDivisor beatDivisor { get; set; } - private readonly Cached gridCache = new Cached(); + private readonly LayoutValue gridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); private readonly double? endTime; /// @@ -67,6 +67,8 @@ namespace osu.Game.Screens.Edit.Compose.Components StartTime = startTime; RelativeSizeAxes = Axes.Both; + + AddLayout(gridCache); } protected override void LoadComplete() @@ -92,14 +94,6 @@ namespace osu.Game.Screens.Edit.Compose.Components gridCache.Invalidate(); } - public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) - { - if ((invalidation & Invalidation.RequiredParentSizeToFit) > 0) - gridCache.Invalidate(); - - return base.Invalidate(invalidation, source, shallPropagate); - } - protected override void Update() { base.Update(); diff --git a/osu.Game/Screens/Play/SquareGraph.cs b/osu.Game/Screens/Play/SquareGraph.cs index a667466965..36ce131411 100644 --- a/osu.Game/Screens/Play/SquareGraph.cs +++ b/osu.Game/Screens/Play/SquareGraph.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using osu.Framework; -using osu.Framework.Caching; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -14,6 +13,7 @@ using osuTK; using osuTK.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Allocation; +using osu.Framework.Layout; using osu.Framework.Threading; namespace osu.Game.Screens.Play @@ -22,6 +22,11 @@ namespace osu.Game.Screens.Play { private BufferedContainer columns; + public SquareGraph() + { + AddLayout(layout); + } + public int ColumnCount => columns?.Children.Count ?? 0; private int progress; @@ -68,14 +73,7 @@ namespace osu.Game.Screens.Play } } - public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) - { - if ((invalidation & Invalidation.DrawSize) > 0) - layout.Invalidate(); - return base.Invalidate(invalidation, source, shallPropagate); - } - - private readonly Cached layout = new Cached(); + private readonly LayoutValue layout = new LayoutValue(Invalidation.DrawSize); private ScheduledDelegate scheduledCreate; protected override void Update() From f71c45cb1bb82b5f606e4465d2b514b6c02bda62 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 25 Feb 2020 11:30:33 +0900 Subject: [PATCH 02/85] Remove shallPropagate --- osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs | 2 +- osu.Game/Graphics/Backgrounds/Triangles.cs | 2 +- osu.Game/Screens/Menu/LogoVisualisation.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index 9a23a2be30..37df5ec540 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -91,7 +91,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor { base.Update(); - Invalidate(Invalidation.DrawNode, shallPropagate: false); + Invalidate(Invalidation.DrawNode); const int fade_clock_reset_threshold = 1000000; diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs index b9c7b26e3e..590e4b2a5c 100644 --- a/osu.Game/Graphics/Backgrounds/Triangles.cs +++ b/osu.Game/Graphics/Backgrounds/Triangles.cs @@ -129,7 +129,7 @@ namespace osu.Game.Graphics.Backgrounds { base.Update(); - Invalidate(Invalidation.DrawNode, shallPropagate: false); + Invalidate(Invalidation.DrawNode); if (CreateNewTriangles) addTriangles(false); diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index dcc68296f6..67537fa9df 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -150,7 +150,7 @@ namespace osu.Game.Screens.Menu frequencyAmplitudes[i] = 0; } - Invalidate(Invalidation.DrawNode, shallPropagate: false); + Invalidate(Invalidation.DrawNode); } protected override DrawNode CreateDrawNode() => new VisualisationDrawNode(this); From 334ec7bbd48ea58c524ba24f0ad4108db9d33dce Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 26 Feb 2020 15:06:30 +0900 Subject: [PATCH 03/85] Apply further framework changes --- osu.Game/Graphics/Containers/SectionsContainer.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/Containers/SectionsContainer.cs b/osu.Game/Graphics/Containers/SectionsContainer.cs index 9d886c457f..07a50c39e1 100644 --- a/osu.Game/Graphics/Containers/SectionsContainer.cs +++ b/osu.Game/Graphics/Containers/SectionsContainer.cs @@ -6,6 +6,7 @@ using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Layout; namespace osu.Game.Graphics.Containers { @@ -142,15 +143,17 @@ namespace osu.Game.Graphics.Containers public void ScrollToTop() => scrollContainer.ScrollTo(0); - public override void InvalidateFromChild(Invalidation invalidation, Drawable source = null) + protected override bool OnInvalidate(Invalidation invalidation, InvalidationSource source) { - base.InvalidateFromChild(invalidation, source); + var result = base.OnInvalidate(invalidation, source); - if ((invalidation & Invalidation.DrawSize) != 0) + if (source == InvalidationSource.Child && (invalidation & Invalidation.DrawSize) != 0) { - if (source == ExpandableHeader) //We need to recalculate the positions if the ExpandableHeader changed its size - lastKnownScroll = -1; + lastKnownScroll = -1; + result = true; } + + return result; } private float lastKnownScroll; From 7ad6ad0bb01fbc023a02080fd6b373571455a4e9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 27 Feb 2020 13:32:23 +0900 Subject: [PATCH 04/85] Remove hacks that bypassed layout shortcomings --- osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs | 10 ++-------- .../Select/Carousel/DrawableCarouselBeatmapSet.cs | 11 ++--------- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs index a7ed1f5846..6cd1aa912f 100644 --- a/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs @@ -246,10 +246,11 @@ namespace osu.Game.Screens.Multi FillMode = FillMode.Fill, Beatmap = { BindTarget = Beatmap } }, - new Container + new FillFlowContainer { Depth = -1, RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, // This makes the gradient not be perfectly horizontal, but diagonal at a ~40° angle Shear = new Vector2(0.8f, 0), Alpha = 0.5f, @@ -259,7 +260,6 @@ namespace osu.Game.Screens.Multi new Box { RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.Both, Colour = Color4.Black, Width = 0.4f, }, @@ -267,26 +267,20 @@ namespace osu.Game.Screens.Multi new Box { RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.Both, Colour = ColourInfo.GradientHorizontal(Color4.Black, new Color4(0f, 0f, 0f, 0.9f)), Width = 0.05f, - X = 0.4f, }, new Box { RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.Both, Colour = ColourInfo.GradientHorizontal(new Color4(0f, 0f, 0f, 0.9f), new Color4(0f, 0f, 0f, 0.1f)), Width = 0.2f, - X = 0.45f, }, new Box { RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.Both, Colour = ColourInfo.GradientHorizontal(new Color4(0f, 0f, 0f, 0.1f), new Color4(0, 0, 0, 0)), Width = 0.05f, - X = 0.65f, }, } } diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 1672131949..6cd145cfef 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -159,11 +159,11 @@ namespace osu.Game.Screens.Select.Carousel Origin = Anchor.Centre, FillMode = FillMode.Fill, }, - // Todo: This should be a fill flow, but has invalidation issues (see https://github.com/ppy/osu-framework/issues/223) - new Container + new FillFlowContainer { Depth = -1, RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, // This makes the gradient not be perfectly horizontal, but diagonal at a ~40° angle Shear = new Vector2(0.8f, 0), Alpha = 0.5f, @@ -173,7 +173,6 @@ namespace osu.Game.Screens.Select.Carousel new Box { RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.Both, Colour = Color4.Black, Width = 0.4f, }, @@ -181,26 +180,20 @@ namespace osu.Game.Screens.Select.Carousel new Box { RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.Both, Colour = ColourInfo.GradientHorizontal(Color4.Black, new Color4(0f, 0f, 0f, 0.9f)), Width = 0.05f, - X = 0.4f, }, new Box { RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.Both, Colour = ColourInfo.GradientHorizontal(new Color4(0f, 0f, 0f, 0.9f), new Color4(0f, 0f, 0f, 0.1f)), Width = 0.2f, - X = 0.45f, }, new Box { RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.Both, Colour = ColourInfo.GradientHorizontal(new Color4(0f, 0f, 0f, 0.1f), new Color4(0, 0, 0, 0)), Width = 0.05f, - X = 0.65f, }, } }, From d92e93ed3168502ef5e449343e40a4d4f5510054 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 27 Feb 2020 14:39:10 +0300 Subject: [PATCH 05/85] Move background creation out from UpdateStreamBadgeArea --- osu.Game/Overlays/Changelog/ChangelogHeader.cs | 14 ++++++++++++++ .../Overlays/Changelog/UpdateStreamBadgeArea.cs | 11 +---------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/osu.Game/Overlays/Changelog/ChangelogHeader.cs b/osu.Game/Overlays/Changelog/ChangelogHeader.cs index 8663ec586b..09bcd62021 100644 --- a/osu.Game/Overlays/Changelog/ChangelogHeader.cs +++ b/osu.Game/Overlays/Changelog/ChangelogHeader.cs @@ -4,9 +4,11 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API.Requests.Responses; @@ -22,6 +24,8 @@ namespace osu.Game.Overlays.Changelog private const string listing_string = "listing"; + private Box streamsBackground; + public ChangelogHeader() { TabControl.AddItem(listing_string); @@ -40,6 +44,12 @@ namespace osu.Game.Overlays.Changelog }; } + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + streamsBackground.Colour = colourProvider.Background5; + } + private ChangelogHeaderTitle title; private void showBuild(ValueChangedEvent e) @@ -72,6 +82,10 @@ namespace osu.Game.Overlays.Changelog AutoSizeAxes = Axes.Y, Children = new Drawable[] { + streamsBackground = new Box + { + RelativeSizeAxes = Axes.Both + }, Streams = new UpdateStreamBadgeArea(), } }; diff --git a/osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs b/osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs index 639c0d9780..faff7381c9 100644 --- a/osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs +++ b/osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs @@ -6,25 +6,16 @@ using osu.Framework.Input.Events; using osu.Game.Online.API.Requests.Responses; using System.Collections.Generic; using System.Linq; -using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Allocation; namespace osu.Game.Overlays.Changelog { public class UpdateStreamBadgeArea : TabControl { - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) + public UpdateStreamBadgeArea() { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - - AddInternal(new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background5, - }); } public void Populate(List streams) From a8c31c31add957afa4dd5f5c916115baeed094c2 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 27 Feb 2020 14:47:31 +0300 Subject: [PATCH 06/85] Move padding outside of the UpdateStreamBadgeArea --- .../Overlays/Changelog/ChangelogHeader.cs | 12 +++++++++++- .../Changelog/UpdateStreamBadgeArea.cs | 19 +++++-------------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/osu.Game/Overlays/Changelog/ChangelogHeader.cs b/osu.Game/Overlays/Changelog/ChangelogHeader.cs index 09bcd62021..f5b10ef0f2 100644 --- a/osu.Game/Overlays/Changelog/ChangelogHeader.cs +++ b/osu.Game/Overlays/Changelog/ChangelogHeader.cs @@ -86,7 +86,17 @@ namespace osu.Game.Overlays.Changelog { RelativeSizeAxes = Axes.Both }, - Streams = new UpdateStreamBadgeArea(), + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding + { + Horizontal = 85, + Vertical = 20 + }, + Child = Streams = new UpdateStreamBadgeArea() + } } }; diff --git a/osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs b/osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs index faff7381c9..d937bf775a 100644 --- a/osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs +++ b/osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs @@ -40,21 +40,12 @@ namespace osu.Game.Overlays.Changelog base.OnHoverLost(e); } - protected override TabFillFlowContainer CreateTabFlow() + protected override TabFillFlowContainer CreateTabFlow() => new TabFillFlowContainer { - var flow = base.CreateTabFlow(); - - flow.RelativeSizeAxes = Axes.X; - flow.AutoSizeAxes = Axes.Y; - flow.AllowMultiline = true; - flow.Padding = new MarginPadding - { - Vertical = 20, - Horizontal = 85, - }; - - return flow; - } + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + AllowMultiline = true, + }; protected override Dropdown CreateDropdown() => null; From f9aa6b9c07a99d5272302d8835b9a797c2277d0a Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 27 Feb 2020 15:33:01 +0300 Subject: [PATCH 07/85] Remove fadeContainer and adjust fade condition --- .../Overlays/Changelog/UpdateStreamBadge.cs | 69 +++++++++---------- 1 file changed, 31 insertions(+), 38 deletions(-) diff --git a/osu.Game/Overlays/Changelog/UpdateStreamBadge.cs b/osu.Game/Overlays/Changelog/UpdateStreamBadge.cs index 10aca31441..dc9d3ccaed 100644 --- a/osu.Game/Overlays/Changelog/UpdateStreamBadge.cs +++ b/osu.Game/Overlays/Changelog/UpdateStreamBadge.cs @@ -26,7 +26,6 @@ namespace osu.Game.Overlays.Changelog private readonly APIUpdateStream stream; - private Container fadeContainer; private FillFlowContainer text; private ExpandingBar expandingBar; @@ -44,47 +43,39 @@ namespace osu.Game.Overlays.Changelog AddRange(new Drawable[] { - fadeContainer = new Container + text = new FillFlowContainer { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Margin = new MarginPadding { Top = 6 }, + Children = new[] { - text = new FillFlowContainer + new OsuSpriteText { - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Margin = new MarginPadding { Top = 6 }, - Children = new[] - { - new OsuSpriteText - { - Text = stream.DisplayName, - Font = OsuFont.GetFont(size: 12, weight: FontWeight.Black), - }, - new OsuSpriteText - { - Text = stream.LatestBuild.DisplayVersion, - Font = OsuFont.GetFont(size: 16, weight: FontWeight.Regular), - }, - new OsuSpriteText - { - Text = stream.LatestBuild.Users > 0 ? $"{"user".ToQuantity(stream.LatestBuild.Users, "N0")} online" : null, - Font = OsuFont.GetFont(size: 10), - Colour = colourProvider.Foreground1 - }, - } + Text = stream.DisplayName, + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Black), }, - expandingBar = new ExpandingBar + new OsuSpriteText { - Anchor = Anchor.TopCentre, - Colour = stream.Colour, - ExpandedSize = 4, - CollapsedSize = 2, - IsCollapsed = true + Text = stream.LatestBuild.DisplayVersion, + Font = OsuFont.GetFont(size: 16, weight: FontWeight.Regular), + }, + new OsuSpriteText + { + Text = stream.LatestBuild.Users > 0 ? $"{"user".ToQuantity(stream.LatestBuild.Users, "N0")} online" : null, + Font = OsuFont.GetFont(size: 10), + Colour = colourProvider.Foreground1 }, } }, + expandingBar = new ExpandingBar + { + Anchor = Anchor.TopCentre, + Colour = stream.Colour, + ExpandedSize = 4, + CollapsedSize = 2, + IsCollapsed = true + }, new HoverClickSounds() }); @@ -112,21 +103,23 @@ namespace osu.Game.Overlays.Changelog // Expand based on the local state bool shouldExpand = Active.Value || IsHovered; + bool allHighlighted = SelectedTab.Value == null && !externalDimRequested; + // Expand based on whether no build is selected and the badge area is hovered - shouldExpand |= SelectedTab.Value == null && !externalDimRequested; + shouldExpand |= allHighlighted; if (shouldExpand) { expandingBar.Expand(); - fadeContainer.FadeTo(1, transition_duration); + expandingBar.FadeTo(1, transition_duration, Easing.OutQuint); } else { expandingBar.Collapse(); - fadeContainer.FadeTo(0.5f, transition_duration); + expandingBar.FadeTo(0.5f, transition_duration, Easing.OutQuint); } - text.FadeTo(externalDimRequested && !IsHovered ? 0.5f : 1, transition_duration); + text.FadeTo(IsHovered || (Active.Value && !externalDimRequested) || allHighlighted ? 1 : 0.5f, transition_duration, Easing.OutQuint); } private bool externalDimRequested; From 377ae3e685466fa1dd4c2519f102a29a8f7336fc Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 28 Feb 2020 12:48:06 +0300 Subject: [PATCH 08/85] Make a separate if section for all highlighted case --- .../Overlays/Changelog/UpdateStreamBadge.cs | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/osu.Game/Overlays/Changelog/UpdateStreamBadge.cs b/osu.Game/Overlays/Changelog/UpdateStreamBadge.cs index dc9d3ccaed..5e10410f37 100644 --- a/osu.Game/Overlays/Changelog/UpdateStreamBadge.cs +++ b/osu.Game/Overlays/Changelog/UpdateStreamBadge.cs @@ -100,26 +100,20 @@ namespace osu.Game.Overlays.Changelog private void updateState() { - // Expand based on the local state - bool shouldExpand = Active.Value || IsHovered; - - bool allHighlighted = SelectedTab.Value == null && !externalDimRequested; - - // Expand based on whether no build is selected and the badge area is hovered - shouldExpand |= allHighlighted; - - if (shouldExpand) + if (SelectedTab.Value == null && !externalDimRequested) { expandingBar.Expand(); expandingBar.FadeTo(1, transition_duration, Easing.OutQuint); - } - else - { - expandingBar.Collapse(); - expandingBar.FadeTo(0.5f, transition_duration, Easing.OutQuint); + text.FadeTo(1, transition_duration, Easing.OutQuint); + return; } - text.FadeTo(IsHovered || (Active.Value && !externalDimRequested) || allHighlighted ? 1 : 0.5f, transition_duration, Easing.OutQuint); + var shouldExpand = Active.Value || IsHovered; + + expandingBar.IsCollapsed = !shouldExpand; + expandingBar.FadeTo(shouldExpand ? 1 : 0.5f, transition_duration, Easing.OutQuint); + + text.FadeTo(IsHovered || (Active.Value && !externalDimRequested) ? 1 : 0.5f, transition_duration, Easing.OutQuint); } private bool externalDimRequested; From 5dff7f0955a9108290277a971e0b5c7b2b73266a Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 29 Feb 2020 02:21:52 +0300 Subject: [PATCH 09/85] Adjust horizontal padding --- osu.Game/Overlays/Changelog/ChangelogHeader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Changelog/ChangelogHeader.cs b/osu.Game/Overlays/Changelog/ChangelogHeader.cs index f5b10ef0f2..dcadbf4cf5 100644 --- a/osu.Game/Overlays/Changelog/ChangelogHeader.cs +++ b/osu.Game/Overlays/Changelog/ChangelogHeader.cs @@ -92,7 +92,7 @@ namespace osu.Game.Overlays.Changelog AutoSizeAxes = Axes.Y, Padding = new MarginPadding { - Horizontal = 85, + Horizontal = 65, Vertical = 20 }, Child = Streams = new UpdateStreamBadgeArea() From b9fef4f715717851998ec403582e46e6bf62207e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 1 Mar 2020 09:26:16 +0900 Subject: [PATCH 10/85] Update link location for appimage --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ae57b1d954..77c7eb9d2d 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ If you are looking to install or test osu! without setting up a development envi **Latest build:** -| [Windows (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.x86_64.AppImage) | [iOS(iOS 10+)](https://osu.ppy.sh/home/testflight) | [Android (5+)](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk) +| [Windows (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS(iOS 10+)](https://osu.ppy.sh/home/testflight) | [Android (5+)](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk) | ------------- | ------------- | ------------- | ------------- | ------------- | - When running on Windows 7 or 8.1, **[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/install/dependencies?tabs=netcore31&pivots=os-windows)** may be required to correctly run .NET Core applications if your operating system is not up-to-date with the latest service packs. From 89399b177c56e7e129a3f642c787ea384e9f0dca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Mar 2020 16:41:00 +0900 Subject: [PATCH 11/85] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 651d1beda1..983c622f77 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 168a358e47..c6c2708e64 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -23,7 +23,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 3c2a67e908..3552047cf8 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + @@ -79,7 +79,7 @@ - + From 5432371c74bf9c4787ae206ff154513abd8da60b Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2020 08:42:12 +0000 Subject: [PATCH 12/85] Bump Sentry from 2.0.3 to 2.1.0 Bumps [Sentry](https://github.com/getsentry/sentry-dotnet) from 2.0.3 to 2.1.0. - [Release notes](https://github.com/getsentry/sentry-dotnet/releases) - [Commits](https://github.com/getsentry/sentry-dotnet/compare/2.0.3...2.1.0) Signed-off-by: dependabot-preview[bot] --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 168a358e47..2b6ac585fa 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + From ee73f3e2b2c2fba744cd67fd499b6fd1ef5fb46c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Mar 2020 18:54:00 +0900 Subject: [PATCH 13/85] Change matching mode for global actions to better discern similar binds --- osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs | 5 +++-- osu.Game/Input/Bindings/GlobalActionContainer.cs | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs index ea274284ac..e83d899469 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs @@ -31,8 +31,9 @@ namespace osu.Game.Input.Bindings /// A reference to identify the current . Used to lookup mappings. Null for global mappings. /// An optional variant for the specified . Used when a ruleset has more than one possible keyboard layouts. /// Specify how to deal with multiple matches of s and s. - public DatabasedKeyBindingContainer(RulesetInfo ruleset = null, int? variant = null, SimultaneousBindingMode simultaneousMode = SimultaneousBindingMode.None) - : base(simultaneousMode) + /// Specify how to deal with exact matches. + public DatabasedKeyBindingContainer(RulesetInfo ruleset = null, int? variant = null, SimultaneousBindingMode simultaneousMode = SimultaneousBindingMode.None, KeyCombinationMatchingMode matchingMode = KeyCombinationMatchingMode.Any) + : base(simultaneousMode, matchingMode) { this.ruleset = ruleset; this.variant = variant; diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 7763577a14..bb0c586d73 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -15,6 +15,7 @@ namespace osu.Game.Input.Bindings private readonly Drawable handler; public GlobalActionContainer(OsuGameBase game) + : base(matchingMode: KeyCombinationMatchingMode.Modifiers) { if (game is IKeyBindingHandler) handler = game; From 489bf16beae7bbdb91e390db14b30f9c0ea33575 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Mar 2020 18:55:28 +0900 Subject: [PATCH 14/85] Add SelectNext and SelectPrevious global actions --- .../Input/Bindings/GlobalActionContainer.cs | 9 +++ osu.Game/Screens/Play/GameplayMenuOverlay.cs | 40 +++++--------- osu.Game/Screens/Select/BeatmapCarousel.cs | 55 +++++++++---------- 3 files changed, 50 insertions(+), 54 deletions(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index bb0c586d73..8f481d94c7 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -39,6 +39,9 @@ namespace osu.Game.Input.Bindings new KeyBinding(InputKey.Escape, GlobalAction.Back), new KeyBinding(InputKey.ExtraMouseButton1, GlobalAction.Back), + new KeyBinding(InputKey.Up, GlobalAction.SelectPrevious), + new KeyBinding(InputKey.Down, GlobalAction.SelectNext), + new KeyBinding(InputKey.Space, GlobalAction.Select), new KeyBinding(InputKey.Enter, GlobalAction.Select), new KeyBinding(InputKey.KeypadEnter, GlobalAction.Select), @@ -142,5 +145,11 @@ namespace osu.Game.Input.Bindings [Description("Toggle now playing overlay")] ToggleNowPlaying, + + [Description("Previous Selection")] + SelectPrevious, + + [Description("Next Selection")] + SelectNext, } } diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index 98e2bc5a03..6b37135c86 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -12,7 +12,6 @@ using osu.Game.Graphics; using osu.Framework.Allocation; using osu.Game.Graphics.UserInterface; using osu.Framework.Graphics.Shapes; -using osuTK.Input; using System.Collections.Generic; using System.Linq; using osu.Framework.Input.Bindings; @@ -204,35 +203,24 @@ namespace osu.Game.Screens.Play InternalButtons[selectionIndex].Selected.Value = true; } - protected override bool OnKeyDown(KeyDownEvent e) - { - if (!e.Repeat) - { - switch (e.Key) - { - case Key.Up: - if (selectionIndex == -1 || selectionIndex == 0) - setSelected(InternalButtons.Count - 1); - else - setSelected(selectionIndex - 1); - return true; - - case Key.Down: - if (selectionIndex == -1 || selectionIndex == InternalButtons.Count - 1) - setSelected(0); - else - setSelected(selectionIndex + 1); - return true; - } - } - - return base.OnKeyDown(e); - } - public bool OnPressed(GlobalAction action) { switch (action) { + case GlobalAction.SelectPrevious: + if (selectionIndex == -1 || selectionIndex == 0) + setSelected(InternalButtons.Count - 1); + else + setSelected(selectionIndex - 1); + return true; + + case GlobalAction.SelectNext: + if (selectionIndex == -1 || selectionIndex == InternalButtons.Count - 1) + setSelected(0); + else + setSelected(selectionIndex + 1); + return true; + case GlobalAction.Back: BackAction.Invoke(); return true; diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 7f36a23a86..1db97af2f0 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -16,15 +16,17 @@ using osu.Framework.Bindables; using osu.Framework.Caching; using osu.Framework.Threading; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; +using osu.Game.Input.Bindings; using osu.Game.Screens.Select.Carousel; namespace osu.Game.Screens.Select { - public class BeatmapCarousel : CompositeDrawable + public class BeatmapCarousel : CompositeDrawable, IKeyBindingHandler { private const float bleed_top = FilterControl.HEIGHT; private const float bleed_bottom = Footer.HEIGHT; @@ -435,41 +437,38 @@ namespace osu.Game.Screens.Select protected override bool OnKeyDown(KeyDownEvent e) { - // allow for controlling volume when alt is held. - // this is required as the VolumeControlReceptor uses OnPressed, which is - // executed after all OnKeyDown events. - if (e.AltPressed) - return base.OnKeyDown(e); - - int direction = 0; - bool skipDifficulties = false; - switch (e.Key) { - case Key.Up: - direction = -1; - break; - - case Key.Down: - direction = 1; - break; - case Key.Left: - direction = -1; - skipDifficulties = true; - break; + SelectNext(-1, true); + return true; case Key.Right: - direction = 1; - skipDifficulties = true; - break; + SelectNext(1, true); + return true; } - if (direction == 0) - return base.OnKeyDown(e); + return false; + } - SelectNext(direction, skipDifficulties); - return true; + public bool OnPressed(GlobalAction action) + { + switch (action) + { + case GlobalAction.SelectNext: + SelectNext(1, false); + return true; + + case GlobalAction.SelectPrevious: + SelectNext(-1, false); + return true; + } + + return false; + } + + public void OnReleased(GlobalAction action) + { } protected override void Update() From 3404b09010608459508bf0f7829adde00bae011d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Mar 2020 18:55:52 +0900 Subject: [PATCH 15/85] Bring database snapshot in line with specs --- osu.Game/Migrations/OsuDbContextModelSnapshot.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs index a6d9d1f3cb..bc4fc3342d 100644 --- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs +++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs @@ -43,7 +43,7 @@ namespace osu.Game.Migrations b.Property("ID") .ValueGeneratedOnAdd(); - b.Property("AudioLeadIn"); + b.Property("AudioLeadIn"); b.Property("BPM"); From ed516f05c2fb6d054e20cb6ffd0e343f5603f74f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Mar 2020 18:56:09 +0900 Subject: [PATCH 16/85] Forward any unhandled scroll events to volume overlay --- osu.Game/OsuGame.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index a73d5b57c4..5781a7fbc4 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -25,6 +25,7 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics.Sprites; using osu.Framework.Input; using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Framework.Platform; using osu.Framework.Threading; using osu.Game.Beatmaps; @@ -805,6 +806,13 @@ namespace osu.Game return d; } + protected override bool OnScroll(ScrollEvent e) + { + // forward any unhandled mouse scroll events to the volume control. + volume.Adjust(GlobalAction.IncreaseVolume, e.ScrollDelta.Y, e.IsPrecise); + return true; + } + public bool OnPressed(GlobalAction action) { if (introScreen == null) return false; From 88583b5c79c5c5becd55268ffc19ce6b1b76be5c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Mar 2020 18:56:41 +0900 Subject: [PATCH 17/85] Remove any existing bindings to increase/decrease volume --- ...02094919_RefreshVolumeBindings.Designer.cs | 506 ++++++++++++++++++ .../20200302094919_RefreshVolumeBindings.cs | 16 + 2 files changed, 522 insertions(+) create mode 100644 osu.Game/Migrations/20200302094919_RefreshVolumeBindings.Designer.cs create mode 100644 osu.Game/Migrations/20200302094919_RefreshVolumeBindings.cs diff --git a/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.Designer.cs b/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.Designer.cs new file mode 100644 index 0000000000..22316b0380 --- /dev/null +++ b/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.Designer.cs @@ -0,0 +1,506 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20200302094919_RefreshVolumeBindings")] + partial class RefreshVolumeBindings + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BPM"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("Length"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.Property("VideoFile"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Key") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("SkinInfoID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("SkinInfoID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("ScoreInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("ScoreInfoID"); + + b.ToTable("ScoreFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Accuracy") + .HasColumnType("DECIMAL(1,4)"); + + b.Property("BeatmapInfoID"); + + b.Property("Combo"); + + b.Property("Date"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MaxCombo"); + + b.Property("ModsJson") + .HasColumnName("Mods"); + + b.Property("OnlineScoreID"); + + b.Property("PP"); + + b.Property("Rank"); + + b.Property("RulesetID"); + + b.Property("StatisticsJson") + .HasColumnName("Statistics"); + + b.Property("TotalScore"); + + b.Property("UserID") + .HasColumnName("UserID"); + + b.Property("UserString") + .HasColumnName("User"); + + b.HasKey("ID"); + + b.HasIndex("BeatmapInfoID"); + + b.HasIndex("OnlineScoreID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("ScoreInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Settings") + .HasForeignKey("SkinInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Scoring.ScoreInfo") + .WithMany("Files") + .HasForeignKey("ScoreInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") + .WithMany("Scores") + .HasForeignKey("BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.cs b/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.cs new file mode 100644 index 0000000000..ec4475971c --- /dev/null +++ b/osu.Game/Migrations/20200302094919_RefreshVolumeBindings.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class RefreshVolumeBindings : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql("DELETE FROM KeyBinding WHERE action in (6,7)"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + } + } +} From 6989738710cc6e3da689b26087ab9f6f3dc09295 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Mar 2020 18:59:05 +0900 Subject: [PATCH 18/85] Change default global bindings for volume changing to include the alt key prefix --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 8f481d94c7..71771abede 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -58,10 +58,11 @@ namespace osu.Game.Input.Bindings public IEnumerable AudioControlKeyBindings => new[] { - new KeyBinding(InputKey.Up, GlobalAction.IncreaseVolume), - new KeyBinding(InputKey.MouseWheelUp, GlobalAction.IncreaseVolume), - new KeyBinding(InputKey.Down, GlobalAction.DecreaseVolume), - new KeyBinding(InputKey.MouseWheelDown, GlobalAction.DecreaseVolume), + new KeyBinding(new[] { InputKey.Alt, InputKey.Up }, GlobalAction.IncreaseVolume), + new KeyBinding(new[] { InputKey.Alt, InputKey.MouseWheelUp }, GlobalAction.IncreaseVolume), + new KeyBinding(new[] { InputKey.Alt, InputKey.Down }, GlobalAction.DecreaseVolume), + new KeyBinding(new[] { InputKey.Alt, InputKey.MouseWheelDown }, GlobalAction.DecreaseVolume), + new KeyBinding(InputKey.F4, GlobalAction.ToggleMute), new KeyBinding(InputKey.TrackPrevious, GlobalAction.MusicPrev), From e608d807f4c8db1c90741cf80fff56dc769f292a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Mar 2020 18:59:38 +0900 Subject: [PATCH 19/85] Handle SelectPrevious / SelectNext as volume change operations if nothing else handled game-wide --- .../Overlays/Volume/VolumeControlReceptor.cs | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Volume/VolumeControlReceptor.cs b/osu.Game/Overlays/Volume/VolumeControlReceptor.cs index 76fad945cc..4bff8146b4 100644 --- a/osu.Game/Overlays/Volume/VolumeControlReceptor.cs +++ b/osu.Game/Overlays/Volume/VolumeControlReceptor.cs @@ -14,8 +14,25 @@ namespace osu.Game.Overlays.Volume public Func ActionRequested; public Func ScrollActionRequested; - public bool OnPressed(GlobalAction action) => ActionRequested?.Invoke(action) ?? false; - public bool OnScroll(GlobalAction action, float amount, bool isPrecise) => ScrollActionRequested?.Invoke(action, amount, isPrecise) ?? false; + public bool OnPressed(GlobalAction action) + { + // if nothing else handles selection actions in the game, it's safe to let volume be adjusted. + switch (action) + { + case GlobalAction.SelectPrevious: + action = GlobalAction.IncreaseVolume; + break; + + case GlobalAction.SelectNext: + action = GlobalAction.DecreaseVolume; + break; + } + + return ActionRequested?.Invoke(action) ?? false; + } + + public bool OnScroll(GlobalAction action, float amount, bool isPrecise) => + ScrollActionRequested?.Invoke(action, amount, isPrecise) ?? false; public void OnReleased(GlobalAction action) { From 81191a3d20d6f7150bc201bf40149d160dd3b259 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Mar 2020 18:59:55 +0900 Subject: [PATCH 20/85] Handle SelectPrevious/SelectNext locally to volume meter when hovered --- osu.Game/Overlays/Volume/VolumeMeter.cs | 27 ++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index 7effd290e6..07accf8820 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -11,16 +11,18 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Input.Bindings; using osuTK; using osuTK.Graphics; namespace osu.Game.Overlays.Volume { - public class VolumeMeter : Container + public class VolumeMeter : Container, IKeyBindingHandler { private CircularProgress volumeCircle; private CircularProgress volumeCircleGlow; @@ -260,5 +262,28 @@ namespace osu.Game.Overlays.Volume { this.ScaleTo(1f, transition_length, Easing.OutExpo); } + + public bool OnPressed(GlobalAction action) + { + if (!IsHovered) + return false; + + switch (action) + { + case GlobalAction.SelectPrevious: + adjust(1, false); + return true; + + case GlobalAction.SelectNext: + adjust(-1, false); + return true; + } + + return false; + } + + public void OnReleased(GlobalAction action) + { + } } } From e890e454200dbccb263df546d37ba209cc5fb7b6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Mar 2020 19:40:32 +0900 Subject: [PATCH 21/85] Fix Ctrl+Enter behaviour regression --- osu.Game/Screens/Select/PlaySongSelect.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index e744fd6a7b..af113781e5 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -4,6 +4,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; using osu.Framework.Screens; using osu.Game.Graphics; using osu.Game.Screens.Play; @@ -49,6 +50,20 @@ namespace osu.Game.Screens.Select } } + protected override bool OnKeyDown(KeyDownEvent e) + { + switch (e.Key) + { + case Key.Enter: + // this is a special hard-coded case; we can't rely on OnPressed (of SongSelect) as GlobalActionContainer is + // matching with exact modifier consideration (so Ctrl+Enter would be ignored). + FinaliseSelection(); + return true; + } + + return base.OnKeyDown(e); + } + protected override bool OnStart() { if (player != null) return false; From 23068034b103e6e5ac5c320cbfa37d43c19bb17b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Mar 2020 19:51:06 +0900 Subject: [PATCH 22/85] Rename bool and make property for legibility --- .../Overlays/Changelog/UpdateStreamBadge.cs | 23 ++++++++++--------- .../Changelog/UpdateStreamBadgeArea.cs | 4 ++-- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/osu.Game/Overlays/Changelog/UpdateStreamBadge.cs b/osu.Game/Overlays/Changelog/UpdateStreamBadge.cs index 5e10410f37..97c58614b8 100644 --- a/osu.Game/Overlays/Changelog/UpdateStreamBadge.cs +++ b/osu.Game/Overlays/Changelog/UpdateStreamBadge.cs @@ -100,7 +100,7 @@ namespace osu.Game.Overlays.Changelog private void updateState() { - if (SelectedTab.Value == null && !externalDimRequested) + if (SelectedTab.Value == null && !allStreamsDimmed) { expandingBar.Expand(); expandingBar.FadeTo(1, transition_duration, Easing.OutQuint); @@ -113,21 +113,22 @@ namespace osu.Game.Overlays.Changelog expandingBar.IsCollapsed = !shouldExpand; expandingBar.FadeTo(shouldExpand ? 1 : 0.5f, transition_duration, Easing.OutQuint); - text.FadeTo(IsHovered || (Active.Value && !externalDimRequested) ? 1 : 0.5f, transition_duration, Easing.OutQuint); + text.FadeTo(IsHovered || (Active.Value && !allStreamsDimmed) ? 1 : 0.5f, transition_duration, Easing.OutQuint); } - private bool externalDimRequested; + private bool allStreamsDimmed; - public void EnableDim() + public bool AllStreamsDimmed { - externalDimRequested = true; - updateState(); - } + get => allStreamsDimmed; + set + { + if (value == allStreamsDimmed) + return; - public void DisableDim() - { - externalDimRequested = false; - updateState(); + allStreamsDimmed = value; + updateState(); + } } } } diff --git a/osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs b/osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs index d937bf775a..314f94101a 100644 --- a/osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs +++ b/osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs @@ -27,7 +27,7 @@ namespace osu.Game.Overlays.Changelog protected override bool OnHover(HoverEvent e) { foreach (var streamBadge in TabContainer.Children.OfType()) - streamBadge.EnableDim(); + streamBadge.AllStreamsDimmed = true; return base.OnHover(e); } @@ -35,7 +35,7 @@ namespace osu.Game.Overlays.Changelog protected override void OnHoverLost(HoverLostEvent e) { foreach (var streamBadge in TabContainer.Children.OfType()) - streamBadge.DisableDim(); + streamBadge.AllStreamsDimmed = false; base.OnHoverLost(e); } From 9f73b2960d39d6039054df3590fd32a5da0d781b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 2 Mar 2020 20:08:04 +0900 Subject: [PATCH 23/85] Prevent unnecessary auto-size computations in mania --- osu.Game.Rulesets.Mania/UI/ManiaStage.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs index a28de7ea58..bfe9f1085b 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs @@ -115,9 +115,8 @@ namespace osu.Game.Rulesets.Mania.UI { Anchor = Anchor.TopCentre, Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.Both, Y = HIT_TARGET_POSITION + 150, - BypassAutoSizeAxes = Axes.Both }, topLevelContainer = new Container { RelativeSizeAxes = Axes.Both } } From 69b47137311e121be5c1fdd9c7d865f570b548c4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Mar 2020 20:16:58 +0900 Subject: [PATCH 24/85] Refactor everything so I can read the code --- .../Graphics/UserInterface/ExpandingBar.cs | 20 +++++----- .../Overlays/Changelog/UpdateStreamBadge.cs | 40 +++++++++++-------- .../Changelog/UpdateStreamBadgeArea.cs | 4 +- 3 files changed, 36 insertions(+), 28 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/ExpandingBar.cs b/osu.Game/Graphics/UserInterface/ExpandingBar.cs index 439a6002d8..60cb35b4c4 100644 --- a/osu.Game/Graphics/UserInterface/ExpandingBar.cs +++ b/osu.Game/Graphics/UserInterface/ExpandingBar.cs @@ -13,17 +13,17 @@ namespace osu.Game.Graphics.UserInterface /// public class ExpandingBar : Circle { - private bool isCollapsed; + private bool expanded = true; - public bool IsCollapsed + public bool Expanded { - get => isCollapsed; + get => expanded; set { - if (value == isCollapsed) + if (value == expanded) return; - isCollapsed = value; + expanded = value; updateState(); } } @@ -83,19 +83,21 @@ namespace osu.Game.Graphics.UserInterface updateState(); } - public void Collapse() => IsCollapsed = true; + public void Collapse() => Expanded = false; - public void Expand() => IsCollapsed = false; + public void Expand() => Expanded = true; private void updateState() { - float newSize = IsCollapsed ? CollapsedSize : ExpandedSize; - Easing easingType = IsCollapsed ? Easing.Out : Easing.OutElastic; + float newSize = expanded ? ExpandedSize : CollapsedSize; + Easing easingType = expanded ? Easing.OutElastic : Easing.Out; if (RelativeSizeAxes == Axes.X) this.ResizeHeightTo(newSize, 400, easingType); else this.ResizeWidthTo(newSize, 400, easingType); + + this.FadeTo(expanded ? 1 : 0.5f, 100, Easing.OutQuint); } } } diff --git a/osu.Game/Overlays/Changelog/UpdateStreamBadge.cs b/osu.Game/Overlays/Changelog/UpdateStreamBadge.cs index 97c58614b8..6786bbc49f 100644 --- a/osu.Game/Overlays/Changelog/UpdateStreamBadge.cs +++ b/osu.Game/Overlays/Changelog/UpdateStreamBadge.cs @@ -74,7 +74,7 @@ namespace osu.Game.Overlays.Changelog Colour = stream.Colour, ExpandedSize = 4, CollapsedSize = 2, - IsCollapsed = true + Expanded = true }, new HoverClickSounds() }); @@ -100,33 +100,39 @@ namespace osu.Game.Overlays.Changelog private void updateState() { - if (SelectedTab.Value == null && !allStreamsDimmed) + // highlighted regardless if we are hovered + bool textHighlighted = IsHovered; + bool barExpanded = IsHovered; + + if (SelectedTab.Value == null) { - expandingBar.Expand(); - expandingBar.FadeTo(1, transition_duration, Easing.OutQuint); - text.FadeTo(1, transition_duration, Easing.OutQuint); - return; + // at listing, all badges are highlighted when user is not hovering any badge. + textHighlighted |= !userHoveringArea; + barExpanded |= !userHoveringArea; + } + else + { + // bar is always expanded when active + barExpanded |= Active.Value; + + // text is highlighted only when hovered or active (but not if in selection mode) + textHighlighted |= Active.Value && !userHoveringArea; } - var shouldExpand = Active.Value || IsHovered; - - expandingBar.IsCollapsed = !shouldExpand; - expandingBar.FadeTo(shouldExpand ? 1 : 0.5f, transition_duration, Easing.OutQuint); - - text.FadeTo(IsHovered || (Active.Value && !allStreamsDimmed) ? 1 : 0.5f, transition_duration, Easing.OutQuint); + expandingBar.Expanded = barExpanded; + text.FadeTo(textHighlighted ? 1 : 0.5f, transition_duration, Easing.OutQuint); } - private bool allStreamsDimmed; + private bool userHoveringArea; - public bool AllStreamsDimmed + public bool UserHoveringArea { - get => allStreamsDimmed; set { - if (value == allStreamsDimmed) + if (value == userHoveringArea) return; - allStreamsDimmed = value; + userHoveringArea = value; updateState(); } } diff --git a/osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs b/osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs index 314f94101a..ffb622dd37 100644 --- a/osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs +++ b/osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs @@ -27,7 +27,7 @@ namespace osu.Game.Overlays.Changelog protected override bool OnHover(HoverEvent e) { foreach (var streamBadge in TabContainer.Children.OfType()) - streamBadge.AllStreamsDimmed = true; + streamBadge.UserHoveringArea = true; return base.OnHover(e); } @@ -35,7 +35,7 @@ namespace osu.Game.Overlays.Changelog protected override void OnHoverLost(HoverLostEvent e) { foreach (var streamBadge in TabContainer.Children.OfType()) - streamBadge.AllStreamsDimmed = false; + streamBadge.UserHoveringArea = false; base.OnHoverLost(e); } From 90c2f7bd89620bc05cd0529f3a74780199e2fa0a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 3 Mar 2020 12:13:36 +0900 Subject: [PATCH 25/85] Fail tests by default --- osu.Game/Tests/Visual/ModSandboxTestScene.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/ModSandboxTestScene.cs b/osu.Game/Tests/Visual/ModSandboxTestScene.cs index a1fa757452..8bfa373e46 100644 --- a/osu.Game/Tests/Visual/ModSandboxTestScene.cs +++ b/osu.Game/Tests/Visual/ModSandboxTestScene.cs @@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual public override void TearDownSteps() { - AddUntilStep("test passed", () => currentTest?.PassCondition?.Invoke() ?? true); + AddUntilStep("test passed", () => currentTest?.PassCondition?.Invoke() ?? false); base.TearDownSteps(); } From 1e26df64b6cc022123222e4e104fbb382670037d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 3 Mar 2020 12:58:07 +0900 Subject: [PATCH 26/85] Fix constructor test failures --- osu.Game/Tests/Visual/ModSandboxTestScene.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/ModSandboxTestScene.cs b/osu.Game/Tests/Visual/ModSandboxTestScene.cs index 8bfa373e46..11612d0eca 100644 --- a/osu.Game/Tests/Visual/ModSandboxTestScene.cs +++ b/osu.Game/Tests/Visual/ModSandboxTestScene.cs @@ -37,7 +37,14 @@ namespace osu.Game.Tests.Visual public override void TearDownSteps() { - AddUntilStep("test passed", () => currentTest?.PassCondition?.Invoke() ?? false); + AddUntilStep("test passed", () => + { + if (currentTest == null) + return true; + + return currentTest.PassCondition?.Invoke() ?? false; + }); + base.TearDownSteps(); } From 2e4adc056f3e02d1e165945bc0b7f251cae50d65 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 3 Mar 2020 13:38:10 +0900 Subject: [PATCH 27/85] Fix potential deadlock during gameplay tests --- osu.Game/Screens/Play/GameplayClockContainer.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 1c061c215b..591e969ad8 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -123,6 +123,10 @@ namespace osu.Game.Screens.Play public void Restart() { + // The Reset() call below causes speed adjustments to be reset in an async context, leading to deadlocks. + // The deadlock can be prevented by resetting the track synchronously before entering the async context. + track.ResetSpeedAdjustments(); + Task.Run(() => { track.Reset(); From cb1129218135bd2b1e2d3a287d381c13e08725d6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 3 Mar 2020 16:06:31 +0900 Subject: [PATCH 28/85] Reduce social overlay/direct overlay paddings --- osu.Game/Overlays/SearchableList/SearchableListOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs b/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs index 0783c64c20..72796df6d5 100644 --- a/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs +++ b/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs @@ -15,7 +15,7 @@ namespace osu.Game.Overlays.SearchableList { public abstract class SearchableListOverlay : FullscreenOverlay { - public const float WIDTH_PADDING = 80; + public const float WIDTH_PADDING = 10; protected SearchableListOverlay(OverlayColourScheme colourScheme) : base(colourScheme) From f1f4f1ffbd29bfe1126159c217084e552058f3a8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Mar 2020 18:04:12 +0900 Subject: [PATCH 29/85] Add torus font --- .../Resources/Fonts/Aquatico-Light.bin | Bin 4998 -> 0 bytes .../Resources/Fonts/Aquatico-Light_0.png | Bin 68407 -> 0 bytes .../Resources/Fonts/Aquatico-Regular.bin | Bin 4994 -> 0 bytes .../Resources/Fonts/Aquatico-Regular_0.png | Bin 69000 -> 0 bytes .../Gameplay/Components/MatchHeader.cs | 4 +- .../Gameplay/Components/MatchScoreDisplay.cs | 4 +- .../Screens/TeamIntro/TeamIntroScreen.cs | 4 +- .../Screens/TeamWin/TeamWinScreen.cs | 8 +- osu.Game.Tournament/TournamentFont.cs | 75 ------------------ osu.Game.Tournament/TournamentGameBase.cs | 3 - osu.Game/Graphics/OsuFont.cs | 17 ++-- osu.Game/OsuGameBase.cs | 5 ++ 12 files changed, 22 insertions(+), 98 deletions(-) delete mode 100644 osu.Game.Tournament/Resources/Fonts/Aquatico-Light.bin delete mode 100644 osu.Game.Tournament/Resources/Fonts/Aquatico-Light_0.png delete mode 100644 osu.Game.Tournament/Resources/Fonts/Aquatico-Regular.bin delete mode 100644 osu.Game.Tournament/Resources/Fonts/Aquatico-Regular_0.png delete mode 100644 osu.Game.Tournament/TournamentFont.cs diff --git a/osu.Game.Tournament/Resources/Fonts/Aquatico-Light.bin b/osu.Game.Tournament/Resources/Fonts/Aquatico-Light.bin deleted file mode 100644 index 42cfdf08de4977e66acd9063ef31741f88715f0f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4998 zcmZ9O$&XZ36vof1YC$6UOoK#>Od^8_O?Lw-Gzv7>Ey|#X4HzR~kimgL8AMGq{sB0| zg_`Kb7$YVoy3mDjr7K z#f*s9A3y8r5Jg8vtUG>k&xynPj?Uh6_~4-vQ7Q9^$8;|`ioB$wI$eMNzivrh*R}WX zk=@;0$BrC~j*Al_W^2WY7>YvwN9>C^5v8uERR>~H^hY(Ogmx}(llEmXznB^~WlcMm zbETb+598j(x_)W5OZ!pR(%7bTH)!uD=jr$2%Es6j>+`c)rOlUipx72GV}rELxKG*w zY3CJVVoa4bHLjPoP};*vZANU#&njklhqUjrwmGjuTO{orJ$p;sEbXS4E$vQebBaYp zPb`r(GwzbMSlSQG7#nkpCDLxJ%qwn<30k)-RBPEKty-C1Op1xpwqy^MO5?wt*ePvt z-mhD~zo|s5j{Bu`#yUOQBki|p#Po(QGo|%PJ0lNzvIm`Ux3p!_K9{z%;R{|Zm-cq$ zP5EN)*GQ8d9~F;i9bQezo~o8Orx?o`aaU@c_KfeU=cBvvEc@LftxxNgx2)6Jc%kjt zRnms!)udRU7TTX{khY_Ef80#oFYTYoh3IWOi}wT4&dRHv7Hv@4CwdpwG-gtJm5Lp&SKk;HccZjp zs^i+mvo~nngVHXjM+@{UIcv_!hoqI2U*(>2mdE8LX}x;3tD&jip7%7g16sFJ z+6Bd3(a`W8fcn7dVbawao1Hst{k!(Wa(&-qd|@aIU3VIz4Zi9 zuW6`BthKhINyM?{taz3fqj?wNn)obS$r>;GmBhr6-&iJcHc^L(+Due#!VMESo2X+f zF}JQ4vImfTf$SB;e)aqW`wsJvWRJZzLtr8)Do+SR?ox=(@>Lm z-kN*P6Ye?Won$lY7ox_nh;Ab;Ai_-EhtYx|E5#Cc8mKM|F+lyLJuDPC|AU zBF;$S_jX1hLlSZlSq@ z5ViU4@g_NY?mByjtVR<5OyO?I#9b4aj%AHZ#}aGEY9td`nd}gE$;z6YM6rpLSZON_R^srgHccA%;GON{0(G2|~X@~J+?1$`T}Co-x1R75t)4a3nVd6ExDLp*L9Q3F2atV%>=pg- zevqR<)LTyw^_r;HM7<_w#X2e)%}-FJ$!8dMeTFduxG|O>GBA;qiA+soZ=xa-Rhp=L zEHO#!SI^{%&|D?t$|1V}v7et{KNI_zsAD+qM;*h7)o3XbT1reNaYr%{*F;e(XUNeYX8<`X$eHrSvTis*tQ*dmItE!eF63Mw=l369{|p2G diff --git a/osu.Game.Tournament/Resources/Fonts/Aquatico-Light_0.png b/osu.Game.Tournament/Resources/Fonts/Aquatico-Light_0.png deleted file mode 100644 index 332d9ca0564224402f3b28d295b9ec05d3736822..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 68407 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 Rd(f$N8tR(DYOlLK{x5_sc-8;_ diff --git a/osu.Game.Tournament/Resources/Fonts/Aquatico-Regular.bin b/osu.Game.Tournament/Resources/Fonts/Aquatico-Regular.bin deleted file mode 100644 index 3047c2eb3ef7354d250cf4c88b919269a696a06c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4994 zcmZ9OOKeqD6o%Kit%`~{S|k_|M0qG6rId;Sf>54Sd8&YDROBUyAdjLlFp-f%;s9}= zaUuzcP8m6IVB)}mF>0KsL~({OUL6+fhu4 zh&}PGKAocI?2MI12X~z~u=_~V%6#ovsnJ>Fi=7>l_5c4X=55`(fAG+*W8JSDIQ&vi z_Zx@zN9Uy*Bc^H1m0}UOQT3S~;F6{wn zOBLf*#USn-(jJudwNjfFPi0Mem9wR7&Dw^hHb>fD#q?rIOqMn=ly5m#+VNs$(Hrxn zO^rvSJtXa+W{mYY#yn|X*6u0pjhnTuH&jE}E$xW3QZ;nM`s~4cY5erY4r#aK{d)BM zy^e^-VvV#eIT7UoX+Ox9$(1kiv+R|&JU)(v*@G_C9_2!5zsEp_rk1 zyH&@`T<=n8|HK?=i>1xV+GEm|6|*W&XJ!wUN&7T(rRFWxw|T!> z+S%F}dF^#tQ?8MAQS-d8((r1nw83USCggssllE0DG>h(~?kg2LcGi9z6L&z`_c6B^ zU+veWbx%n9wqqb>D{Ib&=BIp8+SJ<5WA1%Q+9$fVr@B|YEjLKpq@1ajx!3-s&Pd#; z{aWW>le8=HYMt_{Y1Xt?xmnsfs;#fOm;P>%#(b=ct=g|Ew12r(-=Eh^F0R(8KjqWX zF2%Dt9X^BfP5#HJ_@kLK>-1F7&!@4ZWhS>t`JrV_)w6QDw0`yXaWuBRr zH>I5~HjkN?x1^0%ey#KJwzRV?XOI}Dq%CX9?}9X5V;eQ`%)REjykYfty#%HA>yr}1 zBf_KuUJ;K2YaDo!j$uU{WNRE>69?495j1hgOpKq20W&dHCWgtxxR@9c6Juba`6k+H zqJbt_WLhO^^KCS-W|uzoOmxEJxLkEEkn@9_Gu>n7M!we-an)5qt{k!(Wa(&-qd|@a zIU3VIy^RD>uW4J8SZi&2lZa!@S#d8hM)EGiHSt>bH)|aDe-jf&exsSl*+d;CYBN!} z2{%mSY@&|Q#N4`G$R0rU1+rHV`!(_%>}RqET(vI{bqwWo)G?IU6)j~#ONnzp+~G{D zFcH^8ToZ9k#5EDuL|hYH_&e`M7fhZeI^dik*8#aU$n`?x(nt{bH4=N_eLWNXs3%qv zt&xcprfp5)erxVIcev+}dk*2tNZtisOzt^X-E*D~tQ$%Y>xObJ(A7*_HQ5a^I-<{T zzG~Od>?CA&A>s@tzCWW+BNMvG6>-%SL9PgLMUX3k$Ymt&LVhM!#8p?sbI$HUb{ry? zMuJ#ta<|ajL5SLX^=OlvJy)GQL{`IzKU27xGI7;JrlVOS)6v8lvKr1rRwg^dRkE^X zC(-OKL{`2^RwiOx&HE9xrH@ z2ILr!V?d4pIR@kykYhlO0XYWb7`W{ikYhlO0XYWb7?5K?jsZCaM2u?*BF44EZ@-Bl zo|-9ZeiNhd1hVEgF`D1Rkl)0R-^7sL#E{>_5Yyvb{3b@*)71PXM%&la{3b^8n;7z& z81kDK@|zg)n;1H#kE7W?H2VkHKgj+;_7Aduko|+~AH-Z<%?_Pt67x~d+Q}x-MQd+1 z3D>XXCkVOtSweopi9h7neKga`CJ`fLjkqaM7rP8+;%Xz|Ct4$GtTQ=h?rqT=g@I8NiLv1d)M>tW0ET zB6|}RnW)l4<)ev7V!uWvSA^y&Ay*FB4T$}G2m6`W&qN(Vc|YnHN~}gpnb1;VGKo8! ziMS@>nuu#6u8EyY#5EDuL>K08{yK8|^C>DxKakmN(yaj?oad&A86e(`ODJ@#uNh$8ZgQhqXch8sW zxxe@Pll;hWdf;?m?b!i@C~W_nN+|D}{b|6n7L^wmPv&av0l`l3=X50eKR~J2GHE#2YyT_ER=caw?8c?h?3YfL zK;U8+Idm9`v4RZ2^ds>5Zc8zy%Tp^=D|RAj6;&()?=GPQm z_bscI5*ia4Q-4LD!;$-QU>y;%vIRz)o|HjwT4CJz$b|QQLh`=dKiGA8JSMHgsf@f^ z{GEFqmbKdz%6ub!;+%}JJ&HDiY}7~6D$KWuBXPJfYYP02Zz{2NjXW@+9}z)_f8^a` z7`o^Qd~Z2m_|7=a_|9Q+cjw}=#gaH3*zxGD*sg^#n9zo|ZI0to(EQ1`Zt;}x;xJFb zG=Mw}Mi+r|#8G56dHFS(WP%>-6M_rjY9)I2sv(<1)L%LY{#CNzdiE<+a8fP6^@D>I zr1T!UV;5f#|Y48wAPrJ~W4}fvBH$hjIVX z$Rv0mG#`LLG3^mj&H8)KbJ3ydB^6&`9MA~-{XIoY1W))y3N;ug{UNH?43=179RT0MZc^Nu5@(b-hf0fTWED2T+7TzKLXOK*Fi*d zghXF>n|VHJJgsl`kReRVNu9n0g)I{)Vdgu96DGavpf zC%kPV_)bX|z52xz_#IcDhVHDW1*0=`U}sX5=4~4pX4uN)c|H5Bto+MWg^5X(!Q#5v zi@qn{0^e<6`$lO#$GGR+=tmQ^tCb+81=fX6yVOsj9ca1OX4(<0dRfG1YUtl_QgaCS zl|qghA7Op>=q8Q)+09|+VQU=XHzIHLh6mtK1@tX$46SW`tvTmtLuobP?|jw}x)Zt+ zHN@*!Qx#ME&UhuBo;S&-XY&mcnwGdC+e}e1MrYX<;vb~y{PGEx4L&Yu&_}JR-6O8O zKsQQ^aANz_{^j0B|c&?5Ze6uNk7bwz`5v1vsKjJQ{<0_ zY5gx{bjyj%hS8%CQ7URc8XRTVg(m0!Ux0Q3^$Usu5nvv zhgGuJpziZ7QW8ZRWTpB<7g3X}heq-^hb{t`q?kxGP1=@R5NlO0*DPEe@%y0n z7RgVN8zmzVD>|-n%5djuHupi2!2p%2D&hGU_sPgG@r3qpR#@AiVVZz4?wuG!z0QRM zG>vWcMdl>-Gy5FM8Yz=_Q^5hTfa&NCW|HDdNW^{NaX$iMDfdhNDo->7VV(?##l+8l zh^#aP)sZYF`m$_gPDbEx*cCqhVr(!@4|s0QWtUm(T!BmO9{=k4_gJd+8EG5qghEC6 z+GuN4N%~BqzEuV@FqKP7Oo1o1Pi<(8$PXC#0pLnQ2yN*TQsFY%2-GZ2Lc8*X+?K_ z&@YpYw^ns6A|nlm@;P{r1zo|6U%!h9v>Y-h>GLO?1ANX%7cK6!8W7YMozIKWtA}=H&l=cx`=`Ke27d_9E&^?CEJ$PlYJhe|rRo z;G`v&PIe70O|D+bv=D==QiRA_7$n%G-Cx(!neeuxJb0yJTPhtR-)OM-2|4LvJ}I^Z zcNrzSgcYYvK0^=%)RClq(Mb{?g;mi+X&SN2y*hqcGk|F5e)UgYxF3|CQS>M?ve z8C~>wi#xqB+KucP(-ps1%%su1JYGv#`>MP_3z$ib$ zd&oOjJ*-a>cQ6r*)l25F`y=cMCqnzgas5Mooc$43x+2aFK(V1?-+22TAy2*)Q;>Ep zSWNx5CP|KZ@U7^tF=;l#(h8xF&qeLDjSNEMRdV@iPcdP-2t9jN5Y5c%o39EAB$moC z1zEeCwPYT}rZg_XHx%$r`+S@x&cK?azP&F`*>C6G=+B%M(M1(u*===+QSSK?iR{ba zKhRb=lA_7QnOzzx%=;=e1bmxSLpB5Md<-74HwECw#eicF&Wl6kB~4XZ{dw(8sUX%x z*HgDmms5l5<9ythBHzo5$)@;1`UYwAl|u>lU>{O zP-(-Kn!LluyZA(Rqvk8CS9raH*3jrDE2_GbzJ9 zs3nE}H|OYWi$cPU%Syd^Y&{^~2yAV1wf19K7W^CU((>3SSZXnTofs&+I-0Hn_(KMi zUKoYw@D5M`m!t|&C|xIslLH^)wblAF0&Y@>G6OM3jvkB?ZRR?FeKX;!;HHK*F%wzv zCh!$4@Lm)XWFcNpohK5-QjrCh#oyW&seknN#<$-ZAXJ!psV!WuHRg;v?R_@o{|OU2e!|TcDaG$o+Z8a21>7tuIlm* z1cIA1Bd++&ZA1C(!KB|)VWZBuT#UG0*{d%yvid_cjMw1c(zqLIXxLzx`?dEB16*6!!29qlVmx~%cc3(UR8AMRDu}Uo$~2O*+N!ZD zQlB&GoC9sb$%MOuufEjhC6Dcoy{30a#LQ!Em#__#2DK?{O8}Q%mJ)R)R*fF~q7n&q z1X~UnmuVWkABf!sT5a`d1MjnJLrMR8(bCjIYoPSdg~)_rSva_-ol<$B4Ehp;NNOl%vkA@tZ%q(hlfsz@#Nf6(cq>t0J8K=|c26WfC@- z;2|Q>Gyw+I9I2(lu}E+e2*+cFRV#ZNx1PzPBe}M!YZ>{5;?j8S;*ZX;v|CPGHaxn_ zZg7Nux`UmN**6=Wf^Q=PWOM@O@HqRb00V)wc|+i)kM(zT9eYzutFuUYaFa*uRo90h z*2ToJ1GW)g{nqv@9c{qcQkg=qVI7L<%cnPAREUE-n{2;t=mHs26w~#_4(vyKA{Oo! zW=H$KC%G4G0Y6qVGiDPJTdX!57=3mch~VZ6dd;2LL3I|?MJrZM$-lz(uP)cdwD%kR zqeZjVS`B!h5r6Z?X>C?ja*EmG*unQQ1r3V+Eck2sk+*x#4|W^8AeMUBtD*k8jv6WC z+)71U+D*5~`6AGofWBIIMNMTH7gO7CujI&YL;L8& zf2cV(+SKh|s{k$tm3P7THo__BS42QlHr!SQ<`JKN?7Vi{Y||?En|`pmmy>g>16)Z1 z+LU9go6p`HTeomo%69T8Nj^7^W)0N_{CNwM?u~`V)K_G6&b~^>Dhu~5jXUuA6~=YV z)IN6xbi9_68aue2TZn8N8GUkm`Ztwv-~E}()T{k>T~@|S!WMs4^UNjh(hBsUKXEU; z>bD)psl(f)4x7Pim<+2H6I%|C*h-ji%QRTveV}hKRGI~hK7h%?;|wGoC>UH`MfiSC zmec!KTHz;v?W5=Cx=L3|Pr#p=I;}yI973V_!y6Ralp=zGeaQepEaU3yf3J-AbqQ9X zqMKmk_47^j5axR#3F7?i?^dXOeQgVf`y}eH7!n_03;xXwn6Kp_IIn=Js(%92V}Arb z`L2xs0Q(E0T#nj2!(Q&MB0Lkh9-B+2-bu6Ig3lp>1x z`wDPUkU@eVr-}!r?(JG=Oj$5h6i~Y=BKvf||B6{>LT{i+#=|KQ=Sxj4r@-ZZ)y zz`#(>USq9xUgJ0})Qa}5Etcm;wOF%FV6vma$x{ZxOb}VS*|yyOt<_)F`WAbRP?ko> zpY_?_nOs}bzK7~a$`|eLFx@)R7<|D1rEF^zt!;1M%7=(`ESATy?z5!%RcCDrJ`F>g zBcQ)f=)>{EURetvszcJKsMvY|Dwq|XGcn(Cf7`Yz*}>8dW?(#eivh2A`Yt{9 zGE}BE)VK>GIFh|qe@0A5D7SEbg7a`FQZMVd@4H{Tg^}sT_HvpEB&{L+XRQ0X!13T6 zgmb9(%0a7Xtz_!m!fAOuF;J(-@K|n*QXKJ2i{|1aSgY($VY>4~Kn3n_XCjdDf_=nF z_-jHH6tl3MZ_3t$!|f`?)M zaT%xzvc(GtnUw9YE6Jg#MQ7i zie>5k78l5jZi-K{bB9s8j3+~CO@_SElT8)g*L zI&m+^AVEj<+J=y4hn3bLM%($cC78#jrgrk&nQoUwQ}2vdRsjVD;l45~XqVSdbu=tt z+c%$mHWJ(Tk>!$F z+nJK*vek(pGRHfil`b&vWidcK9^%*SlvW)yk?Nb%J7Op>QB#Qumh6DzwlDJO9sXwZ zUH+JRO8qfz^Q->$JQ0@l@}={~OozQDOKOh>5j;dSAW0Q4&l%@P;i|xwQn;L8m@%yW zjg3mJ019G9ajEH9KdUULR6a&md_?%nxmc}@6|)mNpWWBL_GyLiTRn8fg0P@MB4q_h z+4S*@@JrZFXA{F$=<(bCp04>3m?aerW-&4V#jGzK0+8{7L)FSj^U!jx0Pb1}0>I9D ze6Ktl$?;a&g>N=A5AW^!yoqN9!i7E!!1*^{oH3pF`353&Cg)xQ0EGiN2Ouk#?I7+P zVLbmCemdh2skYEF0)}SJk5$DF&t`UL&&heH3E3t96E9`xBzS6_QN4jVa{>lw%xkcD zX(_68TZQ@-!xkpb6J7v=TahCeH9|Y$9prjN!cMTt*a(JkR^zAEBhU2(Jo5>C0gkL| z?_Uj*cP9;hvZri^jF3hz*J7{Q({&(&GKr0&%>$2@&;V5bzUNqbd-Xw#AU<;jv%rT) z{LtaCceHGlnKkpOiwD#xHH6|-A5q{+6Z)9= zSu8X7@*L#s+$6s#tEX)Es{_h-W_iRmeHpa;4SOK>Jwd`P@i{KB$&%M`pu{9RWzE-O6|;JyGZO0QNJ+_ak6hyP8L1=?n*bNpK*<&i`Q? zUUP`CU+KlQ?$rn>t|qNKh4NLQv;DJX2i`BRr-16h&*sXkK3n*<1%R{6x;uY+8;S(H9bLn_5r}W1&p>6GD zeRSSgU_w=HaIl0Ls7|Is;m(JoynZST4>o(9=e-Jx2%+`HZk?G$C+-Q@^c8)c80Mzd5AVE9E8SEBQ9JAbFVNhp3WMm$8hP>*Tcn6+=5 z;Tg54_*T4Sv51Iej=54v zSO2^pKeH%95aLOoEeshv**7qAr^bba;-s51w!$7MLMI2oL`vv{YQ={F><K{`)-y+Fzs;7ImNRieCSrfnRM?%7w-wZT5zFhm5DAN5!%wmQc5sk zmx8%$&PMx3d94Lqz8li6p?PkoqE1MS*Q`=*wG2*}h;kxHA*ChNtb;uo!!T@D^695q z`Kvih7PcUa0J+#ivf;U#RglIFVV4AMVdPl!39$_llZnyz>Z45%XxyMX*eHv9$!JF;k$$1`*9+KZO(WUfJx9r)EEfT^ ze#zlxN>m;$U>GkOJb5kJ!8oJ3R*^55ZP&0}hqcIXX{Cn+;y>8F?3qUHge@^`nQEf! zlJ`#6116neeGjC>W^m>iSK*^!_nMSR(hKTaM%_QsThRDbvs)Le(A79XUga+;q64^ncbj^#Ww{VIl-GTWN!!f?W>PC@Tj zd-Hc%A(QVolSaVp!X&M4Kh?RUtUWg$(*>FbAC%jfQ=xYr1Wg$DP}F%0z|hS;37&;8 z$VfU4u+@a?`wWODtM~NT3(@z;M1jh#JsVi1{3OZ!y{LvBpj=QNbX&cF9c(h+&;(@= zmEvtmu``7X$fO_RdWTD0wKXp-jWMg2AS6IUQUZ_&OIzW`lyr&@=@6ADoaAQc}7H7C(DPdWmh3~vh zd55va>m|=uFYN3G`F;E5ca&xNO`H#0YuLHe`bW4>xi4IrF?K4fRJ%w1YW@=z>m_D; zfTdA&?6&Gc)#Sb28Z%O*-(Ndo(iwmTWc~Aap(KGow3b*uw|GxxZ-In$b0vq5)(%z{w&h|Eg$CwY@e0XB#>hK)j2EvuqGY&~BIyQxk)6$SlKSHwWH$A15TmUY zxFa*_I>DH2t1{o}stnHzWW1n z^q^>KTav&(RO==ru3OK1wa^r`>c_pz$Wy*Hq7xO`fHSq=K`b}lmlICDM5A`b{S zm*aEEueCa`^M2a?*aTPZ&v>bDcTL#a!D9WG@BKf`Im_#4yG3i~3jOYDJLh_Te6`hW93pVVL(ab6QO z%j0gl%S9kuAeI*O(vbT;rXAK;4C)=p)8ny>B=WPCXFRmFXcGOPCtCW3J^aNP0f$hA zzgaUOV(V^c)kR;X-hFkC+R?;qU9gN4%rhqE*Yis-;#@s{&)kF2ID1YLkbJ0j5qlCi znC9r-z#v_BWK2!(Xz{TLWX+TvA5-Vv<0BLAp6WtXV5if=Q(Yex_+gkSZsHQjY?FJV zq>Y;8EFQ%QdQY(-3Nl4lI6XK80g3Jx^k;had!Sj8p(z8f%vyH&@BbM{{mq~x#!i0) zwes10ub~Ft@klB1zPNn)RAw0CakW;TN%0bXZFBnEW8HG%VbJ=?kV={@^VWmh&S$Q# znc|WP-eREI>M6S3Nwnz11xbBlAlQJ4VQ=Syo`S-WB9AIFG#^${#qW+Tb2L^%***MZ z9Yooekz8?Sif;QI5!XpJY~SMv5B>5`Z&hMGhPgJ}XH1pWL>{+sZjlw+fi1(wF<u{Z!q)1xu#ou%8iO;2K zzi9?B-KpMh>?snV@gQ=@ax)Fu3t2;pAFGg%#LyX=?x}a8?lH;t23S^lwe2BL^1*kz zz=ZQNjuBZ`!oMSaCvqYMSe=y~n{quhg=&@>r!7-ei-=>8#r)xEFki^%0qmpFEuRQY zC{OK!$lyqU!OUY~lDoCCRj`)uj`Ads9`J!;@7KvdpBF%S1C?75O{o5vThj>g!W5$5 z&3DUJiwoPB2yPEW>h`+*XN`=K#(HaN9;&44s(})DYz_SS(+wtqSAXQ!gT1N5ot~j* zwVkbE)CzQoTIr>j$4cr^&Htcpgg=+5STqRuf%4U73lhv#ZMK1L5-Z*wJ(MQ^*9aiN z8sRzC@O%BUmTLctwA39uQmhs<*tG-apI1~HA41lq4CbAn{t0KDxAdAEq7J zUkr2%^XQAn&ZDdu)&F*ElcKeSF++x)>Q=2voKJ>(MV}@m6{i}iZNKk_(%A;n$att+ zzI4KTMSDVOMcY8_WRA!I^iN&GB5r%^O%IMWT;IYo!*0DGIAP>B%i;M-hH;g6ohQsU z{9U%a_4C4x1*n~K_N2mW3iK%9OyfkE^Ro6^@xe{znk?^|r&z&VQ;0VE@@pB=TRV&} zKWi;ngX@N!5Qpuj4*hHLh{8lu6W5QaUpAc6xsf%q~O!2{NXFhm85r(on6Q2& z9n-e#W6+f<4C%~O(Wa4!oGiZpf)33umRV5;Hs=)mFJ?{47d>aTT&W^2n#ItGSG>fK zcP@Ga-$mB(p>&OkuTcSLXd}49b2LJ^{-nCtnVu)iVej7~a>k%3{#ZXz8+CpcGwo`M zMQ1a>!X=HX%R~jq zJ~!=;u7Gn6@giZU(#@3f6?esB0wDzi*PCGP>dQ{JU5zpMk*mY?GrLA#*g4CD2%f%S zV4Z9ra5{)IZc*t7K7}|P-TzncMG}L48#FQh@PhsEQ-jZ*Q4~eT`=;hG z-XkN5oh_E$P9u_ef7Uqk|Dc>)kg0f8m=c~B)}FV+Jh%Hvh6t^Kon6VF*z#ycUxt@g zrgd`U^S~XeH_#9TYvSV#~$f&niDng_!Az5IP&)Gt0X-fchUl*e>$n?5&Oc z=Uwgn`bCxt$(~M)^*dBWrF&;o{=U3CaFKFmG6laVzVa`CvGEc$p%XP|JF}@f?fvSY zhVrg{6Op9K^!)uXzH_^W>+5F&z|}+%IvyPt3Fd`Z=O$&8_+{*cj7JrbGu5RM3r^Su zz9l|ZtLP3n(SfiU+{a?pf-aw0%26LRR%e(%6N&9E)_^J4AUVpvvx<63NRrlIuLw6$*k+?6%AOrns33N~t)uv3TiAYI zDj!aej`h#vykVHhz)lGZKJ^zrqQ4xdBu3-Il$ILq#(&_org!w?b{o+j&k>I@h^ny& z(QeRsQ(7VEP{JoT9p)MS7tlAbwV5I4jrZ~8$A}H42^*jA%rzc~{kzy=XL7%YZR+IX zIm!wS$8Y=axoQ5S)upIF-_bb8uLi$(W4(cgqoF(U%U{=K{|Xc8H2jAReCRJ$9n=i5 zY4G`-lu6(6nU863n){mtcWd9{>Nu;Sh|`g8DzmCEy+>?G&GlN!SNp@|67~MqQG%lM zu#MnEJEyPBN$6~PN2-hVA8h>Ce^uxgIsL*_}vxjgui3e_SsmGxJ=bE3ST2YK84D?I*FTKzzhg4kR z0G;uMNG#}C+n|9DfPABa9~GXB`>?L)8cdB7tPl4Ba?ML3wSX=^!m-yi@7l8SWTt0y zuw8p--_MRHKpoJxWDKS@uB7EQ%bjq8?(5HN{Cn1-S%SGrBR@te)4~h`65sQ<&aPXi zng9EnlXS7I9t2C>AVPywF&ki_=>-fHT_p!r4>WtGa9DQj0i@f8P_z?l=M~wlr{Dlq zf)5xpt(1ESOBXZSUJq-1rV&{*@NxZzG!9}3gQzrhz^4qZzTkvjJBMTWQ%EW$oDqiAsNpSu|gt?4_-IqbNO2KOuzL;Zx*}loO(y<-sVV~@GRuzto7fo zaUOx6imPu2*L0p!P4td!80MzF!%Hka5BdgBYs*P6q@{uM3@F?#0|XhPr&Y|Btp|#uaD4G zj_mDd#a?6C*+5NqaB&LXt?IMzEcP|3{@;XSmf#!K@WCfYJs6HIqu{!Z!6F+W=-JZ* z{gQCz{}+GrZPb1D0Nbf^WOpFOWTl$t#=^*rVK0qZB)49{z{~K86Mve%t+|z?6FRi7 zGQ2X^^*ny>8LInsvJhYJU*m*mw*1?EhRFVZbBzo#*WDaa39(vd>DQ?2syIE2mJ<+E zFi}SJ83WROjKjZ(5sXox_r(62s;6#2ydmcV5A&Z}=+rMqh5JW_(LVBbg)jY}s#p6U zavIqiFjo@1+?oL0d4B&XbB)jZlI(khVJ-8wksP-NAaqYBB(&FHUVk0aHgBVx^y1eA zQ5&sNxa##kxh~?$4d;4sM(JUizvu{1w6l#%Najg!WnOO}dM*=xvN5I$KK|aw5ft-= z^9spbHkzDpXMrmhd(+ONG>nr_=a8v={>99#sVsP^xjMlO`;Z~quA}__hF})`&FhVV zCa1ZK|J4vGn_OaBmmqhuVO><)s1wEbM+z_?_8F7eb^zx0eg`w?G*t$kQ3st~WwIy* z(V?X)3wG~sZioWjOU$3&p8gfk>&H6Fld@41cIdH;H4>Xnxf5~Luwo$ML+!PmgksyfFZheYT6L7l?Mn*`2 zV7)GUZ{#tQcun&>85)*rgZ~~Fq`K^69dhj_Pm@P(6QTx}JX+%?LQ~tgH-;oka0$N9 z`^(r8!a^)CGJ1Q%y-G{5?T(_NW=2)(at$W`vKl6EnHD#%1DU-0K)yW5qgA1HRPL8% zshZXD0X3D8IEE9PO4h{^oE60(qFN5LeR5IllU?C%dVUl!h^1-ivLUx(=F0=cp!j%J zvZHtU)-0O|*JO*X9?TxRL8ONhM=o@DrCCgY>!dt|S7q@pM z)CVNa>j31l_kjJ_W(`7=8bNB@avtrY|?KY)gjXIM+dX8E9Sk8Eohe z%LYw^W8~T7w07l?mRE34eM9j9lXY1GPFUY*X>b53@k!h^R?uAgvBByq7I`!=-_(3% zW6>LiwCdpri5bB}_9{?`eImNmi}m7uBs%@`jtg9pR~5ff^s{?I(LYf()i~2XU|yRu z+p0q{Rg6}1)cZg$Dldg zi>)Wi7guiQ<=|7A_ylR6Ni4In)ITf#mVA)S_W zt!XC!ckJ^Y^howN7SJT7{l`H!W_5h#jktn{dM^Q`jW73Kqj2dDaG7ZIUG=8+JS=+Z z#b!1@2CFPUr{Sxt)2P7ZD zb4NtefFE5%zm1bEK<#z965m0_E=v0TXB6T+vt$c) zLd^oM{Lhz36D9Y%h7D@6;Cjs7$9CY9+{=t`UhvgR!s8v^yvyT*a_PfgwR@fawj6+r zCIz{j$JZkigAZS_i1lEur4=XE-qW7zTZTZ!)S?wpweMLgyGhkUKf3+a91=}{Ipwt199Y0<#Q@(4+>@^~VI5_ygPa$CxRYk$|imRd4ihWGyiH(4bF zclE&6j#Xw;W-t+IH?PfdwImIVz83FT zVRUm5!vELs{ybTr#-|CUSyo`b0zqULt&g>Aa*N(~njwiPz5qriJ{T+XH=db-C5U+CJWD|h&7(t5TS6cs7zbjIN zFZv9B3YPn_c5~N(?lI_^fWtYHU+`bw3{Q`89rPk*9EmdL`+=%(G=KWJzt`74hk0ef} z+dGfCJrn=MmLd4gcmC0wqGng5v)pm*YKJ43ClfA}?wrPz;GVv$wD-Zzw4KM9XV1Oi zO&0@j^gv=-({oki$Oe4OoqFCwv{mj!9pYz{;~3=JCyOaqZU?^k@*3L2JL1B^Np3ncc>ByP4C<#FV_pa=VDE$H_WYEFvLdferz&OQxh>+Nj}eq5;?^d=J}H zI>Zh9PilX#2d%cEIDrw<<}RyP4yaR>qFp{GQeQvzJ(7Vn3$6lg>Z%_L2aC@ z@(h1pe0m)U;k}=C0PmR8-9NZ%UAqPW6mpug;TXJ&mCW+O-M=@iojgMQu}`X>^n^Q8 z>pEKNWyakvxMJr=5A?NI*Q`G8seH2$c{?HadKg7fo_2-m>$CqugKok~K0-_=JV8p4 z+iP{!9$59W?Dlh&BRDYU3S#+z;bAWR`F!R@aBkM2HMpYGen-l#8*Kb^%5NyN#&Va1 zQrh)2jeB3MY2&QP+_QQRiLdM+b=Z(t)90CBL9fTZ@Hb9ipAgl<7#3!*Pu!dSfVR;C z-H~yiBWSuYCtIn+woZRri8hd4Af>B4fHf|gThAdrk~C{=17e5{l(;iHoh+#}aQHZp?wNG<(G4h3mH{8bG0ywnH5x zt0MJo&zwC$z;;fB95DFP%ZeqwL4)yrB_72pnJHc8vCVJy;?s`KiXGIp_I}Z0gZ{!R3oeHJ&mn2Zlk0`` z*a2M%-;eK);bp#qRolKs2V*1YZYQ0ZMt=uTK^yfh`%$wdgp8{HL#2H9eEOLbF{!DP zl16D9Jh36g3g~Yn`{9g^*gMg3oa*xxiI$jf#g1jTi{ax$*&0xrg^#hf0pz%QDh4w_Wb~HCzeIprC!KB_9rWEIofpiz2Wq|L;5L;v#9d5vFUJ+v!uQ;(^4(;ezU}1tbZ=qv7U_7`U*~yO6#@kv=}&?hmk9oUp9u^j~I6M4@0B zIy9LCy{vfV&0(*ky|=Ss1vdUBwH0P;{)5um+-&>!Sq?|Vw(W{YiLF!WR3Gpbx1|^K zWS*Kmp0ntJiBM?hnbo9Go?nu_#g+IP#Dg3uq*j~QL{ipiv3l{hsalXI1kk4^GaU|} zLwUzN#Z)1*EF14>m`Dy1y7z-Oev^~h-hla)#yxI{XWBR<%GHYE#ZP{jSh;^?a1k^?7V<9*Ran#AcqGILRne?ZTOH=r}5pa;`gXu(1Nk&~N`Lf>5UZp9ak zGUl(MZDEfs@#h`wg9CjRgoA)IFV?@DgF@5{Mju?QpS3|&0}m&l=v3XzXf~trpTbue}l{lFMhxFoKWQq%GP zX<}bk`R?e{7cDSpB|?KKQlBZxjb_GzO0SSkXhVi4+7InO?`D&Ab8+z~C3I4$PO6sm z1RQ58Ol4#3GAw@ksjIYtbW+m-gk$NxjGvoVmvCI9VNcPdhRzbI;)CuKFpYi?$9+QoOj1}pu3{YjcJyPt)qXdDhbBLh7%E& z=Wy*5A{4*F?tC)~-3Bs}*niBPU6i3N&7>s#3U+B=m2)!k6O1v-o>$nIFKSjpZGHbz zH&C=o4mIW34sot{I4cRKgzn}%hGM+Hfhhh`J;JwVYLV>Ic~Y2|akWpb9Q09GV$xOq zO<|rA`uQ4dH<3d>rrKv*nesPnM?`Aw(_rJekZ6*dN>TxC(mz8uN9A={&F;R_;6mDb zi5MY7wIUU?`=0D0=N?DCF2>X6CfgQELvsEIi#E07an6kwRNj}RL^iSOR-#{ay&&R* z!If@fW<_ui>!GmVmQTP6xJk`Ye4+OVoo7|h&TczOTTf3%p zta_m{k~;x8&QKFDdofIqH}T$v303+*#8pB^@%@-8!i2JMJnVmL=>O~Aa|#a=r8kf) z-0vAqd~HUZF0q*IERdgy13&Vd#f6@;n1suJnVU$Y!3l}z5AwHI6Sy#!NZ3W&Q0S#c zBT+_QQ*6aJ^oHbs@Iir1N`cttpT`$JOT%8(69qaeW-fVOVvF*UXb>8UP87%%x!D-o z@hrYcv#EQNSa>0pQBdH);&d@(AJJmn%;X^d{3T&9iz!vtXN<{b-biVJQtIas)CSHN zHJ=oz>h+0YV9?;fg>&u5I;Vn6Vy8m1jFFcBacUKNd3ns+IxyMP${qgrOB;0=s?ac4 zCWWeyft5jrw3nM&@OVyexyQqfj+(qxu!DVXAy_K5iVa?MQ;0&!muqfFM6`% zqxi)tQ|9}XH?WrV=c01=Y=7G7i)t=I@f)ZwOaZ}Q6QZxY0?NXT+;;kQ>HIM=u2)<+ zPji_OO=M5W{nkINvgy$}DvE{yc-<&&bS5B zjj%`ocW1Yr8crAoeZ*0ojz@VQciZ@JVdCZ4?(Oxaw%BI4huij4sU0ts-h1PobDqM^ zqVGj`f2xWoZO;}kh_#JyY1H*6v@iJA5{VRwv!t7F=H8&X>c6}2M+T7=0mV9ObR`xA z>&a(3G-v0_!zevZ{d9PT%DhqxqHtGQ{_}Q#a0B(WKV>j+<(pqZU9JN zT;2lFcf1PS7pHeax?`^V)m%Tz$cZVlC_rBPjEN7ho{UAluxY6(%0frr+83}6zq1!I zN6%NITm{f3W!c+)6m0mmmp3}9OsdmfLjT3u6*kKNpg0ku`nRV)(d$b8WchGWz>W`BR(^pz@mbdYyYxH zSB;z!EGmFLW(AW?N8{wCz@&G*MfLy1p`d)j6G|{xE*&L#`PLdo_AY?pg=X4&6`5AfT3{vy3tJSDlX zw30u%iM1-VDB;@R`Iw;#e)<2N%s7XJ*RA4RU*_b?xse(z)GC+Ehca$2Qj@3u*8D#$ zz*(UnOrR}*p=y;x!By9ebN;G2e(9F%kaPuI2dY zkxXen*%j@=Y%?DsA@z+0Nm(L?HGC=j1|88N9Lrfn3BLgdGnN1U*%m!R^P^tdM@lv5 zMJxtbUaiW-aRYUIW)!Z zAyySr{&nvx^oJ(5TTra3v2a$!;;;@-Kz{+MmC2Lia@^U^YI&uZp+f*h&2KLhkje_= ze9!;)h;R*~)$+w^G7Vn>IDA#4VhX6==}FZa007FC|E1It^jo;?e35LtvuQCj&Q=Jk zcTCel&{PEK)(Qdr3mRX@wUL+Q z+yDQu(M&}x33{%B8BT16)9f1Z;7l!J22Uc0CCQ=JKHE6v{~t})z+OiiZR0cz8{0T- z)Y#4m8>g{t+qP}1jh)7}ZQDL^?)mP0?)-t7J@f9hwf35cXgEK7&3vpq1xx}!4Pr4a zFKSEDZov+JFm-AEFSrj>Jiu!72VnUiXLx%r+#)mRyBYesZ?~_54J|5MAwBsk+9k5Q zSfDZCd;Izud385d+TF^PaD4etfney;Y%6FLA}Pg!&!Ye&C${-Jb*}RSw5x{XE^6%u zg&mMndoMGBz_Djn=CLwRM{gdIsi$8R*6aJMmO+zoX z_d{lv@`G-?(hzMdo`>lSn4t5+JpA#BkL3lg_4%{6QwiVogXvFY76LV>bXsjHo=ulw zfdZIYYA$T1`m4;94HNc(8C_JCiE{)N9`G()b6GA=rn;(Mg|>-HayqhQe3rY`R`&Qg z-G3`+^-xY|L#!;G>*JHG^$I47>H$6`_7#>Us4(}Rz39WI;10`5?6{SI;8uZK|9I06 zwZnXqM`4U~ezy7t#FRf_hOjK2^^#o^_v^+pi>~A6dvMlXRwivpf+W!~vMjH`=@Vw8 zpVlRwfYWvGs-14&oyFVQI9zgGuELOo$)iowo3ELJkMjlr3x_VqaKg$X@XjP{(6sW> zaGq&;yvky{RL+wzg{ z*cCHTR^0FcrJ+r@_}=_6{wLM^*y96!Alse5I2TX)-yLqLG0V2+F&FOgdvm%O>?8X2 z+hSO981l^DS`Hp*OkeRe>D$d+CbWYw9k!Bn&kf_%nKQ}=va&6*B@0?iH;(U_`MLa0ZZw~e++Xf>%qyxqc^&th zQ8&0dd2V!IfQ~3qmK#wcW;VPGzV-&{kQ;%}2BX=r%sOmEn-De74hjedHrr=0w^%z- zR6cf!d*u5T8>;4}+8t5U-DFXDZw$(k>IlUyXOyeiUepd0nIQqBDeewW?n%xC{J%FS z?|Zz8snCBb@Aom7Wy*+a3BfZKTxkE7Q`k;YctQBUT(1!te~<9*CK{&a2@-(bl9|~Q z8D1tB!fe3W3CFZHp4bKWjCyH(r};+}8LwK@ecMS-snVZBl3s9* zHMMEK%#R#0gdW2hK+}WLUwQ*|lRZaZ^#89cgGs(M6f*MiLpFcUcTt{8ZEKtl*{7mK zj4!q}McK{|U-jYq81D5mG~H_#pp1ssy3;GM3kt|hV}Rwx6DltWHR!j;D@YeS6B!%V z#__oEfk>6xUwdgjf4+VkPzL!Vf`dv~{J!P=O%1m`2GlgU+Ew_rf&bBDtje)05}hs# zJeaVKcGG;?I>f@E1Hyuy((aj&{YEGL#KstTo*x-1C?aL@9wCd-wxWMLrU<1!TtP~dkz68bcE9t;EZ;1pXW(nN=%8eJJ^7%S|Rk61a zWo!m(O4_WAN=yXYx%sO86%Vkhr4eZWmOV%sXXKgOO10uMo2|jkKny}RZUI9DuH%|B zDzZ%`C6!H%+y3{+^Bh-4rF2VykNNL{!~Cfc`|V%XG3BceTR2VCM`d}=PK9?1ojZ}= z@*vdzL-1WSOzUDP9-$~h_w5R#vopckul#$QrHETi!^{pac5datU$$wFU&fK&CKMj; z0EJR`%emK43?WV7Ma8X`m8Doq9R)Dsfr=z<4lbXOPCfcRdY{N9pk;Aao9yM7c9Sx| zwzEro(D=v7ug1s5>+gB(r{8&u6_uBn(Zd(aD}d&SJ}LRl$3u(C7LwI?+dS{o}= z>*i{puTf1>?@+c)gDU&8MOw-&H9A9m4_d#`6z~lZe}p|YHPM0z>P;G7;M>Tj5?qdK zpAp?68lbv<_Dsfcmeq|59%N%S$Qa;NCBIt_U`A`V?%W@SzCFn>;bJ z%?Nn(!<7z`HSev=S~~mNvyT5O3@bV;nnuJit4H3*uDF@q2c{IBD@lf4QE`TyiChN4 zQ7vmH_Ji|V*dqVk>)eyZyc)EOk#AZTC-O@0)0e?9it55_{5K!UPJ{zW)|(FzP#2C6 z^za6ub46fIN0Cmbh`a?bc0JMGmb_1wr;ac~!hN47w6Ns1&zopY@;qY5QAwFdX(6|V*4Y^;^%7@>=p79o_ zm6R&T_18Cmmq!N(9b%|}pIhWcomJV-vSUS+gJ6o8&+s8o@XVLUkssLXJp$Z}^9PpNtox+?exiTg7jVpHG2aZXEWyCz z3LV6vW0KpJwg+jyZ8E9X^4@H9dN7cY?5R0#L>AM5&pB`$dL2vc@%#1*q4A%xva~jJ zoZ=661mwE8k_;%kpG(4nBTIf;@mANGGMx2P7eROR{ztwj1GVO`1Q26zDHn3gpl3QY zNInX(acQU4d70}C(9ksJ;b{D@BTbSFk?|Q9raP)zO90oLXCq>EcVo~2IU^64Fh8gw z6I0i{$MBBlmBXa&@N?(eq-j1Usk?$UXcsW&Tx|w<2QdUaXE-{M*LWF;g@qNZ4)cV* zs?>y0qYJ8qzy7Vqw;f5JD4SSB>lZo`#8~b9C);dV0Oeszgl%U$hK@QbXMNg}d(;HH z8GXRllTx?;8szyRBd%%bv6mhcOT!=V84uvJ{E|D6+DEJh^8w*joiMqUx9|qLT(cl- zH0;sf+J8gq_X%G6F^J;xvrBdMYXgr|O`JFGN~6H`mP~ zEo0N)nAv<5U3Fv}=_3G-*S-cHMjMS3ptm-12d4+f9c~rp9etJNp0IL*hNzd^tk_5p z^tq%$MpHO4P;7gXAm5djB^E#zw~G$*{KrA2{}EnU{?f>!9f znoy`|kMnc0MeB5EqN$WbO)=_q*C!6>j~fWO!{0vc<`&C8;<)nsm-ErxoPAUgcv)+L zCw;q35L4HTMwDicB~F9`o8*q<24}5`ps&5%(_I(nSAKo4k`r^UoU)y+BRk-ly%wwy z^t1==AVHNt_>!+%TFPJ|9{I0RW+ZN07Z1`|V70VfJ}WMs=_ruiIe&ueS${q=*Tcu8 zhmbp_cr&ZQSmr@K*gR=jBFiugl2VLW_j_tJWMIkH)GL^6AGj;{Awf;$Ur=%6hntj} zmr6Qf22S@|=z0a-cyP}QnaJpo>@PZ_uF`n2bz-vHBZ(zmi$M-V=$Y;-w!mIX{#vo< zO(#smHL-d|PeINUl=^XDp(tGRO382hVLpl`nQY*Oo_I#`>Cv zAM;v`Uv6@uejD##?ei0j+K3K`js!l;sHDqBu+|8Jk9Wa2_;`~FS1NfY=TfK+C_L3s zu^LKl4|R`qL*gx@r@Y7W`ijwhs6R9g&H^l6&$?~>f?2A%I4(HNV+=&LVfTe|`_cCF znfArFQJ#b%JXA!*a?376D-9@iRkh0zOxcatc3}7n^D6=i@Ns(d5{CS7V|e2t@-Y6* zhB)9wBY)&y-fMS<`Wy2_B9d)I9i;P}34X!&zA2G{WeJ%&w>QGYF&C~9Ac8DpysIU? z@_~s%+dntyq=7+L-+H++hXQ_jh#$~`tcc_sgBZhCd##)vLhGv=I<#uYc$^;+PNY>8 zConOBYtXl=)ypx7hX9`ly5W4GtvUlH)-WGTnL|D#VB%*3$(iKwGY>2fLjWF#ESDH? zXy??$QJ7&@LNSIwW9Gm@QB&oE$>Fz(u=R1f>1N19X-wdC)jgK^hTY2HLyXBt4Kw9> zHIcUUTDYxAJk_dhcDohBrT3_sb!u2K*mWnAPF(M)ul z99NyCHnWy8f?W9px8{cWQv z7?Vy$Tstr_)~$4VxreC27hDTaHQI$ljs&HxY$ZY|vu^c>AaBG>0JF-OrTl zm?@cNx_N9sySXx7{K(ko7b8w9nvy-l!B)D})p#1u9Rklr?k`%-8}<1cRi3~YBXbB` zlyg6=YB?d)vqpehKk~+>uR6$9yueA%*zpW6h?)#|G@}g-3qS6_ydK382NH^pU-6tn zEw*S2yv8aNYq%@=$AOdB!6Bpw2bqeVBWw8zV?7Sz6T1n!B`UOoU-?xIXwTXtVvd^Y z-1P7}qq<1-|Lm%;uPvO^?;ZqG|J8`yeT#PLht(2jMC6fDb?J@6n4V>$Vub9Tc z>!c_z@n!sbZ&ghSV}by#@X|?4~Z+F9p={roaV0=+QmNj z7lwR6PgtFsyfH`&u3S@*uifC!W@N;-Q(=$MnJg`b6hL_{$m=jWM$|!4Ll5e?_PU+ti zl6<4N66SgXR3riV9>TEl|#1=6o@s)1=`CLkM<|wP5 z*wfB>bL?Hi_YjLZd)Pe#UgDK)uJcFrW4CDTLJ>wOos zmn^zRhp}GuSkW@$3O*kEShA1CkrNN`yC_y3gJ1o%<7TXEovt=Qc6r{AIf*ZFbqSwr z!0hc?ZKFKP{!g+oAHvc+-Ujo4mxZ*TDqTP}tL~pF>a3Ark6v{-tqMO6Y|1Y0B!YyE z(_iqNdcTNO1asK(gGAkZ(B0M)j;e;|syPq~ta*tkKEYeA9~epT1AVZ7k6&%I9H-Ur zB3COW>FlGuTxPUsp)QeVzKr~a4>?9xSUgC%9Lu_2P$4pcPFtrWbz3g6jJwB$W3*VzR(|Qxa-^Z@D+DGk73s(bW?-U{ z3-bvzLjAZ^p`VMz&<4U9o}mf21@oEqg2P;YmA2A{u=dAZ>KBhDWtn3^x;Jgfrr1!3 ziHq&-W}5X}(WUl(ZImOh2vR6j64xc}S={Fkle6`4+1R+%Qr63UGDQr@A?49mlf{EL zL$KOK`)S!{F4x1IRwxn~Z%SfTm&@?aiE%ji#n(a@@AkRzMV1ar(9e2s>NEAT-k8D& z&)j4#+c}!8-v66(^84}+A>9cllKG|j&b{?D#Zg}Rnrw6DBPX@G+CB^uc0h6a!;nu* zldavNcTK&mr^zzoRa^epxQgSVt~Thj6+9JeP2B<9W~VLKXNnv?WG-MGK~DRtP*)eR z**uFV#I^83`yxR++UGc;Xq?14QnY`ojPqQlA$x7_hu>>XnG`J_p(T3{Et1NJ3E=ZV zuC#IIgF1xtAy9KceDfsR5@JXqAV+l2B0fcpCz$sD_-W1h0R&3^Jva-PdZR*??`q;z znj+nPT_$*+x4u}eeZ6uY_;<}~XkT7vc?$% z_ZnoYlon$!;C54FwvL&r>GtJa5@($yqH)^7d(mr7;nAcw&|W*})BDExzVn6TR^rwK z;a>t*?f|TOR`N5^!!Mz&g^|fe&52P7_?*sJZ(01Q~1l2&|eAtBS!1~%07o9pawAOI9BBT4HJSTNp+?Zgo-{q zo!Le1EpWlh$S|=K3Md(Rb{={#qHv_hu}jhhFLxKXjJHYIKZ!FwQ z!v#otsy+mJ4fEldYgF#7baS!w3UzYpv&8aMw{zQ7-soe9#q1TjXp1N7Xh5Czs6_IP zI_T>CN;t6+%{EGY5R&2s&my6}c{uYXrj87*%c2)=aB!bvr^1x5lQdCX&Tn;KtPg`_ zmjeH)+P41$GZp_-@GuIimgM6;b19Ft_M)HENc6#E$Q_HG)99OS@$dGq%~CKl5y?Dm zCc4}4?`)-LwC>Yg7ma3x88~Bt3s=u$X)qW9MZrUdcphsN1@_X!jUh9oeg2GveOx^( z@s&tP{^!IqG*(-u5f`LPU1 zv)*Sty=Pj-I5K)-x#VK#QDuzDpSL-7i;nq)=NZG$-x1rtqwsA4q!YD&jK~@RP!E`Q zn}r@sxLcR_(wP(NU>&Q{D#Lu8Ph}cl|70Hv5-VA9PI;nQM@#SOE3w=3bOq;=e7)sl z1|$7}`L=@ibBBMhQqw;;jo&B-)lXZ<>R^l~ewqERnpA-E6M#G`ExY5|reh5!h|>Cm zbNc*5F+;t6!hEn5YhnnpW{WigksbszKsqhh7hkz(%Dp$Ar$;_^Lk_R+C|!XL z6QK#oS!<+M9r6h_q4=@bUwG9ad9^09;2LG`QHYMqOyO3=5|0)Oc{$;`KnKctrd}&- zqmy_~n>~52`xclF?c=P{i7T1@MY=nivDdcybgUt^SPRZL-WRQ^n7?eHyNM|sjoDbg zt>RzQ^JD%yTk>@i5M;0pEmW4`LF0EnozORG<|A_|*Z{t0wdO#Gs{b;>CgXibjlb&q zl-q_Ce>NcoIL;NVu$Ye9B=n$WYrJ6Q+|_rGf>U!i8MT5zhR03Md9|8;hGqGlvUnfH_2>7S&R0YArmU&) z^g7R}Gxx`tP*0P?@$t*?=LV1|)NHvLmRiesJ-Z$MKqY;n5&gf9rhp#xTsq`Q^d-DO zhA4W?GJNTo7E4jC;^AbVr~}G9icA2TWj}M5MwIB%q2jr7TXK|GvBm<7Wphs_nhz8w z-%eJYiRyMg?pk($6N*Os_wL7KXH%7u<*zqNH7EIW@K)G&YHfO&Z5oAF=4h8~$2K6o zII;s2zdid{>{K@>_k+ZFXC3$o|BWXtn9LR+eJ4j0QPMW2=`Qht0NRpCmVT@J@3OdDj%cn}AYVPKDXHZPKFT;elN-&2t+o@Hd;j`rs#Ytw+G zqWz%m-sTU6-p7urA<_9d3#y_?fz6ipp<&5Z%!RQcZ1IPVl&BUa0BuS7GN!hG<>A&q zxA{k@5rvM{9FGZ3Cqq}sB>8l+Z!SLaw3P6iLB?cO%Zy*Rk7a- zopC-#X^+g|!tO(e00TE$Yt!IIlp0C$L90Wp{iiv`r6;KU${z{91@YMPcvi~yL8 zAN+FIeJQPr{u48}NRy}g=5`u6vEI{s;e9X5qQyUyc&Y~$rI>AK!i(dXE~GO4n^CxOg#wqo`84YW0 zbx=&N4vFXGskfkN@))O8p>@T4p1AegYGxsOejoP_sZG2|{d16Lx7Bv!jau|;1kl)! zl@Ty|M(vgxt?1p<|EMUvN_!{Sx9nW=(EdRb5ulbuyBD@a+m-;b>yoS2w>&X6hG2iV z@rdkGVb0KHiITCi@|Sr3;;@pffTzFUP@XimiOL9LD9JIRib$Cezgaue*8B z`c&WC-xc%37&0roDi<+(-W8%DVdgDJ|2&|3#9XE*JdFr=rPEV2_N{ad_>}I+&kpwy^WCE%AB9bT#fF18Jb+CpJkS$zY@GRu93y5C(e=a~ByGERERE`KXlSG`o zmN{!1I@06`X3uszO6J~T#zhU*&s2S{%B#@gWXk2Ee~?XI>KN%mSpN|g4{AO^v%pjP z1Z)n7$DL=h(|BC+VjJo-a-uHxw#dM1sH$bfR}LFBOuqEDY)C2NWQ=Fh*6>ki&9<${ z&o3vdhHs_t(Dr)U{-WIk2}<4fANmu=nP#wfB=N?v2 z5uV?$+`c3a@uF41WS1T#$SE#oPEf+H3 z;q@5TTK&_<^cRJYm=hGbw zFy2sfRg#6*$a^t+R~<=RzFL#tRy!wT1|Q4Myp&!`E>IRdUshQj3o zhGmp1ra{h|}@iWXqQC(oT8zPi=-aSG|(UI6h~2vtcYX+G{$$1$@znU_Vzm7*L@PxPzt$5P-mQv9{v&N}{=iC=vYc-gf%fN}Leag6 zRc`{74m^W1;hKfm-AMnd_k;N8f^PnqzIme>jBJm#{G?Qn&r(VX=ff8`(^{m-s~TgD zi=fOIlMx3yb9+u$O`7lve-fUkdZ(*rTzdA%QQ&gjHz6wFa`c2l+EW)fC|_ z_{czL<;zFSJ@$?0`gkV8e4GKLu+MV+bFKNwO>Q(-8YP;EOgQ4p*C^IL?`Cd+xe~Lx zk))g!U5KSe#m+PfLU2v8=N;H_A&Fs8Q2sciv~pK+)L{Z#&_FkxDMQl71EWz~R;1`%ge>7VZX9Z`I+nQGy6I2O zjgisqhN7PgVWZ8bwU|w=;pLs-#YW~X_^F8#u`=Dw!`?ZrgDOza77MB?Glq_OJd7+3 zhPb0}W}*T`h6ePKPhXOe`n_qKXdX*l)RpNYO3-{Uq{du|Gh+JBmkGt>;%ro_8^9UI zzW0{)MWgynDdQouwE3R}YI@%Kf`NiD zZ3J{U6=eCBZC*$yZ#s~i^1N~mSU@g{aT6~O#q9`?2YYV?d3yVfY`2$Arm-T+E_rk( z^tj7pGXdE+NyNBmst zhIyJ_kj`fpe^Fx=szd9ZN&!S{#;&hWMvybVoZFMf>qKrLKDxin} z6AuCS*ua@tt}NBYDc0B{4)TbCN~IiIO-x`Rvg3b_A5kC_;!gdpWtO&91c>m_GSw|add0WNItmZ*mWwVh3+I0ISg5JSiM|^@ode&Y zKIATpTssx{@44jzAc8yk_1Qc5SxYZ#sz|~zMB~V<+1__T*c21EB;IPWFzFew=TkDj z%ztUR0*DwK;>^7peXNbuZ)@Y7@_inDlx;6D@Emj8sV{qck9e5WUgwm# zd&qa4c@TG05e1=0jymO=ogM~f-w9L?_)(%2*=W-V1pZL1CDOfUM7tE(v?JZT#=RO) z)-VhlzdvI|{GeVC#Elt+_bQf}3?%>6oX5o-F3;4ltR6KZ#X73>fw#SqnSeZfiqwuTzt`FEPdPwVxSh>+ELzBwWVd!Sj5y+5U;?QKK`*Odm6K!A~{* zi*`Z`)PYk0YoW1!Awc%)2=49~5Rxb_3Yf18ZmvZ0ECY40j9d4 zLO4n6>`S${vt43~0gijDTaa;yS*^4qcN$tW?~$HpxAsi)NicF(3%T}oI{@h|@G~3P^N1@Qmt>4^3mj>;n!DQe+y$ zLhnH?G^!a#W^7KCh#5)a!FI2`f=s_%ZzZiE`;*f$F}271+lpprx@DcG;Vb=+wY`}+ z)j!^aMwJS63gGNJMEH(&EdAUi0p_3b$M!T-wV9ggIs7+o@>AknDSCLn@=XyIu+}_V z@a%T%a*(`GPo?I_r`Kf~UQ0#9+=V998-*%o z^@CBt(w z*?o;G18L126Jz`ziL~UN`Z)L65S7)pMMRllrx5${^)M3|jSsnqJDp3^m9HOv_0M-H z#}*$4Oj_XCI}0CQOU+NhJDpPIXJHj0Ya3d=YFdUk&>^-69ZPGDdFCbs#|b+VKIo=h zCVSy=5ogk<_C9HLwjoHu*Q0(ik&Xb>+zQ;LJgH zx>N&jOP+h`7Ugvn!E@A@k6R&QUv%9vieLuXyY%s)p$omzj8#rJWYMGy&Id4C zg?sSKNt<`p9B}lWVmLDZP=piw$yrW9D;GGo>xJ9z1ld}lNvMI;2kWQn^!|CiTqkBj zs{C7(QmM+rC zne~YjZMkAeeDrQ^e+9w3l(3@WsEDPfSMjDTiryCOL%4lw<9=uAPR}`&1q&Z ze~ZlD>_VjCO`(m1Mum*TZBoZ+4tD*ACPBX_gt$jJ_<3CAO&I3GS)WH@yH)k6`|r$l z$vHY&8g(C1MkKBnoV_?ulZ#J70@jSoi>zK@dPoWZH8%KssF= zB13rGkz!Pqwp7?;II_0-x=ocLCL8J#v&Y!~KKAcdcGrYI_NVz%2L~X~8_$Omh2G+J2tyom~@L{pCNf*~`pM>lsC_wT$Ti;;7 zfZq6=p;ok>-K4}O+>xkNWSsUiPkxGoA)8kNrxm;nd(_s~G#t&UpnW%TMJJh1i+d8@A%g;=YwXBNIR?46@)TYg(2c$Mf|6t_T zA52hBTpB;uXSQ({4jsjpngqlfg;cPqwX?&eGA7Ji4m9^4Hqedp2vMS<{#9ee8IE!g zh18~K+|z;2gj0lpokkteq1cqOGzdpJ_Q^xb5O)m`C{5?V28_79fO{KknV@fJF1JV` zlb_6JNk*p+Z(F`$)u;g~BRYJYC>`6bH`WvdNqkoCWA=Ja?RLubJnEbU5@M(~ebdI& z)>OR_@EZeTxh$qyPFR)m2@5QE1PY6)+TXFJ4UbGHnXGWg61Jpa$zP-_@O9zFHgdLt zHXZ&6%Z}+cKHRY z|H3>3EdbmNlyt}Yevs?ON1$hGmBXOR8aP83T01;ai{!xUjc!QdnZKCAUuy(`S-Hgu zuE0bUkS5`4!Uf*HX@VPwhj_nDVXjN`-z34lAK@U7Qgj3LH$kq(+{i1ejiMIyFS1S0 z!4o#woM7i0wlk3hc2(cu_TM4t{UvS=Td?sVjy158i$dfZ>R*0Nh7*pZIGi^tG}+M~ z3axZ@=^rN4vSa>ohw@$N#@^`-QKw8s@f#U-i(OkRW|S%?j?vg|_eYu!ZTtANOk#iK zsnZ?L4e1DZVk}bu1^)2mL0X8@RL3}>SC%&A$J6xeQW#EGjNYqRc$X7<=rr9{rK~10 z4z-GviT1*FLUC7AF62VEC>ylmQXVq@6MGh36)MNi9`)XHSL=<2G--gBr8nkML&xg{ zuBGqGo5t_dt-8t^j-#=)Qf9MJlD@NS)=Yr9yO8$e3O-&8eXLOY3Y;~*%$qR(7^wTv ze>BkiL|fU~Kj)JXZ?{#yBW=|KH|_l|+sV3jK>la5M&_PJ*829SLTj&y#1~WGEY8heELb(@1#dM@j1%;EmJn3Z9O~{D zGbZ&*CJhwM<(6ELG8<)(P&#&%6ZzgJ%H9qAdibHy2+R|f6mT-L0B?dnE$o!jwZ}zn zQLM{Ymv@iThPkm``<5CvYv3oRv|8J0%kJ8UJ@So{B7VFw>nu*G{+J6SeamiA0-pa$Lp~Q$Uw+u89^D`|YX<7)yK~Fa=Gk+Upw}GaA$?SG+hFF9xi2Xf z(~Va>gBLjk_Tfj2_-`Hkihx8?8-ho6WfQ%X&K-|`Dbwp~-5#v5%@v~b@ZCGh7W`Gc zd@t=J%r=!3TaDTyXU^D7KoG^eO36C9=2(M(;T6=k(AV09M^G=lHVbWp0qg5N_5G_| zF?4GB8l{5WlxNBVkGO_p6rsU=>k5pW1>xjMPi-A&f4_GA8M z1Fv%TGnQ;P-3xK_v~C)OaR29ILTv|=Myi#0mbAcqOl^B=XZKErvYh#cwowbK(#EgG zh93U1gG^eI{1lg}hC&sfJ<{>Sbbz5|QOkuC{6lop1M=(3*M(!#fsr!b8d`!9ws@F* zYu498#xmJ)tg*o1;4QNJ91A|rF8D|Bnf?NBe{S~%b?Wt%=7g@ui$^Y`@P39@9f@m( zo*vXG9JA2!2JiWAyiyjdeY#J)HZ$^!^rjO*qnn;A-p7e*hAETN~2i|5>(NqU1^D<3Z0S_MF8h36MH5K zrPFdvQkys?87u)olgy_daisbp1NW;$93d=gfV1$p|y)18oI{2vD<#qlR5c zkUAKfw#_ENIvb2U{mO93Q;Lzi(G{+-$q=FZ;VbJ*=8e-SIvE%eW1cE0Csx2kKpsd# zODxj(ip`Yw`TLn868;!@7wUbTkuIl)N+}cQ5W^Iu zo%1AcOy`CU8js3(vbI(G zaRdPIniJ~%AeOz}B)(EmT1dM4bN^lkQef~Cx@>$YnMUEex|T;lg&+8be(|KcXbCG{ zX|`Ji>)q&cfL|ll3P@z|*{ZX<_axNxF^XO~()OB&Bu-ir4v>cAexxWkK=CHq2|RG7 z56Z@mx?n!k6zzd4<4a*WchB&%8_H*b3I1B#U;Gvg8sU0&*s_r@SPsYTM=6* z4EANI=_=7j{uq43pKi)Wne5Hee4c-s$rI)Aj*i=DX8ojVEL)<41ct;}3>A^hN6yNbAJ1RH`;f(i5l~&;UrSmd zK?|n?mVZcYYW;HkM2TmoW!+GkfuWY*|0BJT<5r=vM!Fdi|JSh0*SE@@H<@YwuQ|CM zC^eiA2aN24zu60mR&qQ^bhme14kTP@+f&ul|0;DRqtyKbw6cbR9yn4;VzAbJGaVTM z2%3;x0qkZFKz!bV`Lo9jb@++Q$l*0tqU{gkm_<7&t{iElKU1Qq|0H`NyYr_Any-42 zhj#M;MD$qye*4HjyVG9k>hL=?ofKyL^w(Y^iJtWxv$w8=XrwSnS(PIB^MjP88~ceq z{kWTtZQwzF5nA(~ALC)qhF`_PN@H1NiYr!8y8M1!xD>d= zrlY2y0iu7%u+yU?rYN=tZdST86Kmp)^AA5?WyZ>HoWOrSe?`D7s!5{8hLy%-GH*s( z0=dL*UR9F@yGgs8aeSXB#>Q4?`EF$UCFC^+1=t1NuZe01aOvM9|0&Zc=zpkBiD(if z?!QG@5H0D}e)dEyp2Aq06&rVYyEB~m=%}Y}bo^d+#v-@WRT(_HADSP0IgI~mCBiY7 zEOpa`-eLLXNu?Bj+r&7S|J=1>?rXYRB8AXY5tTcXCauUm_}+GukU#xZ?2dn!;2qCF zG?{-dUcNB)r9`Yk5BKb@xeg{G=hH7pN9W=tN|4@Ir;v->re~XuSTg&&I(<F|o+s?MmQk@T(6CQSK z;;Oh}-eIo>_GhTR*MB8Eji<_bHf&Xth)y~POG!59qASKXct-UL$&|Wc*w1jrO@U<=~-j$#zR3H?#q>v9|@~sPgQIO8If?s_dFMz$YX8lQVK4 zv4t_~$4K44&g<|DR;cVQBJlZ;6u)$<%da15&V&w~ay`|E3evU!b4vTW%U?SRYA9bP zTn_wXBkCOq{8X1;C-|zYvfVN?2Sm3c0jsftXREb<;S{!=&QkICtF~ZU`OJ=i8AodA z(9=(OoJL3qM(XHaRb3QVAN6*wErUD2qA^z_rh1%+a|q7|B}H>P#r5y1MYS+aBwta2uXhG4Tr3 z?YT7KE`rF-8HPTvfp5OyQt`Vk@IUZPDQE;MG0&T4c(_kMA2R#vl@B>jd1qHu>e1@; zi0HcIzh`g1{Iz`iqq{RIa;&XZ9(9J*z%cUTtGX;WgevU65{ELC-M5jPV%yPwXi5h{ z)pC%+Uj_`bOYn#oKsof&MKD7V56PvLAslx!;4y!EW*eFE6#$T#KKqfxB9&AjTRb|> zbBFjQylwY8Ze(=zI9tJ!0LaD;J7fDKvGddI@86wc>SV^o4UQ4lrtH0fux*l^3+@U+ z^Q2$i`O2In@C}S#3sZv;vju(cr)f=ztiJvM<#GSWjW1hC-wd1&ZkESaRjlgm*s=)! zBSAwN$@<)q6|G~X5>kkxuR<85cvHa}{IbQ4D4l=R{9E2hlhPv->s?-E*J|+Mu{RgO z*i|2>XJg_cE-M1c1E3@~;D$kA6w{5F1LAHuKk5lp3NHDs+}SzZ50%JsCuvTvT)es*~5~g zXkTE!!=$1xDhO%vMhR7&(xw~Kruh!Gs3DtvlDH%4@EcTS%E{i8zqT_-5Xl0(GPnO! z45^H2G^2dW=tm9MDx~PSQ-0d0MzZhE+p~QL{V`l{!|`*9V6haV^Yr#7w*gubW`u=js!&6e0p zw@Y0*U6(p_22JSZ8Yec`J{G0K?$nBm^@??u_;n#e2A4mU0JQqFdoCb-OYddv|8i>F zn2Ypy)yO_i1(PUR$u_S4y))Xe3FZW34J~`15t5xuAk!z%z^>|EXlA#*#+eKP?}LL` zJF1VPo;3N!nrP^gxGjG@BxrF{>|uqWm}1720j0bk z>|DHCa=v;(7vg(JXE;(8Z1m8n;OHNIH`MU@{+-`)juMIBR3XwfPH8@vLjmD8@h>s! zEG^vViw*4mK9nG-JOJ5`N};uV;Fur3*@ewA&%?p;^hvac)SD5#^QB}o&0k{S$yIx( zmY4#CNMgB4C8#~CPf~@E59N=NK-4P7?>0D6Do|s5?-?=Wm>1_)1fIo0WW2>pO3ull zrvekm!)p7A{&GHorjI5yaD2JSxYyX2$VL3n)958Q)>P>1oJXebqbM;^rY z@lM$Al4cj7%s-f8qT73g?(&xRI$saf9fO+4Ew+%q-tz@#mGU#6EQ(S!OXnG=IlKHn z7eLTFsOl(?(zUfJ;H62k(KP{`Eb|ar59TD&N3{;(M{N%6-rh?QM&(l7w8bh!W36`G z`BtZ>B?jX`0ni>|JQUjm8hOntm?6p%b;Q%3KKkwljW8eO*2&8i-7(mV-f^&UfxUfK z-*OklPF0apfy5pBud7@xcnw^nUOBN5ff<9oU=^_9z9`5s8)is4vNSH>&s1R%Vmf6qOHmd_fQ+V#y(9`UeF{lruw|bInUh?pU4`=;7D$r>T#7D zJ41e%_{(HJmH*gVnKAPwv8qdzr%S&K=4a(%V{}@?C~U4URk|FDBTf$8m!%$(HFAos z#YQjU4!J!OoPhiKn0%L?SJh>KvV@buc)5~`Nu5tf#skRW_}b>&;UNVs6;(vt5tH@Z zE>F&vk3wgO)_#?Lwo+dGtM+Kn*{csAj**ds;9N%f*uMe&%O9BK0>-2p@Vkaoy12iD z!yX#fEbaO1U(}YY#X_r;*t0euyPxe9b(oH157%3|qcvvCtmCv`xrase4wV&(;zx$a z$gf0=UEQke6Izs*iGicKIDv;vO7k~safNug*SWSDd=_pK?rDRMHLns3z#i1Le5n0Y z-jNmyUvoE~ogD>BB+XSc_8ZXP2eV(b_)4O>S$fdfb}UwWj8Y+8#F|2g6`|JEl2G@+ zp!ZdTlw0I*Y1!EFZ-8e%O`yCUy)2h~ygP2?Rl+o?F`RWQ;Xn#fz5w}n2uQs_FKQsNy%KX$G8AD+H4tPLjEI#{8lIK{2F zI}|G(+}+*XT}yGdQrz8(TM6#&?!|+<u%_5bgZtVrYMKQ_KoJ{ynpt(Z93&Z)LTT$=|RN@;y;UspX@ch+myC912*+i#CS&48dFBhZvtx!OGMbl z2MpZ)Y0y&R13VWF7!(r3z9>D5V8-8j(6V!PJ9k{s-U-%VK4E}X_AlTc0p1g1YoYd` zv(Ai7BJmV(6RCYlQK~F`+}7-0@B;L3cdCB%_l1bUvW~blPR+&M-_jA>2ksZ z=C&-oUWLifos2($tvBYT`FF~1OZH-e%ewvmhf3M?eK3!gI$rXK@j<0YaT;Q+sPA4M z9^|I(l*>axx%bhXwS_!PTE1o+bO1g{yyFPjb|cogPuNg%gU-|6In;g#Vw-C}&qm2M z=8k5Z`#WP%@Edb3LHXP7bl=deXq9dL z&o2!U<1B!HD1%Gy8=UK)C)Rh7yB)%>wY~=HuvKxn5l+myrJ=`!yFfo08nj!mJZsuG0vESTMT?cWGEkS`#9~7@WY< zGjiaJMl!Gx*x-jN%stzn#-iVtlvbUJaL0MHDAK%XDJA>{p8}H>`dCegh z{t9(~DWj9Sg2fwgIoJz#XX?>9%^i1?ymhSauU>lCtc`}qF?1lR9T?zoN&pR_T*KcI z{`CgWYhKDnvz6Hyb6I%&pe%+)8IS!&ZP?=O*xS|%nL#qk@mZ-Dw*Ml0fEK)aHXVCu z0sAy~(9f#=orjz4M5C~5$yj-PVUJ&#tY_3Eto~wfIk2G~(*;&bmYAOInS+IyqTIgw z6(5Qp3*Wgsiz;cuI#E=-{b5Hfmlg~Yh}fHscVDQPq6V2nT2!pQ*<*qW3A%+VvIrC6 z!X4P7KIKZDYq%~f!$lG;%5wOS06UW#zo!!ydy|HdIvReL_-cADv)=KI@@up!VaVj6 z+aF#UC7(6$(E}?KP?d+v5rcL5wrvQYMn_V!#7c=(H@M~^=?a%TWG_7Vso}XmeDDi1 z$YmEP!n{X9DuH*BuZ%qp>tPi;*tu+PM4sgSOHBwVH?Y8#IYxx(u!<;r(V2PuM@~3S z#$CtdM|#OOMkwV)7v&rBTmtA}PyPK@@ug*f-47W=kVn~lp_P}l_jC|d;>>sW;zODl zBrD0mrzWWt!J5$%2`lF&0*(^uHz*8fdS&ZY6HQ{HyTj}-AE7v1uUq5mom3v1x3!l;$1!f`L5;o-5H+*frR?c z#)2@ab;VZH03O%}W=^y`_s&#)r;8MK#&%|$Kab36vsjU4H1hl{HnHlaxG8Lc5wBd! zGUGpL;!};KI=cui@)=hdFHZmfFC=g7s9s`PhOc8dd^ZJr;4jMJM#ha+mG{Nx;&G9r z)8wzf`3g#TOCSzoqzVio+*qD%9gohiW)!xEo4=q4eZfV+K>rQpoUK%}5hrd4w~^$n zDD5YVQImG2i%2zN{%r!ArNhlogFo8l1@ndJG3!;lXiQe8Gav0D^oK{kzJ%<^t{{z$BEZ84Z`{ADA{ee#pQAiKv?3m{s zRt?r>V7A_vFeTOQ>)3@T?+saXb!_7EYXRGXEA2`DG<{?K;bz$2xkKT7`-eXhx0hhR zQFIJZng*UVI|>i}LrRt~*|Y#-aF(yPkfqJlXP;r*ftdzumx_r^qxAzRS}wLsG zp*_zVgWUl(CQKzOLv2v0Ye9cDbThR~oXIh6kYu8YQV~&@>p9;Fyh-~z*(^V+=u{o5 z7%x;$`zRswsd@qY2j4nI58|NCZe%y&K-y8p<_`t|!ofWj%n$U3MBB7`+@xg0Ej}W@ z9D_-RvQsc>^#Q^(%_KU2!vS0n%g4b$vgryvS?Ab5CEoF^>l|_X@ag48L?FjYu|S#x z<4@hKt(+sO^=uwg&q8N;wU-Q z3HnT$6nd#%mR})BQ5GC74JIP%vlC;yRH5EQKu=ru!;rBNMf)z66S?`AUNann56wWj zD`$7yfM{P7A7$&sQ&xZ{qTV_x;FqUG0|DefPPD*J1MyX`6@ffz+(_-&JTk|#;Cd8| zXfWsJp9g{~1SdKd`tf*iD@UroA(L`lt9c`|ssB8-S!fTanD~^xPe?DPT*C!xSRmXw z9c4d=+e`=Je2K?vVQ|^+PI?Eucq_uLjnPj9^f|}soqCqx^WwZXdNb`aj&V>-iIy*H z{PI>TLIv8mf1`1cu#>tkU-0H5z23&(cM|we%~joW6<+wTF_Cka$~ECZ%&72K4|-sW zGp=`xsw24^h%DnYqZqmp66J$kD&{V9OUJ^C>o;V|isaj9V?{mQ`78!4&@8yqrMbkp zgdOeo-sC~NXnnmT1tQ;XeGLc6kioC3&>;JYeXJuc+k=U#k8TYam`tTloLMJ3? zLLsg5(8dm+d&d1byVHwphSa&k+MGIta(nqm!GX+-lh~pBfTX!IHmb8qo;+~Tn0kaN z;Xw3^(Y^VCmvOrK83whewHh4tYp@;1MTRJIht1R6Lb(qt%uny_^{J_)A*JBw7rfUY7&@?U zvlKT9^NyxPSd3vG)oFnPXO1cJ{TcU!^p$a7xo#Es!CXaM;(2Z$!7nY+w;&-MXa46+QP)b(e*U209 zV4GK_+u%gv(~VobuyMDa`BgQVLK(4yhMi|I31p5>R5~mWn-zy<%7OGxxT#%8-c*G* z;lUm6=bx|>$})ALes?t?W4NS8w+`kag>tRa*gv=AcyiY&y=g16uzeUE+CE zr@coIA+^zVinJ*KhGP=qDRJ%BY553#KXE-vkXV zjRB_bU=p>zJH7Dxlw(oSpuI8#>%>_oIT>JOLGjIAL9#bO`bfMfL%TFLOv7_ddJP8f zob9;doqLcKt^c+fYT*tD`oTN3y$QxtJM6%YUF#1!Rm$eKp0NCWq}>G_$(W*gd(ms# znkGv0NiStr&+pG?7Y(gNY7851Z%cZP3PlUlAo7ylFR{RB*{2V)Pz*v;m?B|O6mu5a zZak`OI=VRNKWVqjxG>_~$g`1g8H)+U5-#O5auJm6?Gj@k7TZWkw)}RXK4;CC{>mwg zbl($=G#fBMDmQ3XA#hK2D8Cj!snDCO3=>Jx--;1InfV`vb1AolkjaHYm<4SGE$BsV1C+7?=8~fsO~R z49;R2SXaZ*Wfq$VvboP>y_$aQ9f`W}Z}qnQ^Cq@xE=zXM7{fi;6ZmTlmX?sB>Z#&J za8#|ns2?nQgC|;J=1i;u6v`*Tkq<7oOf*t#!9k0#~SdpZ@FL|4bcWGX8mM5j-A492CK>5 zhFhi@e9hV$J$+Jx_3GaO){l_WC}$2ilK!HL5u+St!gO=s4o$e*~q?}5I- z08D~!jAv_Kn1)!I&+UgJ0n_oAE5k;&1f6)R(m!fAdtQbhnwa8O$p)_#i%(1-lqXfu z#Qjk%@Qwcc=X=s6Yz0k*(K2=HYCmH-(re5~K_c{s9NdbsENtb?CO3w<_+YYx`m)O$ z`YzW^z^@yUA%AtQ0xKwU0W2hUMUcJL533u)uEF__iU`?Zqm+Lz=kK06zh|E5Zamp( zrWQ@UDK3wpJAkH4-0+;joHJ#EHX;n8b#8qQb;i*;V>>=fEtOgP)hN+vMdfI~toY`5 z0N6K`s%y_G2*eEtJ43&nGSHB1wmg0|D`H3i&>=-Q~l{*F%(u zGLeFyCQz--t3M;=Lc^lVXUS^RWTk7sv)Ay(4|%SQKUNGX4Docdr#rMZr4&uqZ3jS> zZNd53*rP84QAhBzO(CXJr1^Q&a+C73#FOym|bMaf}fy`R@>azKEmF#SN20eS=ZA*eQ) zh?Z)y7zzqjaJ|zs6C{L0IKFm=*l2wdXM%glL+lYe>cLwg+rU`Rek!@mqZ_qkzQw#U zCTqEFd-`W{HW>KRU|VA^5Mw>tBsum4)l8OUx8<#Jh|!FYP9dbL4j=L>Wro-~hhW^r zLgd0U`hygaPd=wb?qE0Cy|{fZqh44RzVmOmR+P*uAq)h<_M3(W6}yLWD93Lv>7P3f zdXpH5WrlKnq>}gHl$8R8v20y&**O%jGWylz_6!?k{Awu^Pa7qXmy#vXdCbGi@C;Mv z{4)Fgg=g70m!f`Yvny8TRAAXi4^rcdXcAxH2gew{2G@3!Gf3G8)l3fpZItbS+xAbq zHI%`wBCF2XbE&+nDNTlj}x z!lzFh_yh~qb@M&aehp7B9DTF@BHo4XOE)11`l1iXrNmq~yEqo2fLjQ?qTNpMIf#eek-Khe*l->@C%2;C^|f%lR-MoKj#j^*$VMMy zD&{>XE3=1jOaQc2o$U-vyqfTSb_|WWgC&i>y|V~E!^Oq~Dly$)K-)bN4(^RhUK`8? z3-i)oO+7-8uVMo0B8OsPykmr)8;zr9TPR8s85)ulJVbELg5`z&eR8uG$hmEWjd*&WVA-Az;(*jDVdjwPB4 zQ}8BrNYN(iuYnLZu$y++}ij(Wc8tW@a;Qc z{+$uVQS^`cnsa(ty`Ox;?zTVRgvX=BJ@ne}BCnse;p~*_Mfx=nP~9AVAvlOClOvOU zEu50vNIO^tUb7>X%<0FjOV~yK)X~R}JCPZCBD_pBCVer9_9-2)Ue3r9-W@mVcZflda0qD{AgMx5=r%#Vt05rTo-PvET!9b;=p$wTflqjAjwLQaS zR&FZ*_Wzg}gI)QOr)o#Z{A!>tDW!=nydc7T?;?#4D)z_tx#ts^Gvy&MIt7mzuf~XT zRyz(t`bs*M()O^yX8@F-&Yh%TDeu|m;?LNQJRz~TWNf{jt(}WLmFIdM^kKl;;IRnB z{Oj9b-Go|-VD7hW&?bH{@poG$(S9a*Cje6PsI`bk}iofAa_Y7KV@N zPvAZWjJK2KlPUx%ytbx#9trXj76WQPAlF$f%YaQf9yEjULg>i51nj>8n#`IiYIEb6 zj;8hN@sJm1+bzgXPwT>r?Xr#nHHK*E8Q`XjaldpEn}_}zA=|%fjCv=A6}a%{mXA?K zs#0$wUI20WILV76M{s)0ur{ZQ*TX)|5Vj)wI&Pwxy$XT-C7Njs%g^$JMooU>YQZj4 z-I+)r{-fM)^g`ynHIU_-0m&5^@4t^Y2U1bAM5F^UO?Blq6-{TFWM|sinpky?v>H|tb(G$aaF7I!*IZu6%&8-I=K%kMBb#+(^FJ+7(^DUKm? z@Cm>fq9w?L;((}Ti-1eYeY^2w!Lt3aq;q(e=l5L5G3U4HSI{3;g5sWaxpSRfx-tXs zY05vNV{Z~701U2!aI_3EMpRXC>ahu3OIwj`r!A8a@3~axk#v#&C3-%X;bYHmb?g8e ztV4nA7CB<1En)v(5C-UKu&oG47jne4uKGi^8vH?3h`&f(cjG+jGfMPkXcOU#mz&{_ zzfpw0YPf*YDN@l@wPHBOPaFC0d8BnDGU;1*2uiyy^q2YJyi_~%XZi{T08|BIW_d-Z zuQ^0DBHkuz1f;i|ZqWc(U~m&0Dv4xKjIwk_gdcWADVHq{%;r_OnEu%d>_Zw%e(W`V zbze@M?t}2V85|}Be$wTDI_Hxr?(?gPh_rJf`l06;xTD{PlDltG7Z}7Pl^%p82mArwuQ4}%gOR#!E;udmo z|APpD@mr!+ZKZ1$M!fOdxpW9c;>~QYCTOzp-jRb_1kOmp0MZ&K@O&U- z03VAyJ=iDf$7e)_!@Zm&;aU%6f;d@_L#bB z9qSinry{#h^j81Nz)6I=BRN|!%|j8^>v^i?Mp2nywGG0jVe$lT@`DZtY0I2RY?^%O zRiKr>ss7LG?_1}qmN_0n79jjVdp@P~`O^2&IRbb@VP~y=*2UR0uIa92MBhf7M%M8H z7LUyp27B75R}zn%DzFTzOJxHeYd`C|`L@U&+vEV4j4s2Kp|QQe8TNgkLXa{?wUwU( z@8Ld42mq0HX7*0Hb4MZuoZKiHSxF2L1dl@W)`UxX0NXlt)dn!AebhCZ=SZkQyLC0j zc1n_HYNmu{a@sh%!Doxll-5*sQGZh z_8Mc`czZK1cewi(oSpE#xRp!8f&SCj(0yiGM%7wm-JE4-oqlFS`%Gg1*{BEQEO)*- z)7j+ihXh4Kku#W&5)SrpL-+4;1=UwyF2D6XLVG*!G;G_YxSJYF&i_3&+w-WNJ^@RV z@C)x=lVPrYUOozCx;Z{>q)y`S*85NU+`l|xG*%OPx;UM*qlTOh04Y7@JGR)<8T)~J z)sjwteMJ_vi=$DqqG5cbpnh)>$^bWiS|>IgRz-k=Bx*UQ&0X}Xk{}*%6m||OjJt+- zH-_e>1Zq2FRtS^qp;c5G0-$0DGTJ)vcUjy;7YaIk?T@br2o{fp^Js?o=RrgW6JN@c zCIF2(MH?_iU)K2nev0*cfc0RA-Gri2^E_Z%cw2VrCBCn{>?R|$oc1@@?2DZa^jQSQ~xZz_x-uHTlI4rxPJ z8UFS=mX=5=MGsNXDfsRWB)mtf-(Gl~J&SD+L~DNE&Xsq%2u9K2)b295I|{DuN>ijx za?m$n$kpY=xspP5{dx!9NT}P^e#FQXK;p$Cz*jPad4PM!Z|v1UUb#^Ed1eS&(1$c5 z$ly0G#!ZCHTWFH;tXy-tpg9p4vb!+p!zcY=l#%r`r*vv4HO&nUNUoWwG*$lOJwv`z ztl?>Gw%%w`*|9VOyOA^+NN}mfH)rk54OB4Kbda?r=iR&EV+@kftTu4~S{fI9ZDd?o z$j%L0GTV*4{ZU((alT1tY3tFF z7MYTP;sMhRxl1UIz{5~4l<7|(V1$U$x*BaYi01ON=nY*9KhtRelF%?0D#JEjwWRPs zVO%325b^_AIxv31&ED@?033F~NlE*yVsNX^9ertb^?)|piWpkSz-H}-`wKQ8{nNIXN*^m9o{SVn1lcFrm_63(Z6kV|Y+LycM&4LP z)g%*x$naAlD3i*x7$-hI^tltH*e)1d5WY$Bs0#jzf{(XIuxczUr08lTfeI~Qv5?=W z9kQA~TuucBcL@-8U?3aoX88?@7@jo!9~a=Z3fnL%S{~^{D(aXOr5bz&ZI!WPG=4Zd zJqm4fRrN}uJO9FFL)bMDb8%WHU$6qLmNr|;sm($QGM(-?#!Q+d>7rT`Ui46v8rs8K48sAZ+&s zzRzoCiq2~|CDQ+eA~J5%JK61vkMV6pb<}kvbpb1f2zvoxRM1&!|M_NWvii!@U{W}t zlV`@zTXkqJsHTzMyg;y-=t}Vk7cnr{)76I_RI_f-GS!PT8g5y);Ckmx~OKYRQQQZ?i)YNgse{hY# z*peZf_TwOYvAYbcBROPdwhDC{t}I8u&?$>oP%%`jJjOaFB0xDGbB9@DVG{{crv zxCjgGH_#-?M`){DHg1@WDn_CDBhaG4*M%Gs%%uFLrkDL=E~q17tCY`n5}5e*H*T*b z&zBeQBT+XypiFQkl?}9Y^6KxbhSh-|1)jnWw3*D3b0!8rT@Iv|`X`Rg27;dx5xWx8 zdZP{)!2@_ z-@|%FY&ck0m8wA=ed+{vGTcPBu-0iG^8=IlHPh={@Ii#wR(AsC|FrAr3f|3d zR3dHRp0ORihHOETO2&%1FWd`33x4=t3X$6>xE`1U=C#6bH6oZ4S?N+mm&8YSZv_bS zy+$Q3eeyiLW=g>d#$iNl1rz9g+SE;lxwHocXyN@PjI?j!gGxLpu$(2lr&KAdB)_Kt zeLci@iV}y(5{7PT-o-rny6%Q{=qPNk%Vgghw1vS{?x4prDscmzS*+G(J2l7#1{?D0hZl^vnJ zUWTzt=y9%KOLSAu)O}i?-L*&wdqKiXb;(EcDF^2)775(&T@qtX+lZqXkyYy#z(Qpm zf?589E3*)Zq(N9vF;Oki z4ZV38_DGA^F8w5iMl+z;IOh}m!e6224%RVL+SIWo5&=g8;Bgo;qv$FB|JdhWP;l54 zF{rbQ)B}eK&6y|IgdYG6@o{1+z-SJA8k5f|uMjMeb3yd@w;$9j|JFY$KtQlAVlLi)LesC=P_O?{a7#tL#vgT}A5 zR(gAoTDRPcJz?i3yv$x_N2Bu;Xn%%Z&-FG2c?0^W#2$TpwN@ETG>}`{$Suh&;`x? zV7*JraU=gjemshzr=zlx9-y?m&yQ?TYVpGMeHxK%x%D9GR?lTRf(a*L#$(7Ycj-ka zbfN&W$r|Gb2L_Qe)tY@f+fBz(iri`aIfSbAH3jgP&tijXai06h#@vmkT2&LrikJkX z&*nM6NwbSDOT0Z)K0zL+voYKy)N}u0bRqwryBEGSZQOp3Ii@RroAGQV9qrVH+mO{< z1aKpgTyw1m=aGFDhej|Zx`d|5)+9EmHA;mE?;OrG_d4<4dr=_mxGRluQ%<<^GPeuh zx;-ekY;QCU#K0EUM(@PSZM==di=wlFpUeO?m@J^ue&7V&S~FaC{_^N}FnCN%YJ3(- zk>^$XC(#sC)94a^1>KHP+$**EWLEqIUctCVS;pup?sipUMG3LWx ziHih8Lc4FIqhxh_`GT;&&Y!VEQ7Kd2-f!IY$Qw}}@&u&y}Pk@!!j{;`#Mvo7|ksQhZmjvU?oN=wbK-x>z zI+TSiLwgX@@*ied#*bMfrX7sX-on9WttC`efPPEuq7;&$N64qQh#yy^wCumy8D!nK z74N^r`13YtFV`dCwXL@>#;)52+bc7jG)tY$XGmx{?6<2xv(QEuN&J@j>kqS0+VD)| z;C{u-Vj+Sd(vzLv!NLq|UsBQRWjqE0F-D&GK6A&!&cPUY7a8vLKDfpE3Vp1GE1_DS zvH+!Wy*CS8$rMIUdVql5isW=XFzE3piI$vku=g}ltkny;(Fhs2+;w#)^Fn;WTvy_nO zNGld6D-Cpw9<7fyj-rluN_4t(hJSIG}hP5&+2#c;xP5in15 zaXemHhtutt5`G}JHmlQqgn0vN76Wa3O_QMTYuVV)m!D(1coU=6WlUz679vzLsja{C zIVrvmQ%AFN`Rr=2e5z1BN!`_}daQ|bH>NSG>sMCuC%}i~`ew-HxodwaZy?pY=S?~5 ziWTCBA=cj8!Ug2Od01M6_Y#3>6HLcvqc>#;D^~t=#^mIARP3;U6bbFNNZpl>Y^yIr=DNqFUIrBI9Zsp%T&dwk}=O3@TK-z zc69=hmTS%<&&U2T6db@sv+eI8x(hf_fZfFp*2C4*#8}3JDHJ4>*KZO5GRLkkmn2SZ zVXiBKtj!u4V=Ke?%JC~RV~5VKEm&uouEWm$-2+TA%O-f5}C;tSY4ko4-i3 zlmBNUAFoO{unb|l5jALXBMOS;3J@`6KQFg|ob?(OquO%}n{Wm_pRyt($<4$`!c?Zh z6CJudTTx@Z5%G3-?*IKBYK3dTY>bq$;zrTyM)vlzTn!Eh_D_7QPxV#8&6Uw<>su); zQXTb=-)jv|CMly?aMWQ6$hr+8sUN4Zo`17Tp*B4>*4|a2#M>!+=bBzUizEUxshwB? zbDs6te~+Dj+QY6OHCGp6C@;(C_$jzB$OO%m11ca>TB@yHtogu?y8oKlbgbGc8q$u! zNiH+-o1cCRvV{KU@oAd{XBLj&YlIc5bPA-jok*LdRu(~{(8{3HCx%#*q%|uj=OGpp zI;>SSum{3de}A*0^w5JZDR>yoP2e7*KkD4Y-ryJHuSHr!}+PjqnkW`q6`>{Rjs?eLfG{U_F1(LeRI< zm^Lh35An#f8S;M$Hj=u(VBY~}vDM=r&Gz5RC%3{2q@*zU5}o-!KSFtXR0mw^C`$@% zhPH1O?;Cdytuf-r;|25Rj?tqAdTn2p>qom=u5WNs-UZV3mUUo)?1S~wuO9eF^ye~n z6{}h8KAwFZH@tVmN=&}Z;9Fw?=)t~XX6D%umE>DKP+}{;V|OWTiEd>4ReXB9gVLL0 zgK6hH;7V-m+{sD@szF=diA>>A?k+h>(yjx-Yi8Px2#TevFGTsF02{zB`tUXeP0aKp{jDip^|{r z$X!Ii5$yW+kDB?-7{G0@by>_X6U9opVpKFX2SgtvQuzRXeO>RF#nD3xwqHVG*5?){Wazc%Ke09v za+YJl2d>t(_?RB~s$BUurDPy}v>}P=$50fetB{U`EG6zuruGHXV3Ew>YN~8KA{NsQ8Gm+( zI+OhGLDL#$T=dSRM&6X@0e{}04w@U%o>psk#@R4tE{cDW^R#xy{jZh+1SzGdFm!7TFA+XCt%?rIQ0ll#`qreLvA8Jr-a+@J_N0A3;EQ*%Tuo&aQ zMk|%e7HZI1<~4dRHLGafFuFWy_a~I(J-14#rYG_%?ktM3;k6ZHB-*xJU|_nh!7nV~ zqKYN=LM}-$=#Txc10#nAD9bRS)Q1UjC5UeV(~j}$5bL2*NP1YL23}^t|FA9 z+=}dDq4DVhs^rvJK|KK`wQlR#7N1h{awsSGri*!WnZC(BE(k)#^`O}~IZwKl=V^BA zjsG>VbuB2KxS4diDc*%RL;5Id4ja|gT<{ib7;&85crFEvG<^Z>ecq@N2d>u9-!w4u zV>-~LJss6#hCUu@GV%o>CV*2JP7BW#uEA=C`Dbg0Cl2^P3}tMTSUaMbCVi6Jwa+(a z5d~H-;rbBdO#+;8PVDx*e}OUzEIA*_m9evlfy$Z2 z0f)R`Um0v*2jRW$PsLj3r+hhEER}X!EKvn$gmw>NMGxYehXjWW(BDob$lBUEwF^`Y zv3Fzw@lrqOp)UmDe8&MQU;G@hiY_?%`(f$o@9=%5!i%d^5tka1Aaos9d}~X-|Ky;b z&ckMzK9zc61JM@KU45Yq%ea?^5|z)nWGt7sMsA3{#S~b{WxM)uy1dkQhTjPARzTjS zk`$qx%qXJ@VkrWrB9aT80)S>vK`em|hhExhFB=Q^z!|Q)W)^e6?a4J>>*9=pcvqZ$ zXTBi*4|0#1I|v$#E+-xb_5$mJ18WU|Y7j@Fm#)SuZteN(46(rr|>}kSw*Iu;@D3^~aQHE(3z;(Hz(sa>6`NrXmqRnQ{Px3%*(X{;c zsXL@vlldevNu!L3a(n`jSb*^O-&{)i_tFjdlbos z+SrH)DZgT5<^=#4&jBLt4Vjoi?u(-ZWsqI+FnfX~$Qr)>cI)w?_e>nL#F~j#XGO~4 zz_oH8ep@Z7SnxYRq|@JX=Um%-PH?vLMGnwP*$I_iUnwbOCq~7cVT@8+L_4Yj7yEoJ>77M#@MY>B7 zXv)z?>9n4JTS+uy+3Nn-G6bzv_X1Z~G}VqJFRsm!0RNjX{TI1*o~dO2^1o&F<`iwI zQ`H0=WP1a%B&!={Cw_*w-89 zLsx_&o5wkS=gRjko?`EUx)RxY_aOlF6}wmuRs8t0s~p^z8AkF5-pT^se1kNZC;zK{}B29wE3ev85y!1{#4%t(&sY;)DEaP7vcYW^!JLxL{x!(j%vMTKf=OUX zqGE-ikTGIq8u{-vhwai9A{;+?=t-cZz~O&s!SYzuWKXqfsG|qS;#KV2wPT_+S6;a^ zl~eokf{Jd;u3}kj{G9$|LpojrJ3{3OT(xjzt|6Do#V6Ht7(hi|7u(?9l6;>R4ynye zb0jB$tC(Al{TQ!?o>s+avBDK??8naCEwE^lW^5~Ale0Co&eOWy5yBY)k(kfc))i=B zIAT)ghy(p-vg(IU@FcW@940&d!uwx!OF2i%xE2nM=B+nOp!pOzWHwD*DSG(scg+{< ze3ydmLaRFD4JJ4v=f)2WDK+N$pN!<+5R0R0HTN9-mE6{gnOns9>Fwpm(EZ`pxMHWD zPd52Xp)rHo7T%ll`ForVD|41%y*FZ{*^=#5)?>EH1PtI>;C0Y=Xdxm2ZrkA2g=nXB z;~B(~c-3t;ZW`iCzhXF2>e-@ba0sdQJlt_nTj(9!0)&b8pOyxV0%4T1zNmF>eWAhb zjG`^TMa)v}(N?lRNKcg(7eSyQQc?!NASgQtPgmxV8%Ua|eZ?fgl0u?9d@(KV&T{i9 z)7M{6-*|cZu1Hd^sQysH;kIPpd)~DBp?EDM;^HOmjY2kJVYYyd%bsmw>DP&N|HKV) z#HZWBM}8-gnfPacjP9#nc~63W$0ET<-_Qr(D;Wb;&bvT@at6w)0bl?s^&hYHja03f z6&ui#5RJqAsKyVH1>^|dd=aec!U&VRN>b+(e)S1td;l%LZ>@rXoSoTIqg^$!U?uh-0)AWe|4G(P^~dke|G$8tr{D zX!$r6fwmcN4kD3sC&8~)|ZUSDd@LC&A2ncqew9^7Ud zNxOFusU9oB;BFI7gMm=Sm)dI?;SsZCb~|3#ekND-Zr6SdnL-!oY{1JhzIHHiStt>$ zj*jR~tHtQ~6Z+-*#aPgk+O3{FyOxm*lf+nqYae0hAk!?V|oelKFzhm?88~d{uB|p!EDY9l>kvzf-%D zi#vnQ14FB^7km6B1OVA=A#?r%Wo7InO+dbi26GG9ea`vt zzFLf)!n$2&u!;Hv=|DzTW^I+Rp8W5BwsTIr#pr$8(sQ3oxULgi`l<#5cAtIJ$HS05 zbVqFZ+R)_3U#RSc-P7)RI$(F3IW1*K-}@JYjo5v|B4!W6*FY*qeZkPhkG3%BjNj>r zk?#0fO%G7AU=Z%6e^FnFz*8N)Sjd})82puf=IZ=;nGaue;}qH6yPK(S5di4Vy#E%VUdsLQ$(h2shlLKUtr)#K zeYu%)oJZ9v{7C>K3x6MVH>}Fn@rd&7O8*m)g4-yqEx{_z-JC7v)*@k}Cx*~i3g-ex z#OFAXRiCamwJ(4m7hRa=CZ3w!m3|XtW%Fii2XT1s3;r{Z_B#+hMzXr&hOIzBDe8|t zUdbKb+U_CS0}ByFcoxMOybb zt6KgAz4Csk1W{>vM_kAHgk&Lif%TWQ3qgHF?yi@HJ$}WktW>TQk+{$I{LILQrY|24 z*Ry{k#r`3_A1G3cn#%V>9@ML8kdBTV_$fkn(bbt@qOBpHd4FnKZ43j5Scu(-Skx(J zf<3*pEa(~z=6OnUxrzEQ)KxJla$8S2aE~S?#~me?60Hz%AyE~>X$`jx_i^ZhsrwMjL$@qN zJ2dGlaq?}YK@03L%*=B|@#Xr1YqMI@P#sw7NfAIA-CWc8`>1a5f7NZeZ!1?_ z_n+R@2b57>9d8sdWm&VrfVOdZ&m{eBqFM%O> zvV8F#@ZrL8s@%qhkXWHjy`P+}->}3h94n%i?~tR@&|$%Y%KjRyD;l8irxbmF4o#8g zP-D02X+xZqviRE*nJXPhm7xsRiW`Txa{m-Yclbs!r1`gOXDN=g@1i~1jO1%aYMfPj zwnWi$i5mh>4w6L7>X$sZ6Uckarp%+q?%U<3s4a$Cd9#G?VXcSECV{gZ9>hSdk!%3>gywCI z8}D6uj;tJ`2a(gecmITkZgWCsZ0pYPw_pfdea7Oc1#L^kv3>Lk}CDVR;>W(d|R-t6r^&$64+dYm6 zyFJ$YKQ4gh*4{{w{Cir0!EpH+F{oSp@xq}${2;9Vq&HYRfxLM5OTX_N=Ubd&vV!j@ zdolz8(WwW-l_dEBWeKftNs-adqjvy%B_SP32oK4#t z*jgyZb-q_tn&Huco0#)#Skivqx(@pqF`VJUjYX$g{OzRZ(0;+cyDxz;}^2;k}dzN&|8mIZI6AI z4s~Op(p8OBE+osh0Gr!7lK3~@_#q!UAH9vJI8)RvU$DaN)-|pAT;`(mSLTL5udj&) zIu;No!U}88wTQ+F2&<0!IrVFbl*pS!FYl?6L#pW&b^~$1a>`RfZw|6~(utrOF)NhoOIG44` z2O$ST7@ffv5gO)88hqp;T~P8hMU-l38N*%o+DwsUU{}sE`v(k02yCS%H-QB{MrSA2 zmAhmj^k2UgQ?G{o|7H44Ijf%USVRn;s)JbY0@o^h;W{u75NQ@Z6^x#1ejvJG-aX`x z^)HFH_d&-hs#c%d0=n5-wc9+CWn=HMS4J#h0iP<`d)7l5ZC10fSCyffVMsRNw=Rct zWrFn)80z;3ww}zf)gitxeXy8F-8Vjwd%HML2OVJN zc8-6YU2XZg>R5^+$p6~{Igscq%`nOlmvrJV)ArJ1tS4&nTh-6`>L&gLMuzF?{gn}f zi}acp{B(_P?V`7{()raD)WXc=kd1c*yB#mlp1qcSfG`#}jRx76+22=y7q`Ousm9RD za(U6l920fBX6zhFzweGF-QQwIirvk}oygidrVL%mkpsRVCUe50q*V#!-2d0!cYih2 zHQ}BR0R}O`r-p8Y>)7&!dpdqG>19sf@s`)`AU2Uv!g9y6* z$RiWv3#BI;sA^HJky&>AK%csJo$K>3hK}D#=)Qby+^;T?qLz!{5}iFuaTmTH6$O_8 zySADxz9Y)#?Ohcc+VWP{$jL4}mwvfpjs&6k-o8QtURz>(afnap`l-Ok);JR&Y8d{q zZ24jCQi`$c@$>&9QczP&o60clwvf}qyYHIBa!Yv>0P6dpG<|dRrpPW1e;Dk$&b~8u zs@S{xwq@{ElKRTlAi63&S!q}5a;-xeC)%hk+Lg$-FOFFUNCL#jS8C~DiZguZFa7%N*%PPwBvSP8fq6MN0D z5mD9{WAS_vh<195)T`{YPZh^I7bn}_u380ggoC7!pF%QWO?sMjpl?}zy`Jttg7KK^ z#Za)+-YKW?V6W&VCqRB-K@ZgNC_WjQ*5X1dow}Rc5?;Dg=8($v$+~CWwW_Z@K}l*y zYWmg5d_TJe*({t#dC0!LZOZf3q*Y8H=1#xn`PJt7qS~>0vy;J0ICq{Ll`>|h{^|Am zxL`Rx;I1oiy8)dyEm+Ck>Z7X>nXmjYU;`1pd&mvimTUFz>7GOW@ir9RRFSciqN^wb zcwJhyIs@B`?Hgl0Rvg}K9&CKqoulQQtedIIW>_I>ukx!|T(EI8}G8#k2v&((?G2?(WGvO7M^wdVTw*Lmo5keN1#C2XZ7Cc)#FmH zvh+{)1GCj#>KqJQnyA~T%T0BG*K34@L*8jmrCgm)q9)B-9vV`_WUcGH%)-QmPqqaQ zPl7Njxti9$^jV76Y2$qgJWuKiX(nqydKS3p0cI6KBtChq%&KifnC;u^)OR3r&xQbP zk{DBEZZp39h>8-w(JD}^d1yS(f}`+wL$D!RcVP%U?4Mg}#v_r3W&^^}Lq@)TqW}ZKOx3{Y)a^L5=}e;n%aO&` zU3h78Am}k=mFS^F8UDOr!P@=zn?>g*vATlcmYBwW086dHJ;kKG`n)#iskw528yNOc zcW7ekYX5dkSAwHLFxSCWI`e#($0T7PUZ{;ErLrmlxhR+HD2xjqf#*7uc-)kmp+4eW zjsCc-;kY){r(Gxyb`h%!$LAa;n-S%n2%j>j2Ry8*0*rPlHZk;v`6LN?iotT5^K+7$ zlj)utHs5Dxfj^daDm30js^Zm5sq&D{IPSgbO`1`uzXpzLUKaP1AJm=jjL56sgp^JHWhLAa{zq82b1Pj?Tc@RGZw={3+%hTpk=czqN4bQQLvS-X&F@as>2_IPU z@TLc}py-o;aI`3I<3wa@5^S6&eLZJ$xM$^Sc)`f|bk8{UePDm=^v3iP9x=!^;qG#%{+evtv4mlQd7a&|FgMB|x;O3#zX6dTQr`X7oN zYmjl5lq$fm!1lkG=W2ly>3ON$MU@cvhsfM~?XByV^URW!0#7#WmQ86r%%^BSQ-z%A z3eK{(GMlwMv39IrwBl%NOjP;!r*elgc7#B4q)s|y7GqmQ=S28x#0|(@i{gIF)AVw6 zA2hPh3t9y{KbMyx7aGX`fRR>&E7tr<1d9>k{a0yQ*Y=nX49#IJSz;D(DO%r)%2x6A z$JP22JN+0DXmX*x1qxgO{vNWad#;k@*5p1w!|`Xrwe)kjpvFyn`2?K>Bcew3kIA#QKJjxO6*95JzCgeoLjFqk5%q|J~+V$YiO zDK<^$9Nr>Ji8CapGK$X+Z_5b}CgUpk23r_HZf*aJ?JaNvEk8SDj3|PrZV(@E#>h+g z=%x-X%PX)WUEm4b)M#wqN3jle{oegbM{$y;4fkzeSYMe}jVwn`Tgi-{#L(C<8b^{z z^a%a(^I}dktprv+EO;1tz7QL^a<0ojBZd(=5=+nE+(nNPCH6tP5p~!*L|PTB#q;Wu1obnP@CqeOpMQlD}&Ct+EUZ6#Pc>#-emiBg*@)X~3rs zkkl@^XQe%IUa-~A7PRG0v#!a&*-qO)4wAcsC{IXs5-FIPBa>2!ioIw#YW!%nA8lTe zz$ZSKHFcu6IVrOb=K}l#{~IZc%hOG9@8tLmZ#6pi&4fFB450NFsa;eS-H}@>%Tm^* z*x}ve0E>z4*u2xkt_wvD8l4-CLM=Vi3)K4qvf01T%ep@eXyT-J)*z;7`6x_!`*}}5 z6`#0T+;qlo;pLTwxj%0eo;U@@q3BTGP(Py`%+AgPLBpJ``ZKRRzXS@h9rx6kpX(=H z2WN#@vDMzYowoK~*k8<_a`<`mWS0#lrNW-bNLT*B@11Rby?l95_8qpeIg0O}>1Ps* zeLpo{+eM3rRH-KXzC5x8ZT-gger1rS1D#_U^AasxFL;QH!)z3cDfJI=Q|w4{rLS+e zLf(?IyQ2}?80;tp`&Su@04PD1c9(a+{pL?cLmr4^!`W<*uK<0+GA zfcsuy38vHqw!>YM!+A1w9`%fyp23p7a;8X?n7@f@1{6CVu(hc#OXOU^Fv2S=aI+tM zG5=4&` zsW~aWcI14$wNd)Dfdgh~up@_ikxJ~i6vDji?D+HX$`H?%3ekD__#z0qw)-xX_#2i_ zN<+H2HnlbN6qLE~-C&DE+O6J$N|bY6>U(lpI~QzQ34{e%qHiCO7n z2~pWzkL^v#7Di{E%0TdZ9j|Wntn8$t)Jy$l636-{dRB~Ly(}d{jwl@+$SSt>w{;ft z{_bO{9Y9i>=_FWtqEvZd3V*KlNO%9V?6^!IDR4Z)vX~?1kC+mTb#A)0VPzwtzW_Qr zb~jIoPnPdWbtK;j7fMVrH|GcO2^rleIK%L|vXr>wFY>^ojWn_0efai5xmCZHzy2h_MOx*B;7|eXebDnZNU#1RW&>KFxax05g9ZqF$_;atb6d{CRaL0w+y1Ckr}WU%moM`LR79a6L@~a|c zM^szPkzMgRT#>9szpC%QTljIn?Hg`?#|1AS+K)1%6|B%yeDk^X*Os|c_NO8Az>Bfd zYy%pcT=TiXc!u8W=(}0UL7L=6R@yX(&WJJUtgre*lh8nIzM21FByGD$Q_ocP{^p8CVJt^;?D1PucFx)z3=MhPbn~;R#_=;Hk!cCeRygV zuBQ)bdTXD`=PKB{hOY>Rl;!5sS4n=YO!l1WBo2-4M~4B#l0c0UGNbKX?h%FD4K44N zKbqFm1ceQ`l@E>2NJ7HCPM)6k1*jzX5B}D`MWvuDE>w^zJpp9s(Zgp%7IG9YX6;2v zaeJK~K`helQ#STU($^i|1EC6%;>9#|g`}`vW#pF)*_qVB(aSX!wE3@7%@TaWYV>c^x!E z0@y&(Nve^GcyF*+cl;h@!Qy?-;jSi#N=3M&@##g|&ggoAxE%?o^}c>T+IW$y*0Nzc zO2y2Fq>l}GNiG|gB|s1L6jyGTn%oaG9AQdP)^FMLYq2dJg9l&+ru#8+zeX`6-MLYQ zzjRb5B6}_i%-(Hrmq|6N?~aU2B$P$yGR*xxc^;#|JXF zX7IJr^jfamGP!QD{8Lr`S5!{OoE=I+%vs&z00=@VuCO^hw0_n_EBsUNM^G?N?9n@y zYLG-5&#>uT8hI`vTdjGP8(B8blwHHMP2!YKZ0I4Ztfa6FEnqpySgi%~IV*((u?gtQ zFH@2Y#`g-cDzbbjYQabZ*HYMMRFeNpLvLX$wf7~4#L3{gY0ncKzpJO(PEu{UOwbEc zy;R^73A=b(X+!Rats`#k@<6FTdPq2Ljg;6|hjjGaS%#3?Mpw=jdOF_-*=mG3d@tKn z4-YW83E02*^f9FvjF0CKTm4lsK3~3Agsw_NxeQeJryTouD z9t9aB_1)UtpES7@x9x+qa8tSK3OGW!-$lF>P)9~mZrrI$-OX1klq_z3B5;yEa%!3d zPd5J#e&5>WxUv#xjBY9-X1BoS)98t+1oG`emfnI7Rir`O=GP|mWbp!v_^wrN^@hpB zp0J2DIi|_mjOef>3mUaQ1)H}g$&}S?wY$EC-kL03ZIWJP=3T0JmGLKkbji!L?b*5e zArzcbk$tj*Bt)sz``SnrO|H8JIEfD1(GJb99;xkjC&EATnj>YNS)N~HJsc*aDH!%8 zxS{y?rvbm|DA|lf4Y@ihfASTIa=vSQo}p7XbxHQ!(B#7wgHi-5Nj{JGT3hP3*A8!K z9L>N&O=bt`UB9aE4!Fhl6_`p+7#MZRcP{AM&NLfU5bTlR%bP@SP5cw8`LR|7%oXeI z1UjAC#}v*O^N~!$`9E*ofy@ zpWpn$0Stm3Wa-UQB&DRhbEJeeTYvE5$gE2Q2^nYYbN1U#$%&Z7i$qz7|(P+NnY0u#X9_8@%^OWkF1i^byT=HCOLVvY>ZAL@Kl;&LaPLE@0kK(+gVS z;Q95=KSxZV_~E=upc3(pf0^u}R4Wb+-*eQeD&ORBC-oOfTy1QUW<&6qv%mo!?!`ldB3^&-xxW|gPxA+2TkZ?mj9 z3kgBDZv>v%Pp*>029HdoNsUgxS3|4cg)G$5t>zZY6sN*o!#y!$tp1p=7O64ZH6}Us z1R+WVN49xXXGJO*g}BiNU^=k7+8psL(oi;o4K*KQ-GiU95)%a5od;%CWE%tbH~XjU zn>3PAT7=>N#vS&8_Y0ZyqFJOyVrNhd*gJ=@+f}~nB<{c=$Pd;f(0_M*w)!zp@bO3lW8(B_e?i2-~|?g0Kc za)mx7emXLT1@neRQU^#FwLh>O|AyD9uVt1j^@wwTm7jCCZ3{Uk9{_Wjmx|@xP^{+4 zbEcoa>R}MO8)gTeG|C4gSeT8X49W6T$2O)MCzj$czU=v~XV0P>E{DgPiJXvSay6Q; zgWo4Ih#)#7LjV*KEdt5r%aKqIw~VM> ztX0)t6uq}USJYj>>Fa1*jxwYK_u0D*(72%uAl($jH&kos7onKEPsk97Wspr&xywf6 zLN=sv>%wTvw7_XWe>2lg-FhCU^gYtrk5R(gUQVv(lUPkGo`o&_Z-d;9LdL_c_c?!- z2)k9iQSblmcmzv%-I6~~Y-YOAMDA33A92;gi4yA_#clrj7IZ>5WDsP7+d6$sOdGE1 zIN`#Gj61Wq75U@zGV^-RR>7X}M}kJp-W*ONKWiUb{+y#cGJr0f2qz})x02c0c(!#Q zx*V)nr_!aMkqIR(8k>{Ctlr(V3EbXKApqEtJOFI7;#@T-T!;7S$Z1bou2tWs3Wyd> zWG5xS6O{!$Bqe&3f87g9RY-#j6`c$AP@?N+&OO{I9`uHJ+%mai+?uPKceufx@KcTb zr*`PLSW9$>A$u`H4@m~ERKZm+<(GQstmBorH<-F&XZ2y7L;WPOq8xMXV$Ax>TI!r? zT5MQ!aWR$1>tVWLv&{0pY5`n@_YZ~s9-6IHR*o?;?S*V;;MASjdzsLcFc#uip|73hhusT$w&@KH;NMKK} zlCtX?CDT~=^3qM|Ah=+3aR16s9mDorb0iHDE#!dPqtA~li2vYSu=p_}jIIi5wR`TP zoVZy>S#IdUM3cWBGu(yy^*UgPbIy<~;+D@<#kldA4iTP4DzkC75DDC+Mdf=zK4}W* zfe#UAdD^!EZJu2Pz0Rk!Qujaqh<2G}gw15OMqCe(tVIgHGxM=h0p{eNPbL<2X5Av0 z+7!bkASm^1)hl|4^}p8bbf=YDnF|aL)AictB&U<6_-zY^_PUuJik~h;2h%J1&S&)+ z_tZrPFh@ckG7o!oW}A(sKCwet&_pi|O2;y2u;BTyrSwA-r@`j=l1*Q^anz*oVM;sf z6Xnkb#c?`LUf(!vXi|TLbyQbyu32sv5P_{0Dg$i?Rr*^UZp%c6groMzZB#&{I{mx# z$Zy;$gz)9{eG1O@^o;PsvaBfnopX6_YyeX&Y2s?#4$7I4WCn(R<{FWHpFe#qeb4^> zj}*(R^O*4`Jjq+vxM>=go;Fz9Ort%*W6rZ8l=USs3k0s!jLo`q2TwP-O>Pv%NH=)3A2ptqkom{c7eQM~>>kS(k?#i>Ew@ zK2ugGu#34e$Oz8%HKB4>b17ugFVlF-EPB;{lj}i#SR}J=14{!|V4I8fAG1me^7-NY zW%)EBXbhs^lkS@h^Oy_9?=(nT8`vHmhK6r)7x4MQI^aFj{GHW@BC_%1 zB$I(kk;>ZxBrDaN@Ln0%3?$Zj$ofkbGt1kYQ@vQTHhj|^<$=(i!(A1+!Q<|}B|pKP z&;ZPQG(VWiVo2PYOMBhKTMF@^0#rBF!Kz4)P(fwwS^eOdI7>u^Rz6@qy2r`OXO(`` zri2=Qa{p%N(@GLMIR_mcs~j=%dg%4d!2jTGbJqEXwdFX?PjDNV%|n2y@YFf?;iQV zwmNs+JN=hANOadrZnc`;m){6reMj8Q@9D=eSN1rLt5U9^TKSlotaR|a^h-ptg9T=JD79ZfvmfGOT+R3qV_>>k=`P7HWQRs=+?nYaFCx#j|TOu0%@7lioeCV`3-iF@v zHz4pKDz5^n^h?Lv=%HF^qtxb^A}}YN{q5cn?;YbhH^VbSUWyThKouvc1_Qxy?0OX& zp*d;AXLPC7sID(-q+DQTQcrQ{1J+O{|4fuL(tL6Mp+D7j87zM(GsRCcSYa5sEY^en znqx(r;384hhBUpD)k2J z72e7;Ro1i2lGyo8odTS@?)K%rh=UtyEfH&@TQ{(g3`&z;=xI`N^S$sS-u?P(wZ-^U z()i7}Q<-c1=*UtcWqZP0WjvW2(~)yR?+`rJ8~K(sV<0t4n=W~LEAQ(HUFpl@-<=xg zm>*qr{TS)=dG_w(Y99FIC(}80dKI>Iinxpxjd%ESs}ASSR69+9;>7q(39Xg)c`X}WdyZaC6 z$!WUq93@AO_z)+=-va{!-WnBT*VZChQ5moIt&tq*r?jV2LB*C_`Rka}Jo}Cm{8d{6 z`tO2i?`8h4ZJ7K`d8N+AoIe2(stMH%<9g8v-@6#*S1{*a&&}txm57c`)qb#G znxEo>_}zUvR1@{1_-ssmVl+zXXR_swa;L@l9apF2CIhCs;az8qJ$<2ebA~OgA|0_4 zY8#mZ`ptQvhK$RWInuwHV`u{dj7Lx6u8-ZJ;Z}HafBVfUH_D7rF_LV%fuuLJHb)@* zc$p>7CW-grO;q*KhLF7OHI7&PXtO<;;k*N9R@$X-A*1W7(G@2&TZLy1suh+uuWu`Y z{d;vwe^+T#JCOeYlyD8#_q;c0jZdTL`8|6BiONeRO0iDDFJgwn2 z3@?xgJ^Xc!=;% zows(H{@$LpU~+t}b4+B|K772ABC;X#rB>tj5T_g&z;58(Rl3`GPw+v0z`d8%Tp*D0 z1DsOJx&`ycw_U>|5@y`dSEg_4p%IHC&j;}b@;GO^gc}AfbcKC#G0e~UW!9vS}$%Y;w;(kMXNkkbH2^)mX7zY;Q0ol;>SDnjF&ljV|;%(5kSUc8K zz<|U$yA2SarY<{bE@HUirfM3EAD{Lb+a;Fcj(>(C4+xdN0(>+o*zMIf91JRdieaRA ztB&PyoMqW(4wT^HMM4xhoH9J}<^A*nbtm!h{!I<|%+goCzto9hH8+fOz?Emj``@sA-cV9Ia&Qh++9)HKya4Cf7j_2Mh)C;&b*&`vzVtSRnin6aHO@}=zQP!(ykN0E|C)rZ}r*JCS>2x^mgu!TPM~^|d_^+|q zRa7vYlvezWM;fC`ftcQ*3dNOmyM(Cp%Op9P)8}7=qdVw&IT6N&x!kBH(=`oNiHr~M zd~i0O@;${b`6zeVLuwml^U;v-Z(;g`-A?;HnjOa z-R9$dmx9O!ks({6iQ1g6C3d>Nn(T3(mnVYdaDqeWAGy?Oc{uTg=3_vHS8;w*m>B%% z!&2A{nZA~{vJJQgD^Netkc|cGDJA>q&f{K0lH{1RzCl=n+KHXI=cl>G9~HgMqZ87G z`ym{O6U5NP-#cEy#0b(O&TR@uJsc}vev~y7Le1CdU}ktG%2O7ta*dx*_8D5@_jg__ z=8HgRWPUZ12<=HQZc>R#5XP<-Ku74uUsMe)G+4&A7Z4QG^XR ztdoGbv-q zDwYR>Q5&Zta?sKd?SC??{Ud|BgzK^(1Y51&ZGZLaMo-WC7hqil>PcohCt{R9T9NM` zUR*Qpsv&)M4X@p$Iz$(32+ck`nigNgzYbgMVBNlFp05);duMFs$Tt5$d)gQOwZKiv z=#G;d1*RkMVWr~yx<4KJ21$jbVpk!kd0^&yn3KN+&e*ZMHok-4bxr(U1SinYwF<8)!Mxg-3notllr{qDo-PU>|1>*q;!(Q1ZHhaM z=#)Zs_uBoz7pxRyngMku~S3DL4zn< zTxgK_VHGcePLvP04ZT_RNKufHX85cd&H5(tH|%u99=j$(xl}LhaSEnc)#B#&j;8-B zAr(+j^)ecTL~&G}%?l0Clu!27xb^*&jQ~RQo>Z%p@oD@_W9{9xUZ;&x@wpl)%J1oQ z7J(H@he`20U!5HBQ&>D~)SMLO_m$O5{$ z?XgZif!dLOTI`rSV}1`10#_U56U8*X7j1%!6aR@EQxMP*eswCDkohPMl|CBvmwKlo zglu@fAf2J-7g4I4Oc;>_f5Hsx>mmCiIG#{48O#iMO;`0&p`5Y?E;pwR!?RTQ;Tk@t zb404C)>X9^e;#2iyut}_Xo{WWUvRQ6{#j@U#V{j{(@5kEBhskvjE{f(uJfdh;(v<^ z!rj3gdUU7qB7EF{*Y>P*!or$|nRB|cC=oXQf%^0IqRmh$pzsGn2>)(y!;Q8X#F@f!3CcnIU|qG!8G@`WKmgvZ--7kP2@%BE;6D*Z)jU5 zmL^|UJWenAmX3a$-Ue8h?&YvTiie}}iHRwGHj}lCwbl`GMlb5G3gg}BPCNYkLOJ^o?LYVUMoBBj%3G8r%!b${%<9izAaP5(naf?hHoZfKWG?kH)x0F z#xwM|G>YUx`dU#_boM=sSZXgez0@s37gq17JynX<eg3{(0* zy5TH+<7SrTSWn~q(__A2n4_1I@EwNL1us}RUWKglnzzY^fHHX*fOsW5W;m*8?I5hc z@lA3RBFQwfV0Be@*LTF6eja+W)Z^v|?~g12T$9Ax>ON7Pk>Z%uOC)6jmowBakp%Ht zS0Y2Tu#zn-A@cqX1xPQ}^VGjHLcEBi6LH{fhYxB6&K3nVs?qNQ_gvxXnKv-YMDWu# zNX3fNyh_hDCnWPC`kvbWWry}7)BZQUx|naKmDOBL6pE6o#dpOX7B2UsWvT1tIrL7T z3^=iskhe4RA+*~mR>VB4OXDGUH;XWscbkr$sZWGwwA4`L!6-ICB;+}5Q* zUig-u+g{n$D&S2M9GsV-*3=4 ziZl8t7yYFq@HqnDbL!|kWOgh7_X~-9f;i#+^W4*)&6IYc1jUT1lcP~?-j~AD76l2^ zB+7R4{-D0#=@8T!TVlxBhlUq-JkxJP=hM&;`9TxzM=KGs#q%&xGEm4;gJ_M~qTCE* zsO;WNuqnE4fU1V~06SwONVvOE`!A4qNcJB+S9!VX0Tl=}W{r9T2TGmnBLXzo_OKRw1%g z;SSmm%a1!ukohW)o5m)2hWck+`qt6|oO+nDt!${@+XyI zUa;*N#U(}ZBd(fzq(h*)NdMlwkYYmqO79@oiIXe_2D202U^_lxy zqhPIPIG){Mh9-o3*m(0wk8W6O3pNgQ9vzc#`?GHo2?t3$l+paC9SIt4JTJ*JQQl3Z zrgS9g393UoJP+}ZZ2MPFK-xWi0921u;p~gKXKogzZlHloLM)_Yq;AZLSl(+$ZXOy5CiU;N8 z$^4QRF>)!pPy`4-6$9yX<=MzEsQ>)ar6IDa3Q(m@xfJY&izK}jdS{(PM%kBZaAF+l zZS*(PdB)&ZqPLmPRm-mTu>Hf-$Gzu?CFggGU3>7m!IW%mY$2jBZ_w+F(-9QFKeM@( zg5D#c8Yclp!N%dKW-Q~38lMed-H$C3_}~PNf;6Z(99xUwsJHG4tXE#QF7E3k@grCmqhb z$MymJW6=KC5PTw^z7rW>D)T|m8Hy9R(kuF@gP5fsbNAaXq8s-oBBW=|o1a)NoCWiz zt95<5m2w*Yt_P1*LcNS8uRfE>|3bxl_{#x&3{Tb=6)IoyX!-n5Xl9g0B|(ompD)T zCe<;BR3yL)lzuW|PapghMI`Yy=<1STbFtZLEMI8)F?&<_uv6pg8nI!D$`Z{Up%Tf` zYy4$ZLmUrhIWQAm`8|j((8$NE1tBN~4eD*%8*0{Sl5x2nBj=E*=%cnM%Y8?Z&#YM3)gEo;=XiNpr2MBn~ zmpj+4wG_oAM}UHPd_-Equy93+5VemxQJ0jBM=ilic@aXN0IYuu#EYLTO7a_n5I6{e zVJ1Hmc;W+9lvz0PiZ&dRWW2aANml~6J@uwABH@5MZCrxBv_|tv@2zSd`ipbcp8(Y; z1waALM%2UUNxjt6eL(l=2@q8hzblT?X$k~& zn?v^Y!jZy*eU)IZVFjSZsb}TJe)xreYhMA?DY_}%g(Hb*he^q_S^dAtC-%wv^Sn7- zHP3hXE|dWFNe#Qd>c^da)VU{%UVP{D9-wP|`K+RMX-87745{ zYUto-x)qy%swB_pqMBlCZvUT+7pDNKHX^)bwfQECvtcBYkaZ?Sg0v&0qZ($itgrAK z{3_T;dC8!B{q-t6xZZ9;W-A)(#iQ{$tHw;0RtxB>v-&vp6@c7k#2qzQH)%WCG^kt&le zRjFp^ImDk7k#k=tPcEy78wJ*55n%Kn+hQ}^O(r5Hs(tdnhwyq(O^`6kD4}C->M2G- zYE?SW+0k^gAm6))K`)w-B=dr}OMw@J^Xweipz;|N5ANf@0@3PzFzEo23^4KWAty64 zvkhWFk}ac@0XqYx^7d|`Dx-HXLVM@oyVrEnutbA6~?sz_VQ54G`eMqCU z(m$0_vPGnFBvkbJktmb1f$MW7U8kpiU%Wt)=fo92=i*19pfu7BxM&q&%t7|wAAKlr zBJSd(xFewYpNpuj0~c1yD+vAfl7IE`uQ7m5@voEo|Ah=T^u7H6K!pdl^D+Gw0{(@7 z|6d^BtSe|k6#)NKUv=$sGHHR+R{%f;mb-w?aQWhRo#x_roAcu6sCse4qArdo2;|}) z^xqpU)bOvCR4>% DisplayedCountSpriteText.Font = value - ? TournamentFont.GetFont(typeface: TournamentTypeface.Aquatico, weight: FontWeight.Regular, size: 60) - : TournamentFont.GetFont(typeface: TournamentTypeface.Aquatico, weight: FontWeight.Light, size: 40); + ? OsuFont.Torus.With(weight: FontWeight.Regular, size: 60) + : OsuFont.Torus.With(weight: FontWeight.Light, size: 40); } } } diff --git a/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs index 47c923ff30..75d63cde13 100644 --- a/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs +++ b/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs @@ -201,7 +201,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro new OsuSpriteText { Text = team?.FullName.Value.ToUpper() ?? "???", - Font = TournamentFont.GetFont(TournamentTypeface.Aquatico, 40, FontWeight.Light), + Font = OsuFont.Torus.With(size: 40, weight: FontWeight.Light), Colour = Color4.Black, Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, @@ -209,7 +209,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro new OsuSpriteText { Text = teamName.ToUpper(), - Font = TournamentFont.GetFont(TournamentTypeface.Aquatico, 20, FontWeight.Regular), + Font = OsuFont.Torus.With(size: 20, weight: FontWeight.Regular), Colour = colour, Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, diff --git a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs index a0216c5db3..b72cacc66f 100644 --- a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs +++ b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs @@ -126,7 +126,7 @@ namespace osu.Game.Tournament.Screens.TeamWin Origin = Anchor.TopCentre, Colour = col, Text = "WINNER", - Font = TournamentFont.GetFont(TournamentTypeface.Aquatico, 15, FontWeight.Regular), + Font = OsuFont.Torus.With(size: 15, weight: FontWeight.Regular), }, new OsuSpriteText { @@ -134,7 +134,7 @@ namespace osu.Game.Tournament.Screens.TeamWin Origin = Anchor.TopCentre, Colour = col, Text = match.Round.Value?.Name.Value ?? "Unknown Round", - Font = TournamentFont.GetFont(TournamentTypeface.Aquatico, 50, FontWeight.Light), + Font = OsuFont.Torus.With(size: 50, weight: FontWeight.Light), Spacing = new Vector2(10, 0), }, new OsuSpriteText @@ -143,7 +143,7 @@ namespace osu.Game.Tournament.Screens.TeamWin Origin = Anchor.TopCentre, Colour = col, Text = match.Date.Value.ToUniversalTime().ToString("dd MMMM HH:mm UTC"), - Font = TournamentFont.GetFont(TournamentTypeface.Aquatico, 20, FontWeight.Light), + Font = OsuFont.Torus.With(size: 20, weight: FontWeight.Light), }, } } @@ -203,7 +203,7 @@ namespace osu.Game.Tournament.Screens.TeamWin new OsuSpriteText { Text = team?.FullName.Value.ToUpper() ?? "???", - Font = TournamentFont.GetFont(TournamentTypeface.Aquatico, 40, FontWeight.Light), + Font = OsuFont.Torus.With(size: 40, weight: FontWeight.Light), Colour = Color4.Black, Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, diff --git a/osu.Game.Tournament/TournamentFont.cs b/osu.Game.Tournament/TournamentFont.cs deleted file mode 100644 index 32f0264562..0000000000 --- a/osu.Game.Tournament/TournamentFont.cs +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics; - -namespace osu.Game.Tournament -{ - public static class TournamentFont - { - /// - /// The default font size. - /// - public const float DEFAULT_FONT_SIZE = 16; - - /// - /// Retrieves a . - /// - /// The font typeface. - /// The size of the text in local space. For a value of 16, a single line will have a height of 16px. - /// The font weight. - /// Whether the font is italic. - /// Whether all characters should be spaced the same distance apart. - /// The . - public static FontUsage GetFont(TournamentTypeface typeface = TournamentTypeface.Aquatico, float size = DEFAULT_FONT_SIZE, FontWeight weight = FontWeight.Medium, bool italics = false, bool fixedWidth = false) - => new FontUsage(GetFamilyString(typeface), size, GetWeightString(typeface, weight), italics, fixedWidth); - - /// - /// Retrieves the string representation of a . - /// - /// The . - /// The string representation. - public static string GetFamilyString(TournamentTypeface typeface) - { - switch (typeface) - { - case TournamentTypeface.Aquatico: - return "Aquatico"; - } - - return null; - } - - /// - /// Retrieves the string representation of a . - /// - /// The . - /// The . - /// The string representation of in the specified . - public static string GetWeightString(TournamentTypeface typeface, FontWeight weight) - => GetWeightString(GetFamilyString(typeface), weight); - - /// - /// Retrieves the string representation of a . - /// - /// The family string. - /// The . - /// The string representation of in the specified . - public static string GetWeightString(string family, FontWeight weight) - { - string weightString = weight.ToString(); - - // Only exo has an explicit "regular" weight, other fonts do not - if (weight == FontWeight.Regular && family != GetFamilyString(TournamentTypeface.Aquatico)) - weightString = string.Empty; - - return weightString; - } - } - - public enum TournamentTypeface - { - Aquatico - } -} diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 1c94856a4e..9916b0e042 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -54,9 +54,6 @@ namespace osu.Game.Tournament { Resources.AddStore(new DllResourceStore(typeof(TournamentGameBase).Assembly)); - AddFont(Resources, @"Resources/Fonts/Aquatico-Regular"); - AddFont(Resources, @"Resources/Fonts/Aquatico-Light"); - Textures.AddStore(new TextureLoaderStore(new ResourceStore(new StorageBackedResourceStore(storage)))); this.storage = storage; diff --git a/osu.Game/Graphics/OsuFont.cs b/osu.Game/Graphics/OsuFont.cs index 22250d4a56..54a25875bf 100644 --- a/osu.Game/Graphics/OsuFont.cs +++ b/osu.Game/Graphics/OsuFont.cs @@ -19,6 +19,8 @@ namespace osu.Game.Graphics public static FontUsage Numeric => GetFont(Typeface.Venera, weight: FontWeight.Regular); + public static FontUsage Torus => GetFont(Typeface.Torus, weight: FontWeight.Regular); + /// /// Retrieves a . /// @@ -45,6 +47,9 @@ namespace osu.Game.Graphics case Typeface.Venera: return "Venera"; + + case Typeface.Torus: + return "Torus"; } return null; @@ -65,16 +70,7 @@ namespace osu.Game.Graphics /// The family string. /// The . /// The string representation of in the specified . - public static string GetWeightString(string family, FontWeight weight) - { - string weightString = weight.ToString(); - - // Only exo has an explicit "regular" weight, other fonts do not - if (family != GetFamilyString(Typeface.Exo) && weight == FontWeight.Regular) - weightString = string.Empty; - - return weightString; - } + public static string GetWeightString(string family, FontWeight weight) => weight.ToString(); } public static class OsuFontExtensions @@ -102,6 +98,7 @@ namespace osu.Game.Graphics { Exo, Venera, + Torus } public enum FontWeight diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 07c9d37a86..676b4b6c7a 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -157,6 +157,11 @@ namespace osu.Game AddFont(Resources, @"Fonts/Exo2.0-Black"); AddFont(Resources, @"Fonts/Exo2.0-BlackItalic"); + AddFont(Resources, @"Fonts/Torus-SemiBold"); + AddFont(Resources, @"Fonts/Torus-Bold"); + AddFont(Resources, @"Fonts/Torus-Regular"); + AddFont(Resources, @"Fonts/Torus-Light"); + AddFont(Resources, @"Fonts/Venera"); AddFont(Resources, @"Fonts/Venera-Light"); AddFont(Resources, @"Fonts/Venera-Medium"); From 08756186e983d533d739979cdcfaeca61c5a89f7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Mar 2020 18:35:28 +0900 Subject: [PATCH 30/85] Update resources --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 983c622f77..d62011bf1d 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 3b00b807a2..e407fca8b7 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -22,7 +22,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 3552047cf8..cc0b8074bb 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + From a2273234cb9009745fc7c6b2eb080d720a584717 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Mar 2020 18:09:25 +0900 Subject: [PATCH 31/85] Better handle startup when ladder cannot be read correctly --- osu.Game.Tournament/TournamentGameBase.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 9916b0e042..69f2bf57be 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -121,10 +121,9 @@ namespace osu.Game.Tournament using (var sr = new StreamReader(stream)) ladder = JsonConvert.DeserializeObject(sr.ReadToEnd()); } - else - { + + if (ladder == null) ladder = new LadderInfo(); - } if (ladder.Ruleset.Value == null) ladder.Ruleset.Value = RulesetStore.AvailableRulesets.First(); From 372060bc2b37dd5547defdfe1455e38f85e90310 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Mar 2020 18:11:21 +0900 Subject: [PATCH 32/85] Only trigger changes if new user information is actually populated --- osu.Game.Tournament/TournamentGameBase.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 69f2bf57be..dd93215fc1 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -201,9 +201,11 @@ namespace osu.Game.Tournament { foreach (var p in t.Players) { - if (p.Username == null || p.Statistics == null) + if (string.IsNullOrEmpty(p.Username) || p.Statistics == null) + { PopulateUser(p); - addedInfo = true; + addedInfo = true; + } } } From 093f2affdffcc95fb91dec285954e823bbc229d0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Mar 2020 18:12:42 +0900 Subject: [PATCH 33/85] Add seeding screen --- .../Screens/TestSceneSeedingScreen.cs | 125 +++++++ osu.Game.Tournament/Models/SeedingBeatmap.cs | 23 ++ osu.Game.Tournament/Models/SeedingResult.cs | 21 ++ osu.Game.Tournament/Models/TournamentTeam.cs | 27 ++ .../Screens/Editors/SeedingEditor.cs | 288 ++++++++++++++++ .../Screens/Editors/TeamEditorScreen.cs | 27 +- .../Ladder/Components/LadderEditorSettings.cs | 43 --- .../Components/LadderSettingsDropdown.cs | 26 ++ .../Ladder/Components/SettingsTeamDropdown.cs | 55 +++ .../Screens/TeamIntro/SeedingScreen.cs | 316 ++++++++++++++++++ osu.Game.Tournament/TournamentGameBase.cs | 18 + osu.Game.Tournament/TournamentSceneManager.cs | 14 + osu.Game.Tournament/TournamentSpriteText.cs | 16 + 13 files changed, 955 insertions(+), 44 deletions(-) create mode 100644 osu.Game.Tournament.Tests/Screens/TestSceneSeedingScreen.cs create mode 100644 osu.Game.Tournament/Models/SeedingBeatmap.cs create mode 100644 osu.Game.Tournament/Models/SeedingResult.cs create mode 100644 osu.Game.Tournament/Screens/Editors/SeedingEditor.cs create mode 100644 osu.Game.Tournament/Screens/Ladder/Components/LadderSettingsDropdown.cs create mode 100644 osu.Game.Tournament/Screens/Ladder/Components/SettingsTeamDropdown.cs create mode 100644 osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs create mode 100644 osu.Game.Tournament/TournamentSpriteText.cs diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneSeedingScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneSeedingScreen.cs new file mode 100644 index 0000000000..eb46d75705 --- /dev/null +++ b/osu.Game.Tournament.Tests/Screens/TestSceneSeedingScreen.cs @@ -0,0 +1,125 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Beatmaps; +using osu.Game.Tournament.Models; +using osu.Game.Tournament.Screens.TeamIntro; +using osu.Game.Users; + +namespace osu.Game.Tournament.Tests.Screens +{ + public class TestSceneSeedingScreen : LadderTestScene + { + [Cached] + private readonly LadderInfo ladder = new LadderInfo(); + + [BackgroundDependencyLoader] + private void load() + { + ladder.CurrentMatch.Value = new TournamentMatch + { + Team1 = + { + Value = new TournamentTeam + { + FlagName = { Value = "JP" }, + FullName = { Value = "Japan" }, + LastYearPlacing = { Value = 10 }, + Seed = { Value = "Low" }, + SeedingResults = + { + new SeedingResult + { + Mod = { Value = "NM" }, + Seed = { Value = 10 }, + Beatmaps = + { + new SeedingBeatmap + { + BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist" } }, + Score = 12345672, + Seed = { Value = 24 }, + }, + new SeedingBeatmap + { + BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist" } }, + Score = 1234567, + Seed = { Value = 12 }, + }, + new SeedingBeatmap + { + BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist" } }, + Score = 1234567, + Seed = { Value = 16 }, + } + } + }, + new SeedingResult + { + Mod = { Value = "DT" }, + Seed = { Value = 5 }, + Beatmaps = + { + new SeedingBeatmap + { + BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist" } }, + Score = 234567, + Seed = { Value = 3 }, + }, + new SeedingBeatmap + { + BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist" } }, + Score = 234567, + Seed = { Value = 6 }, + }, + new SeedingBeatmap + { + BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist" } }, + Score = 234567, + Seed = { Value = 12 }, + } + } + } + }, + Players = + { + new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 12 } } }, + new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 16 } } }, + new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 20 } } }, + new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 24 } } }, + new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 30 } } }, + } + } + }, + Team2 = + { + Value = new TournamentTeam + { + FlagName = { Value = "US" }, + FullName = { Value = "United States" }, + Players = + { + new User { Username = "Hello" }, + new User { Username = "Hello" }, + new User { Username = "Hello" }, + new User { Username = "Hello" }, + new User { Username = "Hello" }, + } + } + }, + Round = + { + Value = new TournamentRound { Name = { Value = "Quarterfinals" } } + } + }; + + Add(new SeedingScreen + { + FillMode = FillMode.Fit, + FillAspectRatio = 16 / 9f + }); + } + } +} diff --git a/osu.Game.Tournament/Models/SeedingBeatmap.cs b/osu.Game.Tournament/Models/SeedingBeatmap.cs new file mode 100644 index 0000000000..2cd6fa7188 --- /dev/null +++ b/osu.Game.Tournament/Models/SeedingBeatmap.cs @@ -0,0 +1,23 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; +using osu.Game.Beatmaps; + +namespace osu.Game.Tournament.Models +{ + public class SeedingBeatmap + { + public int ID; + + public BeatmapInfo BeatmapInfo; + + public long Score; + + public Bindable Seed = new BindableInt + { + MinValue = 1, + MaxValue = 64 + }; + } +} diff --git a/osu.Game.Tournament/Models/SeedingResult.cs b/osu.Game.Tournament/Models/SeedingResult.cs new file mode 100644 index 0000000000..87aaf8bf36 --- /dev/null +++ b/osu.Game.Tournament/Models/SeedingResult.cs @@ -0,0 +1,21 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Framework.Bindables; + +namespace osu.Game.Tournament.Models +{ + public class SeedingResult + { + public List Beatmaps = new List(); + + public Bindable Mod = new Bindable(); + + public Bindable Seed = new BindableInt + { + MinValue = 1, + MaxValue = 64 + }; + } +} diff --git a/osu.Game.Tournament/Models/TournamentTeam.cs b/osu.Game.Tournament/Models/TournamentTeam.cs index 54b8a35180..7fca75cea4 100644 --- a/osu.Game.Tournament/Models/TournamentTeam.cs +++ b/osu.Game.Tournament/Models/TournamentTeam.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using Newtonsoft.Json; using osu.Framework.Bindables; using osu.Game.Users; @@ -29,6 +30,32 @@ namespace osu.Game.Tournament.Models /// public Bindable Acronym = new Bindable(string.Empty); + public BindableList SeedingResults = new BindableList(); + + public double AverageRank + { + get + { + var ranks = Players.Select(p => p.Statistics?.Ranks.Global) + .Where(i => i.HasValue) + .Select(i => i.Value) + .ToArray(); + + if (ranks.Length == 0) + return 0; + + return ranks.Average(); + } + } + + public Bindable Seed = new Bindable(string.Empty); + + public Bindable LastYearPlacing = new BindableInt + { + MinValue = 1, + MaxValue = 64 + }; + [JsonProperty] public BindableList Players { get; set; } = new BindableList(); diff --git a/osu.Game.Tournament/Screens/Editors/SeedingEditor.cs b/osu.Game.Tournament/Screens/Editors/SeedingEditor.cs new file mode 100644 index 0000000000..68003a1c43 --- /dev/null +++ b/osu.Game.Tournament/Screens/Editors/SeedingEditor.cs @@ -0,0 +1,288 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Overlays.Settings; +using osu.Game.Rulesets; +using osu.Game.Tournament.Components; +using osu.Game.Tournament.Models; +using osuTK; + +namespace osu.Game.Tournament.Screens.Editors +{ + public class SeedingEditorScreen : TournamentEditorScreen + { + private readonly TournamentTeam team; + + protected override BindableList Storage => team.SeedingResults; + + public SeedingEditorScreen(TournamentTeam team) + { + this.team = team; + } + + public class SeeingResultRow : CompositeDrawable, IModelBacked + { + public SeedingResult Model { get; } + + [Resolved] + private LadderInfo ladderInfo { get; set; } + + public SeeingResultRow(TournamentTeam team, SeedingResult round) + { + Model = round; + + Masking = true; + CornerRadius = 10; + + SeedingBeatmapEditor beatmapEditor = new SeedingBeatmapEditor(round) + { + Width = 0.95f + }; + + InternalChildren = new Drawable[] + { + new Box + { + Colour = OsuColour.Gray(0.1f), + RelativeSizeAxes = Axes.Both, + }, + new FillFlowContainer + { + Margin = new MarginPadding(5), + Padding = new MarginPadding { Right = 160 }, + Spacing = new Vector2(5), + Direction = FillDirection.Full, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new SettingsTextBox + { + LabelText = "Mod", + Width = 0.33f, + Bindable = Model.Mod + }, + new SettingsSlider + { + LabelText = "Seed", + Width = 0.33f, + Bindable = Model.Seed + }, + new SettingsButton + { + Width = 0.2f, + Margin = new MarginPadding(10), + Text = "Add beatmap", + Action = () => beatmapEditor.CreateNew() + }, + beatmapEditor + } + }, + new DangerousSettingsButton + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.None, + Width = 150, + Text = "Delete SeeingResult", + Action = () => + { + Expire(); + team.SeedingResults.Remove(Model); + }, + } + }; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + } + + public class SeedingBeatmapEditor : CompositeDrawable + { + private readonly SeedingResult round; + private readonly FillFlowContainer flow; + + public SeedingBeatmapEditor(SeedingResult round) + { + this.round = round; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + InternalChild = flow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + LayoutDuration = 200, + LayoutEasing = Easing.OutQuint, + ChildrenEnumerable = round.Beatmaps.Select(p => new SeedingBeatmapRow(round, p)) + }; + } + + public void CreateNew() + { + var user = new SeedingBeatmap(); + round.Beatmaps.Add(user); + flow.Add(new SeedingBeatmapRow(round, user)); + } + + public class SeedingBeatmapRow : CompositeDrawable + { + private readonly SeedingResult result; + public SeedingBeatmap Model { get; } + + [Resolved] + protected IAPIProvider API { get; private set; } + + private readonly Bindable beatmapId = new Bindable(); + + private readonly Bindable score = new Bindable(); + + private readonly Container drawableContainer; + + public SeedingBeatmapRow(SeedingResult result, SeedingBeatmap beatmap) + { + this.result = result; + Model = beatmap; + + Margin = new MarginPadding(10); + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Masking = true; + CornerRadius = 5; + + InternalChildren = new Drawable[] + { + new Box + { + Colour = OsuColour.Gray(0.2f), + RelativeSizeAxes = Axes.Both, + }, + new FillFlowContainer + { + Margin = new MarginPadding(5), + Padding = new MarginPadding { Right = 160 }, + Spacing = new Vector2(5), + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new SettingsNumberBox + { + LabelText = "Beatmap ID", + RelativeSizeAxes = Axes.None, + Width = 200, + Bindable = beatmapId, + }, + new SettingsSlider + { + LabelText = "Seed", + RelativeSizeAxes = Axes.None, + Width = 200, + Bindable = beatmap.Seed + }, + new SettingsTextBox + { + LabelText = "Score", + RelativeSizeAxes = Axes.None, + Width = 200, + Bindable = score, + }, + drawableContainer = new Container + { + Size = new Vector2(100, 70), + }, + } + }, + new DangerousSettingsButton + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.None, + Width = 150, + Text = "Delete Beatmap", + Action = () => + { + Expire(); + result.Beatmaps.Remove(beatmap); + }, + } + }; + } + + [BackgroundDependencyLoader] + private void load(RulesetStore rulesets) + { + beatmapId.Value = Model.ID.ToString(); + beatmapId.BindValueChanged(idString => + { + int parsed; + + int.TryParse(idString.NewValue, out parsed); + + Model.ID = parsed; + + if (idString.NewValue != idString.OldValue) + Model.BeatmapInfo = null; + + if (Model.BeatmapInfo != null) + { + updatePanel(); + return; + } + + var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = Model.ID }); + + req.Success += res => + { + Model.BeatmapInfo = res.ToBeatmap(rulesets); + updatePanel(); + }; + + req.Failure += _ => + { + Model.BeatmapInfo = null; + updatePanel(); + }; + + API.Queue(req); + }, true); + + score.Value = Model.Score.ToString(); + score.BindValueChanged(str => long.TryParse(str.NewValue, out Model.Score)); + } + + private void updatePanel() + { + drawableContainer.Clear(); + + if (Model.BeatmapInfo != null) + { + drawableContainer.Child = new TournamentBeatmapPanel(Model.BeatmapInfo, result.Mod.Value) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Width = 300 + }; + } + } + } + } + } + + protected override SeeingResultRow CreateDrawable(SeedingResult model) => new SeeingResultRow(team, model); + } +} diff --git a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs index 494dd73edd..d6fa1a29a8 100644 --- a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs @@ -57,6 +57,9 @@ namespace osu.Game.Tournament.Screens.Editors private readonly Container drawableContainer; + [Resolved(canBeNull: true)] + private TournamentSceneManager sceneManager { get; set; } + [Resolved] private LadderInfo ladderInfo { get; set; } @@ -113,6 +116,18 @@ namespace osu.Game.Tournament.Screens.Editors Width = 0.2f, Bindable = Model.FlagName }, + new SettingsTextBox + { + LabelText = "Seed", + Width = 0.2f, + Bindable = Model.Seed + }, + new SettingsSlider + { + LabelText = "Last Year Placement", + Width = 0.33f, + Bindable = Model.LastYearPlacing + }, new SettingsButton { Width = 0.11f, @@ -131,7 +146,17 @@ namespace osu.Game.Tournament.Screens.Editors ladderInfo.Teams.Remove(Model); }, }, - playerEditor + playerEditor, + new SettingsButton + { + Width = 0.2f, + Margin = new MarginPadding(10), + Text = "Edit seeding results", + Action = () => + { + sceneManager.SetScreen(new SeedingEditorScreen(team)); + } + }, } }, }; diff --git a/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs b/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs index 8ab083ddaf..672b7ecc02 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs @@ -127,49 +127,6 @@ namespace osu.Game.Tournament.Screens.Ladder.Components } } - private class SettingsTeamDropdown : LadderSettingsDropdown - { - public SettingsTeamDropdown(BindableList teams) - { - foreach (var t in teams.Prepend(new TournamentTeam())) - add(t); - - teams.CollectionChanged += (_, args) => - { - switch (args.Action) - { - case NotifyCollectionChangedAction.Add: - args.NewItems.Cast().ForEach(add); - break; - - case NotifyCollectionChangedAction.Remove: - args.OldItems.Cast().ForEach(i => Control.RemoveDropdownItem(i)); - break; - } - }; - } - - private readonly List refBindables = new List(); - - private T boundReference(T obj) - where T : IBindable - { - obj = (T)obj.GetBoundCopy(); - refBindables.Add(obj); - return obj; - } - - private void add(TournamentTeam team) - { - Control.AddDropdownItem(team); - boundReference(team.FullName).BindValueChanged(_ => - { - Control.RemoveDropdownItem(team); - Control.AddDropdownItem(team); - }); - } - } - private class LadderSettingsDropdown : SettingsDropdown { protected override OsuDropdown CreateDropdown() => new DropdownControl(); diff --git a/osu.Game.Tournament/Screens/Ladder/Components/LadderSettingsDropdown.cs b/osu.Game.Tournament/Screens/Ladder/Components/LadderSettingsDropdown.cs new file mode 100644 index 0000000000..347e4d91e0 --- /dev/null +++ b/osu.Game.Tournament/Screens/Ladder/Components/LadderSettingsDropdown.cs @@ -0,0 +1,26 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Settings; + +namespace osu.Game.Tournament.Screens.Ladder.Components +{ + public class LadderSettingsDropdown : SettingsDropdown + { + protected override OsuDropdown CreateDropdown() => new DropdownControl(); + + private new class DropdownControl : SettingsDropdown.DropdownControl + { + protected override DropdownMenu CreateMenu() => new Menu(); + + private new class Menu : OsuDropdownMenu + { + public Menu() + { + MaxHeight = 200; + } + } + } + } +} diff --git a/osu.Game.Tournament/Screens/Ladder/Components/SettingsTeamDropdown.cs b/osu.Game.Tournament/Screens/Ladder/Components/SettingsTeamDropdown.cs new file mode 100644 index 0000000000..a630e51e44 --- /dev/null +++ b/osu.Game.Tournament/Screens/Ladder/Components/SettingsTeamDropdown.cs @@ -0,0 +1,55 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; +using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Game.Tournament.Models; + +namespace osu.Game.Tournament.Screens.Ladder.Components +{ + public class SettingsTeamDropdown : LadderSettingsDropdown + { + public SettingsTeamDropdown(BindableList teams) + { + foreach (var t in teams.Prepend(new TournamentTeam())) + add(t); + + teams.CollectionChanged += (_, args) => + { + switch (args.Action) + { + case NotifyCollectionChangedAction.Add: + args.NewItems.Cast().ForEach(add); + break; + + case NotifyCollectionChangedAction.Remove: + args.OldItems.Cast().ForEach(i => Control.RemoveDropdownItem(i)); + break; + } + }; + } + + private readonly List refBindables = new List(); + + private T boundReference(T obj) + where T : IBindable + { + obj = (T)obj.GetBoundCopy(); + refBindables.Add(obj); + return obj; + } + + private void add(TournamentTeam team) + { + Control.AddDropdownItem(team); + boundReference(team.FullName).BindValueChanged(_ => + { + Control.RemoveDropdownItem(team); + Control.AddDropdownItem(team); + }); + } + } +} diff --git a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs new file mode 100644 index 0000000000..db5363c155 --- /dev/null +++ b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs @@ -0,0 +1,316 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Framework.Platform; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Tournament.Components; +using osu.Game.Tournament.Models; +using osu.Game.Tournament.Screens.Ladder.Components; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tournament.Screens.TeamIntro +{ + public class SeedingScreen : TournamentScreen, IProvideVideo + { + private Container mainContainer; + + private readonly Bindable currentMatch = new Bindable(); + + private readonly Bindable currentTeam = new Bindable(); + + [BackgroundDependencyLoader] + private void load(Storage storage) + { + RelativeSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + new TourneyVideo(storage.GetStream(@"videos/seeding.m4v")) + { + RelativeSizeAxes = Axes.Both, + Loop = true, + }, + mainContainer = new Container + { + RelativeSizeAxes = Axes.Both, + }, + new ControlPanel + { + Children = new Drawable[] + { + new TourneyButton + { + RelativeSizeAxes = Axes.X, + Text = "Show first team", + Action = () => currentTeam.Value = currentMatch.Value.Team1.Value, + }, + new TourneyButton + { + RelativeSizeAxes = Axes.X, + Text = "Show second team", + Action = () => currentTeam.Value = currentMatch.Value.Team2.Value, + }, + new SettingsTeamDropdown(LadderInfo.Teams) + { + LabelText = "Show specific team", + Bindable = currentTeam, + } + } + } + }; + + currentMatch.BindValueChanged(matchChanged); + currentMatch.BindTo(LadderInfo.CurrentMatch); + + currentTeam.BindValueChanged(teamChanged, true); + } + + private void teamChanged(ValueChangedEvent team) + { + if (team.NewValue == null) + { + mainContainer.Clear(); + return; + } + + showTeam(team.NewValue); + } + + private void matchChanged(ValueChangedEvent match) => + currentTeam.Value = currentMatch.Value.Team1.Value; + + private void showTeam(TournamentTeam team) + { + mainContainer.Children = new Drawable[] + { + new LeftInfo(team) { Position = new Vector2(55, 150), }, + new RightInfo(team) { Position = new Vector2(500, 150), }, + }; + } + + private class RightInfo : CompositeDrawable + { + public RightInfo(TournamentTeam team) + { + FillFlowContainer fill; + + Width = 400; + + InternalChildren = new Drawable[] + { + fill = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + }, + }; + + foreach (var seeding in team.SeedingResults) + { + fill.Add(new ModRow(seeding.Mod.Value, seeding.Seed.Value)); + foreach (var beatmap in seeding.Beatmaps) + fill.Add(new BeatmapScoreRow(beatmap)); + } + } + + private class BeatmapScoreRow : CompositeDrawable + { + public BeatmapScoreRow(SeedingBeatmap beatmap) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + InternalChildren = new Drawable[] + { + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5), + Children = new Drawable[] + { + new TournamentSpriteText { Text = beatmap.BeatmapInfo.Metadata.Title, Colour = Color4.Black, }, + new TournamentSpriteText { Text = "by", Colour = Color4.Black, Font = OsuFont.Torus.With(weight: FontWeight.Regular) }, + new TournamentSpriteText { Text = beatmap.BeatmapInfo.Metadata.Artist, Colour = Color4.Black, Font = OsuFont.Torus.With(weight: FontWeight.Regular) }, + } + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(40), + Children = new Drawable[] + { + new TournamentSpriteText { Text = beatmap.Score.ToString("#,0"), Colour = Color4.Black, Width = 80 }, + new TournamentSpriteText { Text = "#" + beatmap.Seed.Value.ToString("#,0"), Colour = Color4.Black, Font = OsuFont.Torus.With(weight: FontWeight.Regular) }, + } + }, + }; + } + } + + private class ModRow : CompositeDrawable + { + private readonly string mods; + private readonly int seeding; + + public ModRow(string mods, int seeding) + { + this.mods = mods; + this.seeding = seeding; + + Padding = new MarginPadding { Vertical = 10 }; + + AutoSizeAxes = Axes.Y; + } + + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + InternalChildren = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5), + Children = new Drawable[] + { + new Sprite + { + Texture = textures.Get($"mods/{mods.ToLower()}"), + Scale = new Vector2(0.5f) + }, + new Container + { + Size = new Vector2(50, 16), + CornerRadius = 10, + Masking = true, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + }, + new TournamentSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = seeding.ToString("#,0"), + }, + } + }, + } + }, + }; + } + } + } + + private class LeftInfo : CompositeDrawable + { + public LeftInfo(TournamentTeam team) + { + FillFlowContainer fill; + + Width = 200; + + if (team == null) return; + + InternalChildren = new Drawable[] + { + fill = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new TeamDisplay(team) { Margin = new MarginPadding { Bottom = 30 } }, + new RowDisplay("Average Rank:", $"#{team.AverageRank:#,0}"), + new RowDisplay("Seed:", team.Seed.Value), + new RowDisplay("Last year's placing:", team.LastYearPlacing.Value > 0 ? $"#{team.LastYearPlacing:#,0}" : "0"), + new Container { Margin = new MarginPadding { Bottom = 30 } }, + } + }, + }; + + foreach (var p in team.Players) + fill.Add(new RowDisplay(p.Username, p.Statistics?.Ranks.Global?.ToString("\\##,0") ?? "-")); + } + + internal class RowDisplay : CompositeDrawable + { + public RowDisplay(string left, string right) + { + AutoSizeAxes = Axes.Y; + RelativeSizeAxes = Axes.X; + + var colour = OsuColour.Gray(0.3f); + + InternalChildren = new Drawable[] + { + new TournamentSpriteText + { + Text = left, + Colour = colour, + Font = OsuFont.Torus.With(size: 22), + }, + new TournamentSpriteText + { + Text = right, + Colour = colour, + Anchor = Anchor.TopRight, + Origin = Anchor.TopLeft, + Font = OsuFont.Torus.With(size: 22, weight: FontWeight.Regular), + }, + }; + } + } + + private class TeamDisplay : DrawableTournamentTeam + { + public TeamDisplay(TournamentTeam team) + : base(team) + { + AutoSizeAxes = Axes.Both; + + Flag.RelativeSizeAxes = Axes.None; + Flag.Size = new Vector2(300, 200); + Flag.Scale = new Vector2(0.3f); + + InternalChild = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5), + Children = new Drawable[] + { + Flag, + new OsuSpriteText + { + Text = team?.FullName.Value ?? "???", + Font = OsuFont.Torus.With(size: 32, weight: FontWeight.SemiBold), + Colour = Color4.Black, + }, + } + }; + } + } + } + } +} diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index dd93215fc1..64a5618d73 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -241,6 +241,24 @@ namespace osu.Game.Tournament } } + foreach (var t in ladder.Teams) + { + foreach (var s in t.SeedingResults) + { + foreach (var b in s.Beatmaps) + { + if (b.BeatmapInfo == null && b.ID > 0) + { + var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = b.ID }); + req.Perform(API); + b.BeatmapInfo = req.Result?.ToBeatmap(RulesetStore); + + addedInfo = true; + } + } + } + } + return addedInfo; } diff --git a/osu.Game.Tournament/TournamentSceneManager.cs b/osu.Game.Tournament/TournamentSceneManager.cs index de3d685c31..38df40772f 100644 --- a/osu.Game.Tournament/TournamentSceneManager.cs +++ b/osu.Game.Tournament/TournamentSceneManager.cs @@ -80,6 +80,7 @@ namespace osu.Game.Tournament new ShowcaseScreen(), new MapPoolScreen(), new TeamIntroScreen(), + new SeedingScreen(), new DrawingsScreen(), new GameplayScreen(), new TeamWinScreen() @@ -121,6 +122,7 @@ namespace osu.Game.Tournament new ScreenButton(typeof(LadderScreen)) { Text = "Bracket", RequestSelection = SetScreen }, new Separator(), new ScreenButton(typeof(TeamIntroScreen)) { Text = "TeamIntro", RequestSelection = SetScreen }, + new ScreenButton(typeof(SeedingScreen)) { Text = "Seeding", RequestSelection = SetScreen }, new Separator(), new ScreenButton(typeof(MapPoolScreen)) { Text = "MapPool", RequestSelection = SetScreen }, new ScreenButton(typeof(GameplayScreen)) { Text = "Gameplay", RequestSelection = SetScreen }, @@ -146,8 +148,20 @@ namespace osu.Game.Tournament private Drawable currentScreen; private ScheduledDelegate scheduledHide; + private Drawable temporaryScreen; + + public void SetScreen(Drawable screen) + { + currentScreen?.Hide(); + currentScreen = null; + + screens.Add(temporaryScreen = screen); + } + public void SetScreen(Type screenType) { + temporaryScreen?.Expire(); + var target = screens.FirstOrDefault(s => s.GetType() == screenType); if (target == null || currentScreen == target) return; diff --git a/osu.Game.Tournament/TournamentSpriteText.cs b/osu.Game.Tournament/TournamentSpriteText.cs new file mode 100644 index 0000000000..e550dfbfae --- /dev/null +++ b/osu.Game.Tournament/TournamentSpriteText.cs @@ -0,0 +1,16 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Tournament +{ + public class TournamentSpriteText : OsuSpriteText + { + public TournamentSpriteText() + { + Font = OsuFont.Torus; + } + } +} From b6edd17242a2f94da3fe03e9c2bf2833b0d6bdec Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Mar 2020 18:59:23 +0900 Subject: [PATCH 34/85] Fix TeamWin test scene --- osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs index 5cb35a506f..1a2faa76c1 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs @@ -21,6 +21,7 @@ namespace osu.Game.Tournament.Tests.Screens match.Team1.Value = Ladder.Teams.FirstOrDefault(t => t.Acronym.Value == "USA"); match.Team2.Value = Ladder.Teams.FirstOrDefault(t => t.Acronym.Value == "JPN"); match.Round.Value = Ladder.Rounds.FirstOrDefault(g => g.Name.Value == "Finals"); + match.Completed.Value = true; ladder.CurrentMatch.Value = match; Add(new TeamWinScreen From 1b355d02d6910b596a547670a1c6ffaaffc0fbd0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Mar 2020 19:10:29 +0900 Subject: [PATCH 35/85] Update video resource paths --- osu.Game.Tournament/Screens/Ladder/LadderScreen.cs | 2 +- osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs | 2 +- osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs | 2 +- osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs | 4 ++-- osu.Game.Tournament/TournamentSceneManager.cs | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs b/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs index 8ea366e1b4..5528c9e9f5 100644 --- a/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs +++ b/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs @@ -42,7 +42,7 @@ namespace osu.Game.Tournament.Screens.Ladder RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - new TourneyVideo(storage.GetStream(@"BG Side Logo - OWC.m4v")) + new TourneyVideo(storage.GetStream(@"videos/ladder.m4v")) { RelativeSizeAxes = Axes.Both, Loop = true, diff --git a/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs b/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs index 4b46264055..140ae54bfb 100644 --- a/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs +++ b/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs @@ -34,7 +34,7 @@ namespace osu.Game.Tournament.Screens.Schedule InternalChildren = new Drawable[] { - new TourneyVideo(storage.GetStream(@"BG Side Logo - OWC.m4v")) + new TourneyVideo(storage.GetStream(@"videos/schedule.m4v")) { RelativeSizeAxes = Axes.Both, Loop = true, diff --git a/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs index 75d63cde13..bff5c6aac3 100644 --- a/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs +++ b/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs @@ -29,7 +29,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro InternalChildren = new Drawable[] { - new TourneyVideo(storage.GetStream(@"BG Team - Both OWC.m4v")) + new TourneyVideo(storage.GetStream(@"videos/teamintro.m4v")) { RelativeSizeAxes = Axes.Both, Loop = true, diff --git a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs index b72cacc66f..cc1f2a73ae 100644 --- a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs +++ b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs @@ -33,13 +33,13 @@ namespace osu.Game.Tournament.Screens.TeamWin InternalChildren = new Drawable[] { - blueWinVideo = new TourneyVideo(storage.GetStream(@"BG Team - Win Blue.m4v")) + blueWinVideo = new TourneyVideo(storage.GetStream(@"videos/teamwin-blue.m4v")) { Alpha = 1, RelativeSizeAxes = Axes.Both, Loop = true, }, - redWinVideo = new TourneyVideo(storage.GetStream(@"BG Team - Win Red.m4v")) + redWinVideo = new TourneyVideo(storage.GetStream(@"videos/teamwin-red.m4v")) { Alpha = 0, RelativeSizeAxes = Axes.Both, diff --git a/osu.Game.Tournament/TournamentSceneManager.cs b/osu.Game.Tournament/TournamentSceneManager.cs index 38df40772f..9f5f2b6827 100644 --- a/osu.Game.Tournament/TournamentSceneManager.cs +++ b/osu.Game.Tournament/TournamentSceneManager.cs @@ -61,7 +61,7 @@ namespace osu.Game.Tournament //Masking = true, Children = new Drawable[] { - video = new TourneyVideo(storage.GetStream("BG Logoless - OWC.m4v")) + video = new TourneyVideo(storage.GetStream("videos/main.m4v")) { Loop = true, RelativeSizeAxes = Axes.Both, From e678a068b6ecca9df29c408c7e4858c75f39e477 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Mar 2020 19:14:44 +0900 Subject: [PATCH 36/85] Bring design up-to-date with 2019 standards --- .../Components/ControlPanel.cs | 3 +- .../Components/DrawableTournamentTeam.cs | 7 +- osu.Game.Tournament/Components/SongBar.cs | 7 +- .../Components/TournamentBeatmapPanel.cs | 21 +-- .../Screens/Drawings/Components/Group.cs | 7 +- .../Screens/Drawings/DrawingsScreen.cs | 9 +- .../Gameplay/Components/MatchHeader.cs | 48 ++--- .../Ladder/Components/DrawableMatchTeam.cs | 11 +- .../Components/DrawableTournamentRound.cs | 11 +- .../Screens/MapPool/MapPoolScreen.cs | 3 +- .../Screens/Schedule/ScheduleScreen.cs | 15 +- osu.Game.Tournament/Screens/SetupScreen.cs | 5 +- .../Screens/Showcase/ShowcaseScreen.cs | 2 +- .../Screens/Showcase/TournamentLogo.cs | 16 +- .../Screens/TeamIntro/TeamIntroScreen.cs | 76 +++----- .../Screens/TeamWin/TeamWinScreen.cs | 176 +++++++----------- osu.Game.Tournament/TournamentGame.cs | 4 + osu.Game.Tournament/TournamentGameBase.cs | 5 +- 18 files changed, 168 insertions(+), 258 deletions(-) diff --git a/osu.Game.Tournament/Components/ControlPanel.cs b/osu.Game.Tournament/Components/ControlPanel.cs index a9bb1bf42f..fa5c941f1a 100644 --- a/osu.Game.Tournament/Components/ControlPanel.cs +++ b/osu.Game.Tournament/Components/ControlPanel.cs @@ -5,7 +5,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osuTK; using osuTK.Graphics; @@ -35,7 +34,7 @@ namespace osu.Game.Tournament.Components RelativeSizeAxes = Axes.Both, Colour = new Color4(54, 54, 54, 255) }, - new OsuSpriteText + new TournamentSpriteText { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, diff --git a/osu.Game.Tournament/Components/DrawableTournamentTeam.cs b/osu.Game.Tournament/Components/DrawableTournamentTeam.cs index 361bd92770..99116d4a17 100644 --- a/osu.Game.Tournament/Components/DrawableTournamentTeam.cs +++ b/osu.Game.Tournament/Components/DrawableTournamentTeam.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Tournament.Models; namespace osu.Game.Tournament.Components @@ -19,7 +18,7 @@ namespace osu.Game.Tournament.Components public readonly TournamentTeam Team; protected readonly Sprite Flag; - protected readonly OsuSpriteText AcronymText; + protected readonly TournamentSpriteText AcronymText; [UsedImplicitly] private Bindable acronym; @@ -37,9 +36,9 @@ namespace osu.Game.Tournament.Components FillMode = FillMode.Fit }; - AcronymText = new OsuSpriteText + AcronymText = new TournamentSpriteText { - Font = OsuFont.GetFont(weight: FontWeight.Regular), + Font = OsuFont.Torus.With(weight: FontWeight.Regular), }; } diff --git a/osu.Game.Tournament/Components/SongBar.cs b/osu.Game.Tournament/Components/SongBar.cs index 8a46da9565..48ea36a8f3 100644 --- a/osu.Game.Tournament/Components/SongBar.cs +++ b/osu.Game.Tournament/Components/SongBar.cs @@ -13,7 +13,6 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Legacy; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Rulesets; using osu.Game.Screens.Menu; using osuTK; @@ -262,7 +261,7 @@ namespace osu.Game.Tournament.Components static void cp(SpriteText s, Color4 colour) { s.Colour = colour; - s.Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 15); + s.Font = OsuFont.Torus.With(weight: FontWeight.Bold, size: 15); } for (var i = 0; i < tuples.Length; i++) @@ -278,9 +277,9 @@ namespace osu.Game.Tournament.Components }); } - AddText(new OsuSpriteText { Text = heading }, s => cp(s, OsuColour.Gray(0.33f))); + AddText(new TournamentSpriteText { Text = heading }, s => cp(s, OsuColour.Gray(0.33f))); AddText(" ", s => cp(s, OsuColour.Gray(0.33f))); - AddText(new OsuSpriteText { Text = content }, s => cp(s, OsuColour.Gray(0.5f))); + AddText(new TournamentSpriteText { Text = content }, s => cp(s, OsuColour.Gray(0.5f))); } } } diff --git a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs index 51483a0964..394ffe304e 100644 --- a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs +++ b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs @@ -15,7 +15,6 @@ using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Tournament.Models; using osuTK; using osuTK.Graphics; @@ -77,14 +76,14 @@ namespace osu.Game.Tournament.Components Direction = FillDirection.Vertical, Children = new Drawable[] { - new OsuSpriteText + new TournamentSpriteText { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Text = new LocalisedString(( $"{Beatmap.Metadata.ArtistUnicode ?? Beatmap.Metadata.Artist} - {Beatmap.Metadata.TitleUnicode ?? Beatmap.Metadata.Title}", $"{Beatmap.Metadata.Artist} - {Beatmap.Metadata.Title}")), - Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true), + Font = OsuFont.Torus.With(weight: FontWeight.Bold), }, new FillFlowContainer { @@ -95,28 +94,28 @@ namespace osu.Game.Tournament.Components Direction = FillDirection.Horizontal, Children = new Drawable[] { - new OsuSpriteText + new TournamentSpriteText { Text = "mapper", Padding = new MarginPadding { Right = 5 }, - Font = OsuFont.GetFont(italics: true, weight: FontWeight.Regular, size: 14) + Font = OsuFont.Torus.With(weight: FontWeight.Regular, size: 14) }, - new OsuSpriteText + new TournamentSpriteText { Text = Beatmap.Metadata.AuthorString, Padding = new MarginPadding { Right = 20 }, - Font = OsuFont.GetFont(italics: true, weight: FontWeight.Bold, size: 14) + Font = OsuFont.Torus.With(weight: FontWeight.Bold, size: 14) }, - new OsuSpriteText + new TournamentSpriteText { Text = "difficulty", Padding = new MarginPadding { Right = 5 }, - Font = OsuFont.GetFont(italics: true, weight: FontWeight.Regular, size: 14) + Font = OsuFont.Torus.With(weight: FontWeight.Regular, size: 14) }, - new OsuSpriteText + new TournamentSpriteText { Text = Beatmap.Version, - Font = OsuFont.GetFont(italics: true, weight: FontWeight.Bold, size: 14) + Font = OsuFont.Torus.With(weight: FontWeight.Bold, size: 14) }, } } diff --git a/osu.Game.Tournament/Screens/Drawings/Components/Group.cs b/osu.Game.Tournament/Screens/Drawings/Components/Group.cs index 549ff26018..4126f2db65 100644 --- a/osu.Game.Tournament/Screens/Drawings/Components/Group.cs +++ b/osu.Game.Tournament/Screens/Drawings/Components/Group.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Tournament.Components; using osu.Game.Tournament.Models; using osuTK; @@ -43,7 +42,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components Colour = new Color4(54, 54, 54, 255) }, // Group name - new OsuSpriteText + new TournamentSpriteText { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, @@ -51,7 +50,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components Position = new Vector2(0, 7f), Text = $"GROUP {name.ToUpperInvariant()}", - Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 8), + Font = OsuFont.Torus.With(weight: FontWeight.Bold, size: 8), Colour = new Color4(255, 204, 34, 255), }, teams = new FillFlowContainer @@ -134,7 +133,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components AcronymText.Anchor = Anchor.TopCentre; AcronymText.Origin = Anchor.TopCentre; AcronymText.Text = team.Acronym.Value.ToUpperInvariant(); - AcronymText.Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 10); + AcronymText.Font = OsuFont.Torus.With(weight: FontWeight.Bold, size: 10); InternalChildren = new Drawable[] { diff --git a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs index 5efa0a1e69..8be66ff98c 100644 --- a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs +++ b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs @@ -14,7 +14,6 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Tournament.Components; using osu.Game.Tournament.Models; using osu.Game.Tournament.Screens.Drawings.Components; @@ -29,7 +28,7 @@ namespace osu.Game.Tournament.Screens.Drawings private ScrollingTeamContainer teamsContainer; private GroupContainer groupsContainer; - private OsuSpriteText fullTeamNameText; + private TournamentSpriteText fullTeamNameText; private readonly List allTeams = new List(); @@ -109,18 +108,18 @@ namespace osu.Game.Tournament.Screens.Drawings RelativeSizeAxes = Axes.X, }, // Scrolling team name - fullTeamNameText = new OsuSpriteText + fullTeamNameText = new TournamentSpriteText { Anchor = Anchor.Centre, Origin = Anchor.TopCentre, Position = new Vector2(0, 45f), - Colour = OsuColour.Gray(0.33f), + Colour = OsuColour.Gray(0.95f), Alpha = 0, - Font = OsuFont.GetFont(weight: FontWeight.Light, size: 42), + Font = OsuFont.Torus.With(weight: FontWeight.Light, size: 42), } } }, diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/MatchHeader.cs b/osu.Game.Tournament/Screens/Gameplay/Components/MatchHeader.cs index d8f2df2e93..ce17c392d0 100644 --- a/osu.Game.Tournament/Screens/Gameplay/Components/MatchHeader.cs +++ b/osu.Game.Tournament/Screens/Gameplay/Components/MatchHeader.cs @@ -5,9 +5,9 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Tournament.Components; using osu.Game.Tournament.Models; @@ -30,7 +30,7 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components new TournamentLogo(), new RoundDisplay { - Y = 10, + Y = 5, Anchor = Anchor.BottomCentre, Origin = Anchor.TopCentre, }, @@ -51,9 +51,6 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components { private readonly TeamColour teamColour; - private readonly Color4 red = new Color4(129, 68, 65, 255); - private readonly Color4 blue = new Color4(41, 91, 97, 255); - private readonly Bindable currentMatch = new Bindable(); private readonly Bindable currentTeam = new Bindable(); private readonly Bindable currentTeamScore = new Bindable(); @@ -106,7 +103,7 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components private void teamChanged(TournamentTeam team) { - var colour = teamColour == TeamColour.Red ? red : blue; + var colour = teamColour == TeamColour.Red ? TournamentGame.COLOUR_RED : TournamentGame.COLOUR_BLUE; var flip = teamColour != TeamColour.Red; InternalChildren = new Drawable[] @@ -169,7 +166,7 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components Children = new Drawable[] { Flag, - new OsuSpriteText + new TournamentSpriteText { Text = team?.FullName.Value.ToUpper() ?? "???", X = (flip ? -1 : 1) * 90, @@ -188,10 +185,31 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components { private readonly Bindable currentMatch = new Bindable(); + private readonly TournamentSpriteText text; + public RoundDisplay() { Width = 200; Height = 20; + + Masking = true; + CornerRadius = 10; + + InternalChildren = new Drawable[] + { + new Box + { + Colour = OsuColour.Gray(0.18f), + RelativeSizeAxes = Axes.Both, + }, + text = new TournamentSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = Color4.White, + Font = OsuFont.Torus.With(weight: FontWeight.Regular, size: 16), + }, + }; } [BackgroundDependencyLoader] @@ -201,20 +219,8 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components currentMatch.BindTo(ladder.CurrentMatch); } - private void matchChanged(ValueChangedEvent match) - { - InternalChildren = new Drawable[] - { - new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Colour = Color4.White, - Text = match.NewValue.Round.Value?.Name.Value ?? "Unknown Round", - Font = OsuFont.Torus.With(weight: FontWeight.Regular, size: 18), - }, - }; - } + private void matchChanged(ValueChangedEvent match) => + text.Text = match.NewValue.Round.Value?.Name.Value ?? "Unknown Round"; } } } diff --git a/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs b/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs index 031d6bf3d2..88d7b95b0c 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics.Shapes; 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.Tournament.Components; using osu.Game.Tournament.Models; @@ -26,7 +25,7 @@ namespace osu.Game.Tournament.Screens.Ladder.Components { private readonly TournamentMatch match; private readonly bool losers; - private OsuSpriteText scoreText; + private TournamentSpriteText scoreText; private Box background; private readonly Bindable score = new Bindable(); @@ -69,7 +68,7 @@ namespace osu.Game.Tournament.Screens.Ladder.Components AcronymText.Anchor = AcronymText.Origin = Anchor.CentreLeft; AcronymText.Padding = new MarginPadding { Left = 50 }; - AcronymText.Font = OsuFont.GetFont(size: 24); + AcronymText.Font = OsuFont.Torus.With(size: 24); if (match != null) { @@ -119,11 +118,11 @@ namespace osu.Game.Tournament.Screens.Ladder.Components Alpha = 0.8f, RelativeSizeAxes = Axes.Both, }, - scoreText = new OsuSpriteText + scoreText = new TournamentSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Font = OsuFont.GetFont(size: 20), + Font = OsuFont.Torus.With(size: 20), } } } @@ -184,7 +183,7 @@ namespace osu.Game.Tournament.Screens.Ladder.Components background.FadeColour(winner ? colourWinner : colourNormal, winner ? 500 : 0, Easing.OutQuint); - scoreText.Font = AcronymText.Font = OsuFont.GetFont(weight: winner ? FontWeight.Bold : FontWeight.Regular); + scoreText.Font = AcronymText.Font = OsuFont.Torus.With(weight: winner ? FontWeight.Bold : FontWeight.Regular); } public MenuItem[] ContextMenuItems diff --git a/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentRound.cs b/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentRound.cs index dacd98d3b8..d14ebb4d03 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentRound.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentRound.cs @@ -6,7 +6,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Tournament.Models; using osuTK.Graphics; @@ -22,8 +21,8 @@ namespace osu.Game.Tournament.Screens.Ladder.Components public DrawableTournamentRound(TournamentRound round, bool losers = false) { - OsuSpriteText textName; - OsuSpriteText textDescription; + TournamentSpriteText textName; + TournamentSpriteText textDescription; AutoSizeAxes = Axes.Both; InternalChild = new FillFlowContainer @@ -32,15 +31,15 @@ namespace osu.Game.Tournament.Screens.Ladder.Components AutoSizeAxes = Axes.Both, Children = new Drawable[] { - textDescription = new OsuSpriteText + textDescription = new TournamentSpriteText { Colour = Color4.Black, Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre }, - textName = new OsuSpriteText + textName = new TournamentSpriteText { - Font = OsuFont.GetFont(weight: FontWeight.Bold), + Font = OsuFont.Torus.With(weight: FontWeight.Bold), Colour = Color4.Black, Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre diff --git a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs index c3875716b8..c42d0a6da3 100644 --- a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs +++ b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Framework.Threading; using osu.Game.Beatmaps; -using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Tournament.Components; using osu.Game.Tournament.IPC; @@ -56,7 +55,7 @@ namespace osu.Game.Tournament.Screens.MapPool { Children = new Drawable[] { - new OsuSpriteText + new TournamentSpriteText { Text = "Current Mode" }, diff --git a/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs b/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs index 140ae54bfb..080570eac4 100644 --- a/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs +++ b/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Platform; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Tournament.Components; using osu.Game.Tournament.Models; using osu.Game.Tournament.Screens.Ladder.Components; @@ -107,20 +106,20 @@ namespace osu.Game.Tournament.Screens.Schedule Height = 0.25f, Children = new Drawable[] { - new OsuSpriteText + new TournamentSpriteText { Margin = new MarginPadding { Left = -10, Bottom = 10, Top = -5 }, Spacing = new Vector2(10, 0), Text = match.NewValue.Round.Value?.Name.Value, Colour = Color4.Black, - Font = OsuFont.GetFont(size: 20) + Font = OsuFont.Torus.With(size: 20) }, new ScheduleMatch(match.NewValue, false), - new OsuSpriteText + new TournamentSpriteText { Text = "Start Time " + match.NewValue.Date.Value.ToUniversalTime().ToString("HH:mm UTC"), Colour = Color4.Black, - Font = OsuFont.GetFont(size: 20) + Font = OsuFont.Torus.With(size: 20) }, } } @@ -150,7 +149,7 @@ namespace osu.Game.Tournament.Screens.Schedule Alpha = conditional ? 0.6f : 1, Margin = new MarginPadding { Horizontal = 10, Vertical = 5 }, }); - AddInternal(new OsuSpriteText + AddInternal(new TournamentSpriteText { Anchor = Anchor.BottomRight, Origin = Anchor.BottomLeft, @@ -174,13 +173,13 @@ namespace osu.Game.Tournament.Screens.Schedule Padding = new MarginPadding { Left = 30, Top = 30 }; InternalChildren = new Drawable[] { - new OsuSpriteText + new TournamentSpriteText { X = 30, Text = title, Colour = Color4.Black, Spacing = new Vector2(10, 0), - Font = OsuFont.GetFont(size: 30) + Font = OsuFont.Torus.With(size: 30) }, content = new FillFlowContainer { diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs index 8e1481d87c..023582166c 100644 --- a/osu.Game.Tournament/Screens/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Online.API; @@ -147,7 +146,7 @@ namespace osu.Game.Tournament.Screens public Action Action; - private OsuSpriteText valueText; + private TournamentSpriteText valueText; protected override Drawable CreateComponent() => new Container { @@ -155,7 +154,7 @@ namespace osu.Game.Tournament.Screens RelativeSizeAxes = Axes.X, Children = new Drawable[] { - valueText = new OsuSpriteText + valueText = new TournamentSpriteText { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, diff --git a/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs b/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs index 20928499bf..d809dfc994 100644 --- a/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs +++ b/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs @@ -10,7 +10,7 @@ namespace osu.Game.Tournament.Screens.Showcase [BackgroundDependencyLoader] private void load() { - AddInternal(new TournamentLogo(false)); + AddInternal(new TournamentLogo()); } } } diff --git a/osu.Game.Tournament/Screens/Showcase/TournamentLogo.cs b/osu.Game.Tournament/Screens/Showcase/TournamentLogo.cs index 1fee2b29e8..6ad5ccaf0c 100644 --- a/osu.Game.Tournament/Screens/Showcase/TournamentLogo.cs +++ b/osu.Game.Tournament/Screens/Showcase/TournamentLogo.cs @@ -11,20 +11,12 @@ namespace osu.Game.Tournament.Screens.Showcase { public class TournamentLogo : CompositeDrawable { - public TournamentLogo(bool includeRoundBackground = true) + public TournamentLogo() { RelativeSizeAxes = Axes.X; Margin = new MarginPadding { Vertical = 5 }; - if (includeRoundBackground) - { - AutoSizeAxes = Axes.Y; - } - else - { - Masking = true; - Height = 100; - } + Height = 100; } [BackgroundDependencyLoader] @@ -32,9 +24,11 @@ namespace osu.Game.Tournament.Screens.Showcase { InternalChild = new Sprite { - Texture = textures.Get("game-screen-logo"), Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, + FillMode = FillMode.Fit, + RelativeSizeAxes = Axes.Both, + Texture = textures.Get("game-screen-logo"), }; } } diff --git a/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs index bff5c6aac3..6559113f55 100644 --- a/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs +++ b/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs @@ -7,12 +7,9 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Platform; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Tournament.Components; using osu.Game.Tournament.Models; -using osu.Game.Tournament.Screens.Showcase; using osuTK; -using osuTK.Graphics; namespace osu.Game.Tournament.Screens.TeamIntro { @@ -34,7 +31,6 @@ namespace osu.Game.Tournament.Screens.TeamIntro RelativeSizeAxes = Axes.Both, Loop = true, }, - new TournamentLogo(false), mainContainer = new Container { RelativeSizeAxes = Axes.Both, @@ -75,8 +71,9 @@ namespace osu.Game.Tournament.Screens.TeamIntro { RelativeSizeAxes = Axes.Both, Height = 0.25f, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Y = 180, } }; } @@ -85,8 +82,6 @@ namespace osu.Game.Tournament.Screens.TeamIntro { public RoundDisplay(TournamentMatch match) { - var col = OsuColour.Gray(0.33f); - InternalChildren = new Drawable[] { new FillFlowContainer @@ -98,31 +93,13 @@ namespace osu.Game.Tournament.Screens.TeamIntro Spacing = new Vector2(0, 10), Children = new Drawable[] { - new OsuSpriteText + new TournamentSpriteText { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Colour = col, - Text = "COMING UP NEXT", - Spacing = new Vector2(2, 0), - Font = OsuFont.GetFont(size: 15, weight: FontWeight.Black) - }, - new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Colour = col, + Colour = OsuColour.Gray(0.33f), Text = match.Round.Value?.Name.Value ?? "Unknown Round", - Spacing = new Vector2(10, 0), - Font = OsuFont.GetFont(size: 50, weight: FontWeight.Light) - }, - new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Colour = col, - Text = match.Date.Value.ToUniversalTime().ToString("dd MMMM HH:mm UTC"), - Font = OsuFont.GetFont(size: 20) + Font = OsuFont.Torus.With(size: 26, weight: FontWeight.Light) }, } } @@ -132,21 +109,19 @@ namespace osu.Game.Tournament.Screens.TeamIntro private class TeamWithPlayers : CompositeDrawable { - private readonly Color4 red = new Color4(129, 68, 65, 255); - private readonly Color4 blue = new Color4(41, 91, 97, 255); - public TeamWithPlayers(TournamentTeam team, bool left = false) { FillFlowContainer players; - var colour = left ? red : blue; + var colour = left ? TournamentGame.COLOUR_RED : TournamentGame.COLOUR_BLUE; InternalChildren = new Drawable[] { - new TeamDisplay(team, left ? "Team Red" : "Team Blue", colour) + new TeamDisplay(team) { Anchor = left ? Anchor.CentreRight : Anchor.CentreLeft, - Origin = Anchor.Centre, + Origin = Anchor.TopCentre, RelativePositionAxes = Axes.Both, - X = (left ? -1 : 1) * 0.36f, + X = (left ? -1 : 1) * 0.3145f, + Y = -0.077f, }, players = new FillFlowContainer { @@ -157,7 +132,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro Anchor = left ? Anchor.CentreRight : Anchor.CentreLeft, Origin = left ? Anchor.CentreRight : Anchor.CentreLeft, RelativePositionAxes = Axes.Both, - X = (left ? -1 : 1) * 0.66f, + X = (left ? -1 : 1) * 0.58f, }, }; @@ -165,10 +140,10 @@ namespace osu.Game.Tournament.Screens.TeamIntro { foreach (var p in team.Players) { - players.Add(new OsuSpriteText + players.Add(new TournamentSpriteText { Text = p.Username, - Font = OsuFont.GetFont(size: 24), + Font = OsuFont.Torus.With(size: 24), Colour = colour, Anchor = left ? Anchor.CentreRight : Anchor.CentreLeft, Origin = left ? Anchor.CentreRight : Anchor.CentreLeft, @@ -179,7 +154,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro private class TeamDisplay : DrawableTournamentTeam { - public TeamDisplay(TournamentTeam team, string teamName, Color4 colour) + public TeamDisplay(TournamentTeam team) : base(team) { AutoSizeAxes = Axes.Both; @@ -187,33 +162,24 @@ namespace osu.Game.Tournament.Screens.TeamIntro Flag.Anchor = Flag.Origin = Anchor.TopCentre; Flag.RelativeSizeAxes = Axes.None; Flag.Size = new Vector2(300, 200); - Flag.Scale = new Vector2(0.4f); - Flag.Margin = new MarginPadding { Bottom = 20 }; + Flag.Scale = new Vector2(0.32f); InternalChild = new FillFlowContainer { AutoSizeAxes = Axes.Both, Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 5), + Spacing = new Vector2(160), Children = new Drawable[] { Flag, - new OsuSpriteText + new TournamentSpriteText { - Text = team?.FullName.Value.ToUpper() ?? "???", - Font = OsuFont.Torus.With(size: 40, weight: FontWeight.Light), - Colour = Color4.Black, + Text = team?.FullName.Value ?? "???", + Font = OsuFont.Torus.With(size: 20, weight: FontWeight.Regular), + Colour = OsuColour.Gray(0.2f), Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, }, - new OsuSpriteText - { - Text = teamName.ToUpper(), - Font = OsuFont.Torus.With(size: 20, weight: FontWeight.Regular), - Colour = colour, - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - } } }; } diff --git a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs index cc1f2a73ae..30b86f8421 100644 --- a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs +++ b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs @@ -7,10 +7,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Platform; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Tournament.Components; using osu.Game.Tournament.Models; -using osu.Game.Tournament.Screens.Showcase; using osuTK; using osuTK.Graphics; @@ -45,10 +43,6 @@ namespace osu.Game.Tournament.Screens.TeamWin RelativeSizeAxes = Axes.Both, Loop = true, }, - new TournamentLogo(false) - { - Y = 40, - }, mainContainer = new Container { RelativeSizeAxes = Axes.Both, @@ -85,141 +79,99 @@ namespace osu.Game.Tournament.Screens.TeamWin mainContainer.Children = new Drawable[] { + new TeamFlagDisplay(match.Winner) + { + Size = new Vector2(300, 200), + Scale = new Vector2(0.5f), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + X = -387, + }, + new TournamentSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.TopLeft, + Position = new Vector2(78, -70), + Colour = OsuColour.Gray(0.33f), + Text = match.Round.Value?.Name.Value ?? "Unknown Round", + Font = OsuFont.Torus.With(size: 30, weight: FontWeight.Regular) + }, new TeamWithPlayers(match.Winner, redWin) { RelativeSizeAxes = Axes.Both, Width = 0.5f, Height = 0.6f, Anchor = Anchor.Centre, - Origin = Anchor.Centre + Origin = Anchor.TopLeft, + Position = new Vector2(78, 0), }, - new RoundDisplay(match) - { - RelativeSizeAxes = Axes.Both, - Height = 0.25f, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - } }; } - private class RoundDisplay : CompositeDrawable - { - public RoundDisplay(TournamentMatch match) - { - var col = OsuColour.Gray(0.33f); - - InternalChildren = new Drawable[] - { - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 10), - Children = new Drawable[] - { - new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Colour = col, - Text = "WINNER", - Font = OsuFont.Torus.With(size: 15, weight: FontWeight.Regular), - }, - new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Colour = col, - Text = match.Round.Value?.Name.Value ?? "Unknown Round", - Font = OsuFont.Torus.With(size: 50, weight: FontWeight.Light), - Spacing = new Vector2(10, 0), - }, - new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Colour = col, - Text = match.Date.Value.ToUniversalTime().ToString("dd MMMM HH:mm UTC"), - Font = OsuFont.Torus.With(size: 20, weight: FontWeight.Light), - }, - } - } - }; - } - } - private class TeamWithPlayers : CompositeDrawable { - private readonly Color4 red = new Color4(129, 68, 65, 255); - private readonly Color4 blue = new Color4(41, 91, 97, 255); - public TeamWithPlayers(TournamentTeam team, bool left = false) { - var colour = left ? red : blue; + FillFlowContainer players; + + var colour = left ? TournamentGame.COLOUR_RED : TournamentGame.COLOUR_BLUE; InternalChildren = new Drawable[] { - new TeamDisplay(team, left ? "Team Red" : "Team Blue", colour) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, new FillFlowContainer { Direction = FillDirection.Vertical, AutoSizeAxes = Axes.Both, - Spacing = new Vector2(0, 5), - Padding = new MarginPadding(20), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativePositionAxes = Axes.Both, - }, - }; - } - - private class TeamDisplay : DrawableTournamentTeam - { - public TeamDisplay(TournamentTeam team, string teamName, Color4 colour) - : base(team) - { - AutoSizeAxes = Axes.Both; - - Flag.Anchor = Flag.Origin = Anchor.TopCentre; - Flag.RelativeSizeAxes = Axes.None; - Flag.Size = new Vector2(300, 200); - Flag.Scale = new Vector2(0.4f); - Flag.Margin = new MarginPadding { Bottom = 20 }; - - InternalChild = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 5), Children = new Drawable[] { - Flag, - new OsuSpriteText + new TournamentSpriteText { - Text = team?.FullName.Value.ToUpper() ?? "???", - Font = OsuFont.Torus.With(size: 40, weight: FontWeight.Light), + Text = "WINNER", + Font = OsuFont.Torus.With(size: 24, weight: FontWeight.SemiBold), Colour = Color4.Black, - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, }, - new OsuSpriteText + new TournamentSpriteText { - Text = teamName.ToUpper(), - Font = OsuFont.GetFont(size: 20), - Colour = colour, - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - } + Text = team?.FullName.Value ?? "???", + Font = OsuFont.Torus.With(size: 30, weight: FontWeight.SemiBold), + Colour = Color4.Black, + }, + players = new FillFlowContainer + { + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = 10 }, + }, } - }; + }, + }; + + if (team != null) + { + foreach (var p in team.Players) + { + players.Add(new TournamentSpriteText + { + Text = p.Username, + Font = OsuFont.Torus.With(size: 24), + Colour = colour, + Anchor = left ? Anchor.CentreRight : Anchor.CentreLeft, + Origin = left ? Anchor.CentreRight : Anchor.CentreLeft, + }); + } } } } + + private class TeamFlagDisplay : DrawableTournamentTeam + { + public TeamFlagDisplay(TournamentTeam team) + : base(team) + { + InternalChildren = new Drawable[] + { + Flag + }; + } + } } } diff --git a/osu.Game.Tournament/TournamentGame.cs b/osu.Game.Tournament/TournamentGame.cs index 7dbcf37af6..608fc5f04a 100644 --- a/osu.Game.Tournament/TournamentGame.cs +++ b/osu.Game.Tournament/TournamentGame.cs @@ -3,11 +3,15 @@ using osu.Framework.Graphics; using osu.Game.Graphics.Cursor; +using osuTK.Graphics; namespace osu.Game.Tournament { public class TournamentGame : TournamentGameBase { + public static readonly Color4 COLOUR_RED = new Color4(144, 0, 0, 255); + public static readonly Color4 COLOUR_BLUE = new Color4(0, 84, 144, 255); + protected override void LoadComplete() { base.LoadComplete(); diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 64a5618d73..435f315c8d 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -18,7 +18,6 @@ using osu.Framework.IO.Stores; using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Online.API.Requests; using osu.Game.Tournament.IPC; using osu.Game.Tournament.Models; @@ -101,10 +100,10 @@ namespace osu.Game.Tournament Colour = Color4.Red, RelativeSizeAxes = Axes.Both, }, - new OsuSpriteText + new TournamentSpriteText { Text = "Please make the window wider", - Font = OsuFont.Default.With(weight: "bold"), + Font = OsuFont.Torus.With(weight: FontWeight.Bold), Colour = Color4.White, Padding = new MarginPadding(20) } From db56fb57594ad14d1713c68778d207c21a3892e0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Mar 2020 22:28:47 +0900 Subject: [PATCH 37/85] Fix venera font usage --- osu.Game/Graphics/OsuFont.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/OsuFont.cs b/osu.Game/Graphics/OsuFont.cs index 54a25875bf..113816a14a 100644 --- a/osu.Game/Graphics/OsuFont.cs +++ b/osu.Game/Graphics/OsuFont.cs @@ -17,7 +17,7 @@ namespace osu.Game.Graphics /// public static FontUsage Default => GetFont(); - public static FontUsage Numeric => GetFont(Typeface.Venera, weight: FontWeight.Regular); + public static FontUsage Numeric => GetFont(Typeface.Venera); public static FontUsage Torus => GetFont(Typeface.Torus, weight: FontWeight.Regular); From 5e218697c5a525d8289cfa3d9959b1e0df6ac2d6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 4 Mar 2020 09:46:53 +0900 Subject: [PATCH 38/85] Use stacked positions --- .../TestSceneFollowPoints.cs | 46 +++++++++++++++++++ .../Connections/FollowPointConnection.cs | 4 +- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index 94ca2d4cd1..87da7ef417 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -2,9 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Osu.Objects; @@ -114,6 +117,22 @@ namespace osu.Game.Rulesets.Osu.Tests assertGroups(); } + [Test] + public void TestStackedObjects() + { + addObjectsStep(() => new OsuHitObject[] + { + new HitCircle { Position = new Vector2(300, 100) }, + new HitCircle + { + Position = new Vector2(300, 300), + StackHeight = 20 + }, + }); + + assertDirections(); + } + private void addMultipleObjectsStep() => addObjectsStep(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) }, @@ -207,6 +226,33 @@ namespace osu.Game.Rulesets.Osu.Tests }); } + private void assertDirections() + { + AddAssert("group directions are correct", () => + { + for (int i = 0; i < hitObjectContainer.Count; i++) + { + DrawableOsuHitObject expectedStart = getObject(i); + DrawableOsuHitObject expectedEnd = i < hitObjectContainer.Count - 1 ? getObject(i + 1) : null; + + if (expectedEnd == null) + continue; + + var points = getGroup(i).ChildrenOfType().ToArray(); + if (points.Length == 0) + continue; + + float expectedDirection = MathF.Atan2(expectedStart.Position.Y - expectedEnd.Position.Y, expectedStart.Position.X - expectedEnd.Position.X); + float realDirection = MathF.Atan2(expectedStart.Position.Y - points[^1].Position.Y, expectedStart.Position.X - points[^1].Position.X); + + if (!Precision.AlmostEquals(expectedDirection, realDirection)) + throw new AssertionException($"Expected group {i} in direction {expectedDirection}, but was {realDirection}."); + } + + return true; + }); + } + private DrawableOsuHitObject getObject(int index) => hitObjectContainer[index]; private FollowPointConnection getGroup(int index) => followPointRenderer.Connections[index]; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs index 921b23cb13..3e9c0f341b 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs @@ -104,8 +104,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections return; } - Vector2 startPosition = osuStart.EndPosition; - Vector2 endPosition = osuEnd.Position; + Vector2 startPosition = osuStart.StackedEndPosition; + Vector2 endPosition = osuEnd.StackedPosition; double endTime = osuEnd.StartTime; Vector2 distanceVector = endPosition - startPosition; From c3f840cc1a5071ee92c758315d5c41ee12cd1dec Mon Sep 17 00:00:00 2001 From: voidedWarranties Date: Tue, 3 Mar 2020 17:12:01 -0800 Subject: [PATCH 39/85] Fix Autoplay = false and AllowFail behavior --- .../Mods/TestSceneOsuModDifficultyAdjust.cs | 6 +++--- osu.Game/Tests/Visual/ModSandboxTestScene.cs | 13 ++++++++----- osu.Game/Tests/Visual/PlayerTestScene.cs | 2 ++ 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs index 1fc9ccccd1..20cb9ef05d 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs @@ -42,14 +42,14 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods PassCondition = () => ((ScoreAccessibleTestPlayer)Player).ScoreProcessor.JudgedHits >= 2 }); - protected override TestPlayer CreateReplayPlayer(Score score) => new ScoreAccessibleTestPlayer(score); + protected override TestPlayer CreateReplayPlayer(Score score, bool allowFail) => new ScoreAccessibleTestPlayer(score, allowFail); private class ScoreAccessibleTestPlayer : TestPlayer { public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; - public ScoreAccessibleTestPlayer(Score score) - : base(score) + public ScoreAccessibleTestPlayer(Score score, bool allowFail) + : base(score, allowFail) { } } diff --git a/osu.Game/Tests/Visual/ModSandboxTestScene.cs b/osu.Game/Tests/Visual/ModSandboxTestScene.cs index 11612d0eca..8a9cdf009b 100644 --- a/osu.Game/Tests/Visual/ModSandboxTestScene.cs +++ b/osu.Game/Tests/Visual/ModSandboxTestScene.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; using osu.Game.Beatmaps; -using osu.Game.Replays; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; @@ -56,22 +55,26 @@ namespace osu.Game.Tests.Visual var score = currentTest.Autoplay ? ruleset.GetAutoplayMod().CreateReplayScore(Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo, SelectedMods.Value)) - : new Score { Replay = new Replay() }; + : null; - return CreateReplayPlayer(score); + return CreateReplayPlayer(score, AllowFail); } /// /// Creates the for a test case. /// /// The . - protected virtual TestPlayer CreateReplayPlayer(Score score) => new TestPlayer(score); + /// Whether the player can fail. + protected virtual TestPlayer CreateReplayPlayer(Score score, bool allowFail) => new TestPlayer(score, allowFail); protected class TestPlayer : TestReplayPlayer { - public TestPlayer(Score score) + protected override bool AllowFail { get; } + + public TestPlayer(Score score, bool allowFail) : base(score, false, false) { + AllowFail = allowFail; } } diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index 0d5aac8cfd..17ad6e80df 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -68,6 +68,8 @@ namespace osu.Game.Tests.Visual Beatmap.Value = CreateWorkingBeatmap(beatmap); + SelectedMods.Value = Array.Empty(); + if (!AllowFail) { var noFailMod = ruleset.GetAllMods().FirstOrDefault(m => m is ModNoFail); From 4294ed4b6495e40bcb9c2eb9664f9e369cd949d7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Mar 2020 11:21:37 +0900 Subject: [PATCH 40/85] Better align fonts to weights --- osu.Game/Graphics/OsuFont.cs | 37 ++++++++++++++++---- osu.Game/Online/Leaderboards/DrawableRank.cs | 2 +- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/osu.Game/Graphics/OsuFont.cs b/osu.Game/Graphics/OsuFont.cs index 113816a14a..4dd7bcdf31 100644 --- a/osu.Game/Graphics/OsuFont.cs +++ b/osu.Game/Graphics/OsuFont.cs @@ -17,7 +17,7 @@ namespace osu.Game.Graphics /// public static FontUsage Default => GetFont(); - public static FontUsage Numeric => GetFont(Typeface.Venera); + public static FontUsage Numeric => GetFont(Typeface.Venera, weight: FontWeight.Bold); public static FontUsage Torus => GetFont(Typeface.Torus, weight: FontWeight.Regular); @@ -103,11 +103,34 @@ namespace osu.Game.Graphics public enum FontWeight { - Light, - Regular, - Medium, - SemiBold, - Bold, - Black + /// + /// equivalent to weight 300 + /// + Light = 300, + + /// + /// equivalent to weight 400 + /// + Regular = 400, + + /// + /// equivalent to weight 500 + /// + Medium = 500, + + /// + /// equivalent to weight 600 + /// + SemiBold = 600, + + /// + /// equivalent to weight 700 + /// + Bold = 700, + + /// + /// equivalent to weight 900 + /// + Black = 900 } } diff --git a/osu.Game/Online/Leaderboards/DrawableRank.cs b/osu.Game/Online/Leaderboards/DrawableRank.cs index 50cb58c6ab..20bda4601f 100644 --- a/osu.Game/Online/Leaderboards/DrawableRank.cs +++ b/osu.Game/Online/Leaderboards/DrawableRank.cs @@ -58,7 +58,7 @@ namespace osu.Game.Online.Leaderboards Spacing = new Vector2(-3, 0), Padding = new MarginPadding { Top = 5 }, Colour = getRankNameColour(), - Font = OsuFont.GetFont(Typeface.Venera, 25), + Font = OsuFont.Numeric.With(size: 25), Text = getRankName(), ShadowColour = Color4.Black.Opacity(0.3f), ShadowOffset = new Vector2(0, 0.08f), From 75968fb4baa8657c49f0bf9865a83c842553279b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Mar 2020 11:46:48 +0900 Subject: [PATCH 41/85] Update resources once more --- osu.Android.props | 2 +- osu.Game/OsuGameBase.cs | 4 ++-- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index d62011bf1d..1c4a6ffe75 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 676b4b6c7a..a890331f05 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -162,9 +162,9 @@ namespace osu.Game AddFont(Resources, @"Fonts/Torus-Regular"); AddFont(Resources, @"Fonts/Torus-Light"); - AddFont(Resources, @"Fonts/Venera"); AddFont(Resources, @"Fonts/Venera-Light"); - AddFont(Resources, @"Fonts/Venera-Medium"); + AddFont(Resources, @"Fonts/Venera-Bold"); + AddFont(Resources, @"Fonts/Venera-Black"); runMigrations(); diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index e407fca8b7..4d59b709aa 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -22,7 +22,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index cc0b8074bb..6897d3e625 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + From 275d075acda463cafa23bb6ac97145d3a1262597 Mon Sep 17 00:00:00 2001 From: mcendu Date: Wed, 4 Mar 2020 10:53:30 +0800 Subject: [PATCH 42/85] comment out log locations in bug report template --- .github/ISSUE_TEMPLATE/01-bug-issues.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/01-bug-issues.md b/.github/ISSUE_TEMPLATE/01-bug-issues.md index 0aff276d03..0b80ce44dd 100644 --- a/.github/ISSUE_TEMPLATE/01-bug-issues.md +++ b/.github/ISSUE_TEMPLATE/01-bug-issues.md @@ -9,6 +9,8 @@ about: Issues regarding encountered bugs. **osu!lazer version:** **Logs:** + From 0f7316d41d54a6c1c522aa3bdbac2992095ebd45 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Mar 2020 12:05:23 +0900 Subject: [PATCH 43/85] Move file to other file --- .../Ladder/Components/LadderEditorSettings.cs | 62 ------------------- .../Components/LadderSettingsDropdown.cs | 26 ++++++++ .../Ladder/Components/SettingsTeamDropdown.cs | 55 ++++++++++++++++ 3 files changed, 81 insertions(+), 62 deletions(-) create mode 100644 osu.Game.Tournament/Screens/Ladder/Components/LadderSettingsDropdown.cs create mode 100644 osu.Game.Tournament/Screens/Ladder/Components/SettingsTeamDropdown.cs diff --git a/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs b/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs index 8ab083ddaf..4aea7ff4c0 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs @@ -9,7 +9,6 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Input.Events; -using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Settings; using osu.Game.Screens.Play.PlayerSettings; using osu.Game.Tournament.Components; @@ -126,66 +125,5 @@ namespace osu.Game.Tournament.Screens.Ladder.Components }); } } - - private class SettingsTeamDropdown : LadderSettingsDropdown - { - public SettingsTeamDropdown(BindableList teams) - { - foreach (var t in teams.Prepend(new TournamentTeam())) - add(t); - - teams.CollectionChanged += (_, args) => - { - switch (args.Action) - { - case NotifyCollectionChangedAction.Add: - args.NewItems.Cast().ForEach(add); - break; - - case NotifyCollectionChangedAction.Remove: - args.OldItems.Cast().ForEach(i => Control.RemoveDropdownItem(i)); - break; - } - }; - } - - private readonly List refBindables = new List(); - - private T boundReference(T obj) - where T : IBindable - { - obj = (T)obj.GetBoundCopy(); - refBindables.Add(obj); - return obj; - } - - private void add(TournamentTeam team) - { - Control.AddDropdownItem(team); - boundReference(team.FullName).BindValueChanged(_ => - { - Control.RemoveDropdownItem(team); - Control.AddDropdownItem(team); - }); - } - } - - private class LadderSettingsDropdown : SettingsDropdown - { - protected override OsuDropdown CreateDropdown() => new DropdownControl(); - - private new class DropdownControl : SettingsDropdown.DropdownControl - { - protected override DropdownMenu CreateMenu() => new Menu(); - - private new class Menu : OsuDropdownMenu - { - public Menu() - { - MaxHeight = 200; - } - } - } - } } } diff --git a/osu.Game.Tournament/Screens/Ladder/Components/LadderSettingsDropdown.cs b/osu.Game.Tournament/Screens/Ladder/Components/LadderSettingsDropdown.cs new file mode 100644 index 0000000000..347e4d91e0 --- /dev/null +++ b/osu.Game.Tournament/Screens/Ladder/Components/LadderSettingsDropdown.cs @@ -0,0 +1,26 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Settings; + +namespace osu.Game.Tournament.Screens.Ladder.Components +{ + public class LadderSettingsDropdown : SettingsDropdown + { + protected override OsuDropdown CreateDropdown() => new DropdownControl(); + + private new class DropdownControl : SettingsDropdown.DropdownControl + { + protected override DropdownMenu CreateMenu() => new Menu(); + + private new class Menu : OsuDropdownMenu + { + public Menu() + { + MaxHeight = 200; + } + } + } + } +} diff --git a/osu.Game.Tournament/Screens/Ladder/Components/SettingsTeamDropdown.cs b/osu.Game.Tournament/Screens/Ladder/Components/SettingsTeamDropdown.cs new file mode 100644 index 0000000000..a630e51e44 --- /dev/null +++ b/osu.Game.Tournament/Screens/Ladder/Components/SettingsTeamDropdown.cs @@ -0,0 +1,55 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; +using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Game.Tournament.Models; + +namespace osu.Game.Tournament.Screens.Ladder.Components +{ + public class SettingsTeamDropdown : LadderSettingsDropdown + { + public SettingsTeamDropdown(BindableList teams) + { + foreach (var t in teams.Prepend(new TournamentTeam())) + add(t); + + teams.CollectionChanged += (_, args) => + { + switch (args.Action) + { + case NotifyCollectionChangedAction.Add: + args.NewItems.Cast().ForEach(add); + break; + + case NotifyCollectionChangedAction.Remove: + args.OldItems.Cast().ForEach(i => Control.RemoveDropdownItem(i)); + break; + } + }; + } + + private readonly List refBindables = new List(); + + private T boundReference(T obj) + where T : IBindable + { + obj = (T)obj.GetBoundCopy(); + refBindables.Add(obj); + return obj; + } + + private void add(TournamentTeam team) + { + Control.AddDropdownItem(team); + boundReference(team.FullName).BindValueChanged(_ => + { + Control.RemoveDropdownItem(team); + Control.AddDropdownItem(team); + }); + } + } +} From fc0821f1942d141854b9ff6823d0762256068a37 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 4 Mar 2020 12:37:42 +0900 Subject: [PATCH 44/85] Fix nullref when deleting teams in the ladder screen --- osu.Game.Tournament/Screens/Ladder/LadderScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs b/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs index 8ea366e1b4..86d1081241 100644 --- a/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs +++ b/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs @@ -80,7 +80,7 @@ namespace osu.Game.Tournament.Screens.Ladder break; case NotifyCollectionChangedAction.Remove: - foreach (var p in args.NewItems.Cast()) + foreach (var p in args.OldItems.Cast()) { foreach (var d in MatchesContainer.Where(d => d.Match == p)) d.Expire(); From e9b0770f6418e904686f0744b82475d9db41f026 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Mar 2020 12:44:44 +0900 Subject: [PATCH 45/85] Apply suggestions from code review Co-Authored-By: Dan Balasescu --- osu.Game/Graphics/OsuFont.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/OsuFont.cs b/osu.Game/Graphics/OsuFont.cs index 4dd7bcdf31..12e36871f0 100644 --- a/osu.Game/Graphics/OsuFont.cs +++ b/osu.Game/Graphics/OsuFont.cs @@ -109,27 +109,27 @@ namespace osu.Game.Graphics Light = 300, /// - /// equivalent to weight 400 + /// Equivalent to weight 400. /// Regular = 400, /// - /// equivalent to weight 500 + /// Equivalent to weight 500. /// Medium = 500, /// - /// equivalent to weight 600 + /// Equivalent to weight 600. /// SemiBold = 600, /// - /// equivalent to weight 700 + /// Equivalent to weight 700. /// Bold = 700, /// - /// equivalent to weight 900 + /// Equivalent to weight 900. /// Black = 900 } From aa17a64a0e383e7a6f7b011439a84f2cf1bef191 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 4 Mar 2020 12:47:01 +0900 Subject: [PATCH 46/85] Apply missed xmldoc suggestion --- osu.Game/Graphics/OsuFont.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/OsuFont.cs b/osu.Game/Graphics/OsuFont.cs index 12e36871f0..841936d2c5 100644 --- a/osu.Game/Graphics/OsuFont.cs +++ b/osu.Game/Graphics/OsuFont.cs @@ -104,7 +104,7 @@ namespace osu.Game.Graphics public enum FontWeight { /// - /// equivalent to weight 300 + /// Equivalent to weight 300. /// Light = 300, From 6b7144e21bf923b3f72af0a2980bab8c416ba718 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Mar 2020 13:19:52 +0900 Subject: [PATCH 47/85] Fix non-matching filename --- .../Screens/Editors/{SeedingEditor.cs => SeedingEditorScreen.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename osu.Game.Tournament/Screens/Editors/{SeedingEditor.cs => SeedingEditorScreen.cs} (100%) diff --git a/osu.Game.Tournament/Screens/Editors/SeedingEditor.cs b/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs similarity index 100% rename from osu.Game.Tournament/Screens/Editors/SeedingEditor.cs rename to osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs From dade58adf39afb94677c26936adc9df2a36b3b2b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Mar 2020 13:21:14 +0900 Subject: [PATCH 48/85] Fix crash from editor screen test scene when trying to view seeding editor --- osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs index d6fa1a29a8..ca8bce1cca 100644 --- a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs @@ -154,7 +154,7 @@ namespace osu.Game.Tournament.Screens.Editors Text = "Edit seeding results", Action = () => { - sceneManager.SetScreen(new SeedingEditorScreen(team)); + sceneManager?.SetScreen(new SeedingEditorScreen(team)); } }, } From 003aeeb05294889a74d99dc59f63747b320c17e9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Mar 2020 13:26:25 +0900 Subject: [PATCH 49/85] Add test scene for seedings editor --- .../Screens/TestSceneSeedingEditorScreen.cs | 25 +++ .../Screens/TestSceneSeedingScreen.cs | 194 +++++++++--------- 2 files changed, 123 insertions(+), 96 deletions(-) create mode 100644 osu.Game.Tournament.Tests/Screens/TestSceneSeedingEditorScreen.cs diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneSeedingEditorScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneSeedingEditorScreen.cs new file mode 100644 index 0000000000..014cd4663b --- /dev/null +++ b/osu.Game.Tournament.Tests/Screens/TestSceneSeedingEditorScreen.cs @@ -0,0 +1,25 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Game.Tournament.Models; +using osu.Game.Tournament.Screens.Editors; + +namespace osu.Game.Tournament.Tests.Screens +{ + public class TestSceneSeedingEditorScreen : LadderTestScene + { + [Cached] + private readonly LadderInfo ladder = new LadderInfo(); + + public TestSceneSeedingEditorScreen() + { + var match = TestSceneSeedingScreen.CreateSampleSeededMatch(); + + Add(new SeedingEditorScreen(match.Team1.Value) + { + Width = 0.85f // create room for control panel + }); + } + } +} diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneSeedingScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneSeedingScreen.cs index eb46d75705..335a6c80a1 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneSeedingScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneSeedingScreen.cs @@ -18,102 +18,7 @@ namespace osu.Game.Tournament.Tests.Screens [BackgroundDependencyLoader] private void load() { - ladder.CurrentMatch.Value = new TournamentMatch - { - Team1 = - { - Value = new TournamentTeam - { - FlagName = { Value = "JP" }, - FullName = { Value = "Japan" }, - LastYearPlacing = { Value = 10 }, - Seed = { Value = "Low" }, - SeedingResults = - { - new SeedingResult - { - Mod = { Value = "NM" }, - Seed = { Value = 10 }, - Beatmaps = - { - new SeedingBeatmap - { - BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist" } }, - Score = 12345672, - Seed = { Value = 24 }, - }, - new SeedingBeatmap - { - BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist" } }, - Score = 1234567, - Seed = { Value = 12 }, - }, - new SeedingBeatmap - { - BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist" } }, - Score = 1234567, - Seed = { Value = 16 }, - } - } - }, - new SeedingResult - { - Mod = { Value = "DT" }, - Seed = { Value = 5 }, - Beatmaps = - { - new SeedingBeatmap - { - BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist" } }, - Score = 234567, - Seed = { Value = 3 }, - }, - new SeedingBeatmap - { - BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist" } }, - Score = 234567, - Seed = { Value = 6 }, - }, - new SeedingBeatmap - { - BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist" } }, - Score = 234567, - Seed = { Value = 12 }, - } - } - } - }, - Players = - { - new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 12 } } }, - new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 16 } } }, - new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 20 } } }, - new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 24 } } }, - new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 30 } } }, - } - } - }, - Team2 = - { - Value = new TournamentTeam - { - FlagName = { Value = "US" }, - FullName = { Value = "United States" }, - Players = - { - new User { Username = "Hello" }, - new User { Username = "Hello" }, - new User { Username = "Hello" }, - new User { Username = "Hello" }, - new User { Username = "Hello" }, - } - } - }, - Round = - { - Value = new TournamentRound { Name = { Value = "Quarterfinals" } } - } - }; + ladder.CurrentMatch.Value = CreateSampleSeededMatch(); Add(new SeedingScreen { @@ -121,5 +26,102 @@ namespace osu.Game.Tournament.Tests.Screens FillAspectRatio = 16 / 9f }); } + + public static TournamentMatch CreateSampleSeededMatch() => new TournamentMatch + { + Team1 = + { + Value = new TournamentTeam + { + FlagName = { Value = "JP" }, + FullName = { Value = "Japan" }, + LastYearPlacing = { Value = 10 }, + Seed = { Value = "Low" }, + SeedingResults = + { + new SeedingResult + { + Mod = { Value = "NM" }, + Seed = { Value = 10 }, + Beatmaps = + { + new SeedingBeatmap + { + BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist" } }, + Score = 12345672, + Seed = { Value = 24 }, + }, + new SeedingBeatmap + { + BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist" } }, + Score = 1234567, + Seed = { Value = 12 }, + }, + new SeedingBeatmap + { + BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist" } }, + Score = 1234567, + Seed = { Value = 16 }, + } + } + }, + new SeedingResult + { + Mod = { Value = "DT" }, + Seed = { Value = 5 }, + Beatmaps = + { + new SeedingBeatmap + { + BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist" } }, + Score = 234567, + Seed = { Value = 3 }, + }, + new SeedingBeatmap + { + BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist" } }, + Score = 234567, + Seed = { Value = 6 }, + }, + new SeedingBeatmap + { + BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist" } }, + Score = 234567, + Seed = { Value = 12 }, + } + } + } + }, + Players = + { + new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 12 } } }, + new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 16 } } }, + new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 20 } } }, + new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 24 } } }, + new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 30 } } }, + } + } + }, + Team2 = + { + Value = new TournamentTeam + { + FlagName = { Value = "US" }, + FullName = { Value = "United States" }, + Players = + { + new User { Username = "Hello" }, + new User { Username = "Hello" }, + new User { Username = "Hello" }, + new User { Username = "Hello" }, + new User { Username = "Hello" }, + } + } + }, + Round = + { + Value = new TournamentRound { Name = { Value = "Quarterfinals" } } + } + }; } } From a3709ae2684d5dae521a6ba6c798505bddc75613 Mon Sep 17 00:00:00 2001 From: mcendu Date: Wed, 4 Mar 2020 13:39:37 +0800 Subject: [PATCH 50/85] Also comment out log locations in crash template --- .github/ISSUE_TEMPLATE/02-crash-issues.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/02-crash-issues.md b/.github/ISSUE_TEMPLATE/02-crash-issues.md index 9c3ae33161..ada8de73c0 100644 --- a/.github/ISSUE_TEMPLATE/02-crash-issues.md +++ b/.github/ISSUE_TEMPLATE/02-crash-issues.md @@ -9,8 +9,10 @@ about: Issues regarding crashes or permanent freezes. **osu!lazer version:** **Logs:** + **Computer Specifications:** From 416b9e4e6f38d64113ed35654bf6435be44c0cda Mon Sep 17 00:00:00 2001 From: mcendu Date: Wed, 4 Mar 2020 14:28:18 +0800 Subject: [PATCH 51/85] fix beatmap status display --- osu.Game/Overlays/BeatmapSet/AuthorInfo.cs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs b/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs index 446a075ae4..e80034f494 100644 --- a/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs +++ b/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs @@ -59,7 +59,25 @@ namespace osu.Game.Overlays.BeatmapSet if (online.Ranked.HasValue) { - fields.Add(new Field("ranked", online.Ranked.Value, OsuFont.GetFont(weight: FontWeight.Bold))); + string verb = "ranked"; + + switch (online.Status) + { + case BeatmapSetOnlineStatus.Ranked: + verb = "ranked"; + break; + case BeatmapSetOnlineStatus.Approved: + verb = "approved"; + break; + case BeatmapSetOnlineStatus.Qualified: + verb = "qualified"; + break; + case BeatmapSetOnlineStatus.Loved: + verb = "loved"; + break; + } + + fields.Add(new Field(verb, online.Ranked.Value, OsuFont.GetFont(weight: FontWeight.Bold))); } else if (online.LastUpdated.HasValue) { From fbd0dfd71b6c889794197e6e747ef9a99a185273 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=9CNate?= Date: Wed, 4 Mar 2020 14:55:51 +0800 Subject: [PATCH 52/85] add blank lines --- osu.Game/Overlays/BeatmapSet/AuthorInfo.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs b/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs index e80034f494..f3bf0b8e5e 100644 --- a/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs +++ b/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs @@ -66,12 +66,15 @@ namespace osu.Game.Overlays.BeatmapSet case BeatmapSetOnlineStatus.Ranked: verb = "ranked"; break; + case BeatmapSetOnlineStatus.Approved: verb = "approved"; break; + case BeatmapSetOnlineStatus.Qualified: verb = "qualified"; break; + case BeatmapSetOnlineStatus.Loved: verb = "loved"; break; From c306d3de2e1839a8cdf058bc0448c80d8caf4ddd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Mar 2020 16:03:43 +0900 Subject: [PATCH 53/85] Fix typo in button Co-Authored-By: Dan Balasescu --- osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs index 68003a1c43..e68946aaf2 100644 --- a/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs @@ -94,7 +94,7 @@ namespace osu.Game.Tournament.Screens.Editors Origin = Anchor.CentreRight, RelativeSizeAxes = Axes.None, Width = 150, - Text = "Delete SeeingResult", + Text = "Delete result", Action = () => { Expire(); From 7464a486d980a4180aa86f3404a77e8234688bcd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Mar 2020 16:35:45 +0900 Subject: [PATCH 54/85] Fix DummyWorkingBeatmap's track completion attempting to change game-wide beatmap --- osu.Game/OsuGame.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 5781a7fbc4..69d5a9a583 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -428,11 +428,17 @@ namespace osu.Game } } - private void currentTrackCompleted() => Schedule(() => + private void currentTrackCompleted() { - if (!Beatmap.Value.Track.Looping && !Beatmap.Disabled) - musicController.NextTrack(); - }); + if (Beatmap.Value is DummyWorkingBeatmap) + return; + + Schedule(() => + { + if (Beatmap.Value.Track.Looping && !Beatmap.Disabled) + musicController.NextTrack(); + }); + } #endregion From eaa77bce142cb905b8be09fdf5ecf2e5bd2e8242 Mon Sep 17 00:00:00 2001 From: mcendu Date: Wed, 4 Mar 2020 16:43:35 +0800 Subject: [PATCH 55/85] Use ToString().ToLowerInvariant() * https://github.com/ppy/osu/pull/8128#issuecomment-594360083 --- osu.Game/Overlays/BeatmapSet/AuthorInfo.cs | 23 +--------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs b/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs index f3bf0b8e5e..31c1439c8f 100644 --- a/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs +++ b/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs @@ -59,28 +59,7 @@ namespace osu.Game.Overlays.BeatmapSet if (online.Ranked.HasValue) { - string verb = "ranked"; - - switch (online.Status) - { - case BeatmapSetOnlineStatus.Ranked: - verb = "ranked"; - break; - - case BeatmapSetOnlineStatus.Approved: - verb = "approved"; - break; - - case BeatmapSetOnlineStatus.Qualified: - verb = "qualified"; - break; - - case BeatmapSetOnlineStatus.Loved: - verb = "loved"; - break; - } - - fields.Add(new Field(verb, online.Ranked.Value, OsuFont.GetFont(weight: FontWeight.Bold))); + fields.Add(new Field(online.Status.ToString().ToLowerInvariant(), online.Ranked.Value, OsuFont.GetFont(weight: FontWeight.Bold))); } else if (online.LastUpdated.HasValue) { From 184d10a75a9f0c4cdc839d8b62d1540ac2650781 Mon Sep 17 00:00:00 2001 From: recapitalverb <41869184+recapitalverb@users.noreply.github.com> Date: Wed, 4 Mar 2020 15:45:22 +0700 Subject: [PATCH 56/85] Revert "Reduce social overlay/direct overlay paddings" This reverts commit cb1129218135bd2b1e2d3a287d381c13e08725d6. --- osu.Game/Overlays/SearchableList/SearchableListOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs b/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs index 72796df6d5..0783c64c20 100644 --- a/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs +++ b/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs @@ -15,7 +15,7 @@ namespace osu.Game.Overlays.SearchableList { public abstract class SearchableListOverlay : FullscreenOverlay { - public const float WIDTH_PADDING = 10; + public const float WIDTH_PADDING = 80; protected SearchableListOverlay(OverlayColourScheme colourScheme) : base(colourScheme) From a1dc59500699d1d83875c58a48bf9b0e489ec187 Mon Sep 17 00:00:00 2001 From: recapitalverb <41869184+recapitalverb@users.noreply.github.com> Date: Wed, 4 Mar 2020 15:46:35 +0700 Subject: [PATCH 57/85] Change scroll container padding --- osu.Game/Overlays/SearchableList/SearchableListOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs b/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs index 0783c64c20..d6174e0733 100644 --- a/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs +++ b/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs @@ -80,7 +80,7 @@ namespace osu.Game.Overlays.SearchableList { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Horizontal = WIDTH_PADDING, Bottom = 50 }, + Padding = new MarginPadding { Horizontal = 10, Bottom = 50 }, Direction = FillDirection.Vertical, }, }, From bac35b2a68f15a0abe079f94a17883e6c040ef68 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Mar 2020 19:09:52 +0900 Subject: [PATCH 58/85] Handle beatmap track changing in a saner way --- osu.Game/OsuGame.cs | 39 ++++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 69d5a9a583..0be9e6cdaa 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -396,18 +396,27 @@ namespace osu.Game private void beatmapChanged(ValueChangedEvent beatmap) { - var nextBeatmap = beatmap.NewValue; - if (nextBeatmap?.Track != null) - nextBeatmap.Track.Completed += currentTrackCompleted; - - var oldBeatmap = beatmap.OldValue; - if (oldBeatmap?.Track != null) - oldBeatmap.Track.Completed -= currentTrackCompleted; + beatmap.OldValue?.CancelAsyncLoad(); updateModDefaults(); - oldBeatmap?.CancelAsyncLoad(); - nextBeatmap?.BeginAsyncLoad(); + var newBeatmap = beatmap.NewValue; + + if (newBeatmap != null) + { + newBeatmap.Track.Completed += () => Scheduler.AddOnce(() => trackCompleted(newBeatmap)); + newBeatmap.BeginAsyncLoad(); + } + + void trackCompleted(WorkingBeatmap b) + { + // the source of track completion is the audio thread, so the beatmap may have changed before a firing. + if (Beatmap.Value != b) + return; + + if (Beatmap.Value.Track.Looping && !Beatmap.Disabled) + musicController.NextTrack(); + } } private void modsChanged(ValueChangedEvent> mods) @@ -428,18 +437,6 @@ namespace osu.Game } } - private void currentTrackCompleted() - { - if (Beatmap.Value is DummyWorkingBeatmap) - return; - - Schedule(() => - { - if (Beatmap.Value.Track.Looping && !Beatmap.Disabled) - musicController.NextTrack(); - }); - } - #endregion private ScheduledDelegate performFromMainMenuTask; From 38d91ccd0d20dcd4f9f4e016f03da00da2ba8152 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Mar 2020 19:07:34 +0900 Subject: [PATCH 59/85] Add comment regarding no-longer-required schedule --- osu.Game/OsuGameBase.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index a890331f05..67aa4a8d4d 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -211,6 +211,10 @@ namespace osu.Game Audio.Tracks.AddAdjustment(AdjustableProperty.Volume, new BindableDouble(0.8)); Beatmap = new NonNullableBindable(defaultBeatmap); + + // ScheduleAfterChildren is safety against something in the current frame accessing the previous beatmap's track + // and potentially causing a reload of it after just unloading. + // Note that the reason for this being added *has* been resolved, so it may be feasible to remover this if required. Beatmap.BindValueChanged(b => ScheduleAfterChildren(() => { // compare to last beatmap as sometimes the two may share a track representation (optimisation, see WorkingBeatmap.TransferTo) From 11ffe6a072e935cbcfeaef5546754c0359770470 Mon Sep 17 00:00:00 2001 From: McEndu Date: Wed, 4 Mar 2020 21:45:01 +0800 Subject: [PATCH 60/85] Remove LD_LIBRARY_PATH from vscode launch.json --- .vscode/launch.json | 42 +----------------------------------------- 1 file changed, 1 insertion(+), 41 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 6480612b2e..4e8af405a2 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -11,11 +11,6 @@ ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build osu! (Debug)", - "linux": { - "env": { - "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.1:${env:LD_LIBRARY_PATH}" - } - }, "console": "internalConsole" }, { @@ -28,11 +23,6 @@ ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build osu! (Release)", - "linux": { - "env": { - "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.1:${env:LD_LIBRARY_PATH}" - } - }, "console": "internalConsole" }, { @@ -45,11 +35,6 @@ ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build tests (Debug)", - "linux": { - "env": { - "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp3.1:${env:LD_LIBRARY_PATH}" - } - }, "console": "internalConsole" }, { @@ -62,11 +47,6 @@ ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build tests (Release)", - "linux": { - "env": { - "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp3.1:${env:LD_LIBRARY_PATH}" - } - }, "console": "internalConsole" }, { @@ -80,11 +60,6 @@ ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build osu! (Debug)", - "linux": { - "env": { - "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.1:${env:LD_LIBRARY_PATH}" - } - }, "console": "internalConsole" }, { @@ -98,11 +73,6 @@ ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build osu! (Release)", - "linux": { - "env": { - "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.1:${env:LD_LIBRARY_PATH}" - } - }, "console": "internalConsole" }, { @@ -116,11 +86,6 @@ ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build tournament tests (Debug)", - "linux": { - "env": { - "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.1:${env:LD_LIBRARY_PATH}" - } - }, "console": "internalConsole" }, { @@ -134,11 +99,6 @@ ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build tournament tests (Release)", - "linux": { - "env": { - "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.1:${env:LD_LIBRARY_PATH}" - } - }, "console": "internalConsole" }, { @@ -169,4 +129,4 @@ "externalConsole": false } ] -} \ No newline at end of file +} From e3e66991b08c4f26b0b8f46a4ef8c414f775a6b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 4 Mar 2020 22:02:36 +0100 Subject: [PATCH 61/85] Move initialisation logic to [SetUp] --- .../Visual/Online/TestSceneUserPanel.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs index 54f06d6ad2..597ca00fb8 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs @@ -13,13 +13,16 @@ namespace osu.Game.Tests.Visual.Online [TestFixture] public class TestSceneUserPanel : OsuTestScene { - private readonly UserPanel peppy; + private readonly Bindable activity = new Bindable(); - public TestSceneUserPanel() + private UserPanel peppy; + + [SetUp] + public void SetUp() => Schedule(() => { UserPanel flyte; - Add(new FillFlowContainer + Child = new FillFlowContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -44,11 +47,12 @@ namespace osu.Game.Tests.Visual.Online SupportLevel = 3, }) { Width = 300 }, }, - }); + }; flyte.Status.Value = new UserStatusOnline(); peppy.Status.Value = null; - } + peppy.Activity.BindTo(activity); + }); [Test] public void UserStatusesTests() @@ -62,10 +66,6 @@ namespace osu.Game.Tests.Visual.Online [Test] public void UserActivitiesTests() { - Bindable activity = new Bindable(); - - peppy.Activity.BindTo(activity); - AddStep("idle", () => { activity.Value = null; }); AddStep("spectating", () => { activity.Value = new UserActivity.Spectating(); }); AddStep("solo", () => { activity.Value = new UserActivity.SoloGame(null, null); }); From 5b25b5dfabf972d05f4829fd55b363c575be4421 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 4 Mar 2020 22:04:49 +0100 Subject: [PATCH 62/85] Change brace style --- .../Visual/Online/TestSceneUserPanel.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs index 597ca00fb8..55017db479 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs @@ -57,21 +57,21 @@ namespace osu.Game.Tests.Visual.Online [Test] public void UserStatusesTests() { - AddStep("online", () => { peppy.Status.Value = new UserStatusOnline(); }); - AddStep(@"do not disturb", () => { peppy.Status.Value = new UserStatusDoNotDisturb(); }); - AddStep(@"offline", () => { peppy.Status.Value = new UserStatusOffline(); }); - AddStep(@"null status", () => { peppy.Status.Value = null; }); + AddStep("online", () => peppy.Status.Value = new UserStatusOnline()); + AddStep(@"do not disturb", () => peppy.Status.Value = new UserStatusDoNotDisturb()); + AddStep(@"offline", () => peppy.Status.Value = new UserStatusOffline()); + AddStep(@"null status", () => peppy.Status.Value = null); } [Test] public void UserActivitiesTests() { - AddStep("idle", () => { activity.Value = null; }); - AddStep("spectating", () => { activity.Value = new UserActivity.Spectating(); }); - AddStep("solo", () => { activity.Value = new UserActivity.SoloGame(null, null); }); - AddStep("choosing", () => { activity.Value = new UserActivity.ChoosingBeatmap(); }); - AddStep("editing", () => { activity.Value = new UserActivity.Editing(null); }); - AddStep("modding", () => { activity.Value = new UserActivity.Modding(); }); + AddStep("idle", () => activity.Value = null); + AddStep("spectating", () => activity.Value = new UserActivity.Spectating()); + AddStep("solo", () => activity.Value = new UserActivity.SoloGame(null, null)); + AddStep("choosing", () => activity.Value = new UserActivity.ChoosingBeatmap()); + AddStep("editing", () => activity.Value = new UserActivity.Editing(null)); + AddStep("modding", () => activity.Value = new UserActivity.Modding()); } } } From 1bd49d50c771846168209054257ae132e854e8e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 4 Mar 2020 22:05:08 +0100 Subject: [PATCH 63/85] Remove unnecessary raw string prefixes --- osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs index 55017db479..f128584b1a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs @@ -58,9 +58,9 @@ namespace osu.Game.Tests.Visual.Online public void UserStatusesTests() { AddStep("online", () => peppy.Status.Value = new UserStatusOnline()); - AddStep(@"do not disturb", () => peppy.Status.Value = new UserStatusDoNotDisturb()); - AddStep(@"offline", () => peppy.Status.Value = new UserStatusOffline()); - AddStep(@"null status", () => peppy.Status.Value = null); + AddStep("do not disturb", () => peppy.Status.Value = new UserStatusDoNotDisturb()); + AddStep("offline", () => peppy.Status.Value = new UserStatusOffline()); + AddStep("null status", () => peppy.Status.Value = null); } [Test] From 5fa2638e81619fe0ba9fa726561972ea9886ac1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 4 Mar 2020 22:05:48 +0100 Subject: [PATCH 64/85] Rename tests to adhere to convention --- osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs index f128584b1a..ad5dfb371a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs @@ -55,7 +55,7 @@ namespace osu.Game.Tests.Visual.Online }); [Test] - public void UserStatusesTests() + public void TestUserStatus() { AddStep("online", () => peppy.Status.Value = new UserStatusOnline()); AddStep("do not disturb", () => peppy.Status.Value = new UserStatusDoNotDisturb()); @@ -64,7 +64,7 @@ namespace osu.Game.Tests.Visual.Online } [Test] - public void UserActivitiesTests() + public void TestUserActivity() { AddStep("idle", () => activity.Value = null); AddStep("spectating", () => activity.Value = new UserActivity.Spectating()); From afa3ce494da1856810c549bf84efa262f59d259f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 4 Mar 2020 22:07:02 +0100 Subject: [PATCH 65/85] Set online status in activity test The test would check nothing otherwise. --- osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs index ad5dfb371a..fae3e9b17b 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs @@ -66,6 +66,8 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestUserActivity() { + AddStep("set online status", () => peppy.Status.Value = new UserStatusOnline()); + AddStep("idle", () => activity.Value = null); AddStep("spectating", () => activity.Value = new UserActivity.Spectating()); AddStep("solo", () => activity.Value = new UserActivity.SoloGame(null, null)); From b8889318dbcc6d7b6c8c28c08d4b063b7918f6cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 4 Mar 2020 22:13:31 +0100 Subject: [PATCH 66/85] Pass rulesets to solo game status --- osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs index fae3e9b17b..80fcef2ed2 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs @@ -2,9 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets; using osu.Game.Users; using osuTK; @@ -17,6 +19,9 @@ namespace osu.Game.Tests.Visual.Online private UserPanel peppy; + [Resolved] + private RulesetStore rulesetStore { get; set; } + [SetUp] public void SetUp() => Schedule(() => { @@ -70,10 +75,15 @@ namespace osu.Game.Tests.Visual.Online AddStep("idle", () => activity.Value = null); AddStep("spectating", () => activity.Value = new UserActivity.Spectating()); - AddStep("solo", () => activity.Value = new UserActivity.SoloGame(null, null)); + AddStep("solo (osu!)", () => activity.Value = soloGameStatusForRuleset(0)); + AddStep("solo (osu!taiko)", () => activity.Value = soloGameStatusForRuleset(1)); + AddStep("solo (osu!catch)", () => activity.Value = soloGameStatusForRuleset(2)); + AddStep("solo (osu!mania)", () => activity.Value = soloGameStatusForRuleset(3)); AddStep("choosing", () => activity.Value = new UserActivity.ChoosingBeatmap()); AddStep("editing", () => activity.Value = new UserActivity.Editing(null)); AddStep("modding", () => activity.Value = new UserActivity.Modding()); } + + private UserActivity soloGameStatusForRuleset(int rulesetId) => new UserActivity.SoloGame(null, rulesetStore.GetRuleset(rulesetId)); } } From ce3786cfd912b2cff9e7431e13cb7761352de8af Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Mar 2020 10:11:27 +0900 Subject: [PATCH 67/85] Rename to ModTestScene (is no longer a sandbox) --- .../Mods/TestSceneOsuModDifficultyAdjust.cs | 2 +- .../Visual/{ModSandboxTestScene.cs => ModTestScene.cs} | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) rename osu.Game/Tests/Visual/{ModSandboxTestScene.cs => ModTestScene.cs} (95%) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs index 20cb9ef05d..c2a7a5003f 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs @@ -12,7 +12,7 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests.Mods { - public class TestSceneOsuModDifficultyAdjust : ModSandboxTestScene + public class TestSceneOsuModDifficultyAdjust : ModTestScene { public override IReadOnlyList RequiredTypes => base.RequiredTypes.Append(typeof(OsuModDifficultyAdjust)).ToList(); diff --git a/osu.Game/Tests/Visual/ModSandboxTestScene.cs b/osu.Game/Tests/Visual/ModTestScene.cs similarity index 95% rename from osu.Game/Tests/Visual/ModSandboxTestScene.cs rename to osu.Game/Tests/Visual/ModTestScene.cs index 8a9cdf009b..1ff061dfac 100644 --- a/osu.Game/Tests/Visual/ModSandboxTestScene.cs +++ b/osu.Game/Tests/Visual/ModTestScene.cs @@ -13,16 +13,16 @@ using osu.Game.Screens.Play; namespace osu.Game.Tests.Visual { - public abstract class ModSandboxTestScene : PlayerTestScene + public abstract class ModTestScene : PlayerTestScene { protected sealed override bool HasCustomSteps => true; public override IReadOnlyList RequiredTypes => new[] { - typeof(ModSandboxTestScene) + typeof(ModTestScene) }; - protected ModSandboxTestScene(Ruleset ruleset) + protected ModTestScene(Ruleset ruleset) : base(ruleset) { } From 2a581ef24786102d41709f08cde6747060418ed1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Mar 2020 10:15:17 +0900 Subject: [PATCH 68/85] Remove required types --- .../Mods/TestSceneOsuModDifficultyAdjust.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs index c2a7a5003f..4a284022e2 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs @@ -1,9 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; -using System.Collections.Generic; -using System.Linq; using NUnit.Framework; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Scoring; @@ -14,8 +11,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { public class TestSceneOsuModDifficultyAdjust : ModTestScene { - public override IReadOnlyList RequiredTypes => base.RequiredTypes.Append(typeof(OsuModDifficultyAdjust)).ToList(); - public TestSceneOsuModDifficultyAdjust() : base(new OsuRuleset()) { From 0f1f1d1a6b91ce361913fbf2aa8bb6fe8a23fa01 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Mar 2020 10:18:37 +0900 Subject: [PATCH 69/85] Remove unused "name" parameter --- .../Mods/TestSceneOsuModDifficultyAdjust.cs | 6 +++--- osu.Game/Tests/Visual/ModTestScene.cs | 8 +------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs index 4a284022e2..8ff55c9728 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs @@ -17,21 +17,21 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods } [Test] - public void TestNoAdjustment() => CreateModTest(new ModTestCaseData("no adjustment", new OsuModDifficultyAdjust()) + public void TestNoAdjustment() => CreateModTest(new ModTestCaseData(new OsuModDifficultyAdjust()) { Autoplay = true, PassCondition = () => ((ScoreAccessibleTestPlayer)Player).ScoreProcessor.JudgedHits >= 2 }); [Test] - public void TestCircleSize10() => CreateModTest(new ModTestCaseData("cs = 10", new OsuModDifficultyAdjust { CircleSize = { Value = 10 } }) + public void TestCircleSize10() => CreateModTest(new ModTestCaseData(new OsuModDifficultyAdjust { CircleSize = { Value = 10 } }) { Autoplay = true, PassCondition = () => ((ScoreAccessibleTestPlayer)Player).ScoreProcessor.JudgedHits >= 2 }); [Test] - public void TestApproachRate10() => CreateModTest(new ModTestCaseData("ar = 10", new OsuModDifficultyAdjust { ApproachRate = { Value = 10 } }) + public void TestApproachRate10() => CreateModTest(new ModTestCaseData(new OsuModDifficultyAdjust { ApproachRate = { Value = 10 } }) { Autoplay = true, PassCondition = () => ((ScoreAccessibleTestPlayer)Player).ScoreProcessor.JudgedHits >= 2 diff --git a/osu.Game/Tests/Visual/ModTestScene.cs b/osu.Game/Tests/Visual/ModTestScene.cs index 1ff061dfac..04f93fc683 100644 --- a/osu.Game/Tests/Visual/ModTestScene.cs +++ b/osu.Game/Tests/Visual/ModTestScene.cs @@ -97,19 +97,13 @@ namespace osu.Game.Tests.Visual [CanBeNull] public Func PassCondition; - /// - /// The name of this test case, displayed in the test browser. - /// - public readonly string Name; - /// /// The this test case tests. /// public readonly Mod Mod; - public ModTestCaseData(string name, Mod mod) + public ModTestCaseData(Mod mod) { - Name = name; Mod = mod; } } From 3b19467eadbcdf0bec89b240a7d981a599391acd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Mar 2020 10:19:42 +0900 Subject: [PATCH 70/85] ModTestCaseData -> ModTestData --- .../Mods/TestSceneOsuModDifficultyAdjust.cs | 6 +++--- osu.Game/Tests/Visual/ModTestScene.cs | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs index 8ff55c9728..427f25fe11 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs @@ -17,21 +17,21 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods } [Test] - public void TestNoAdjustment() => CreateModTest(new ModTestCaseData(new OsuModDifficultyAdjust()) + public void TestNoAdjustment() => CreateModTest(new ModTestData(new OsuModDifficultyAdjust()) { Autoplay = true, PassCondition = () => ((ScoreAccessibleTestPlayer)Player).ScoreProcessor.JudgedHits >= 2 }); [Test] - public void TestCircleSize10() => CreateModTest(new ModTestCaseData(new OsuModDifficultyAdjust { CircleSize = { Value = 10 } }) + public void TestCircleSize10() => CreateModTest(new ModTestData(new OsuModDifficultyAdjust { CircleSize = { Value = 10 } }) { Autoplay = true, PassCondition = () => ((ScoreAccessibleTestPlayer)Player).ScoreProcessor.JudgedHits >= 2 }); [Test] - public void TestApproachRate10() => CreateModTest(new ModTestCaseData(new OsuModDifficultyAdjust { ApproachRate = { Value = 10 } }) + public void TestApproachRate10() => CreateModTest(new ModTestData(new OsuModDifficultyAdjust { ApproachRate = { Value = 10 } }) { Autoplay = true, PassCondition = () => ((ScoreAccessibleTestPlayer)Player).ScoreProcessor.JudgedHits >= 2 diff --git a/osu.Game/Tests/Visual/ModTestScene.cs b/osu.Game/Tests/Visual/ModTestScene.cs index 04f93fc683..a8b40a5a68 100644 --- a/osu.Game/Tests/Visual/ModTestScene.cs +++ b/osu.Game/Tests/Visual/ModTestScene.cs @@ -27,11 +27,11 @@ namespace osu.Game.Tests.Visual { } - private ModTestCaseData currentTest; + private ModTestData currentTest; - protected void CreateModTest(ModTestCaseData testCaseData) => CreateTest(() => + protected void CreateModTest(ModTestData testData) => CreateTest(() => { - AddStep("set test data", () => currentTest = testCaseData); + AddStep("set test data", () => currentTest = testData); }); public override void TearDownSteps() @@ -78,7 +78,7 @@ namespace osu.Game.Tests.Visual } } - protected class ModTestCaseData + protected class ModTestData { /// /// Whether to use a replay to simulate an auto-play. True by default. @@ -102,7 +102,7 @@ namespace osu.Game.Tests.Visual /// public readonly Mod Mod; - public ModTestCaseData(Mod mod) + public ModTestData(Mod mod) { Mod = mod; } From fadebcdc03188cb171f453153530e55c6be6c7a9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Mar 2020 10:21:25 +0900 Subject: [PATCH 71/85] Move all sets to object initialiser for code formatting reasons --- .../Mods/TestSceneOsuModDifficultyAdjust.cs | 9 ++++++--- osu.Game/Tests/Visual/ModTestScene.cs | 7 +------ 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs index 427f25fe11..e4b1e30bcd 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs @@ -17,22 +17,25 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods } [Test] - public void TestNoAdjustment() => CreateModTest(new ModTestData(new OsuModDifficultyAdjust()) + public void TestNoAdjustment() => CreateModTest(new ModTestData() { + Mod = new OsuModDifficultyAdjust(), Autoplay = true, PassCondition = () => ((ScoreAccessibleTestPlayer)Player).ScoreProcessor.JudgedHits >= 2 }); [Test] - public void TestCircleSize10() => CreateModTest(new ModTestData(new OsuModDifficultyAdjust { CircleSize = { Value = 10 } }) + public void TestCircleSize10() => CreateModTest(new ModTestData { + Mod = new OsuModDifficultyAdjust { CircleSize = { Value = 10 } }, Autoplay = true, PassCondition = () => ((ScoreAccessibleTestPlayer)Player).ScoreProcessor.JudgedHits >= 2 }); [Test] - public void TestApproachRate10() => CreateModTest(new ModTestData(new OsuModDifficultyAdjust { ApproachRate = { Value = 10 } }) + public void TestApproachRate10() => CreateModTest(new ModTestData { + Mod = new OsuModDifficultyAdjust { ApproachRate = { Value = 10 } }, Autoplay = true, PassCondition = () => ((ScoreAccessibleTestPlayer)Player).ScoreProcessor.JudgedHits >= 2 }); diff --git a/osu.Game/Tests/Visual/ModTestScene.cs b/osu.Game/Tests/Visual/ModTestScene.cs index a8b40a5a68..3d12001cca 100644 --- a/osu.Game/Tests/Visual/ModTestScene.cs +++ b/osu.Game/Tests/Visual/ModTestScene.cs @@ -100,12 +100,7 @@ namespace osu.Game.Tests.Visual /// /// The this test case tests. /// - public readonly Mod Mod; - - public ModTestData(Mod mod) - { - Mod = mod; - } + public Mod Mod; } } } From 5200633f9fa19b8f6994ed6b7ef0ec51d18befdf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Mar 2020 11:25:07 +0900 Subject: [PATCH 72/85] Centralise TestPlayer implementations as much as possible --- .../TestSceneAutoJuiceStream.cs | 3 +- .../TestSceneOsuFlashlight.cs | 4 +- .../TestSceneSkinFallbacks.cs | 5 +- .../TestSceneSpinnerRotation.cs | 5 +- .../TestSceneSwellJudgements.cs | 27 ---------- .../TestSceneTaikoSuddenDeath.cs | 18 ++----- .../Background/TestSceneUserDimBackgrounds.cs | 10 ++-- .../Visual/Gameplay/TestSceneAutoplay.cs | 24 +++------ .../Gameplay/TestSceneGameplayRewinding.cs | 49 ++++--------------- .../Visual/Gameplay/TestScenePause.cs | 7 +-- .../Gameplay/TestScenePauseWhenInactive.cs | 5 +- .../Visual/Gameplay/TestScenePlayerLoader.cs | 13 +---- osu.Game/Tests/Visual/PlayerTestScene.cs | 5 +- osu.Game/Tests/Visual/TestPlayer.cs | 28 +++++++++++ 14 files changed, 63 insertions(+), 140 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs index 74a9c05bf9..ed7bfb9a44 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs @@ -7,7 +7,6 @@ using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; -using osu.Game.Screens.Play; using osu.Game.Tests.Visual; using osuTK; @@ -51,7 +50,7 @@ namespace osu.Game.Rulesets.Catch.Tests return beatmap; } - protected override Player CreatePlayer(Ruleset ruleset) + protected override TestPlayer CreatePlayer(Ruleset ruleset) { SelectedMods.Value = SelectedMods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray(); return base.CreatePlayer(ruleset); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuFlashlight.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuFlashlight.cs index 412effe176..19736a7709 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuFlashlight.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuFlashlight.cs @@ -3,13 +3,13 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Screens.Play; +using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests { public class TestSceneOsuFlashlight : TestSceneOsuPlayer { - protected override Player CreatePlayer(Ruleset ruleset) + protected override TestPlayer CreatePlayer(Ruleset ruleset) { SelectedMods.Value = new Mod[] { new OsuModAutoplay(), new OsuModFlashlight(), }; diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs index 4da1b1dae0..d39e24fc1f 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs @@ -18,7 +18,6 @@ using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Screens.Play; using osu.Game.Skinning; using osu.Game.Storyboards; using osu.Game.Tests.Visual; @@ -56,7 +55,7 @@ namespace osu.Game.Rulesets.Osu.Tests private void checkNextHitObject(string skin) => AddUntilStep($"check skin from {skin}", () => { - var firstObject = ((TestPlayer)Player).DrawableRuleset.Playfield.HitObjectContainer.AliveObjects.OfType().FirstOrDefault(); + var firstObject = Player.DrawableRuleset.Playfield.HitObjectContainer.AliveObjects.OfType().FirstOrDefault(); if (firstObject == null) return false; @@ -75,7 +74,7 @@ namespace osu.Game.Rulesets.Osu.Tests [Resolved] private AudioManager audio { get; set; } - protected override Player CreatePlayer(Ruleset ruleset) => new SkinProvidingPlayer(testUserSkin); + protected override TestPlayer CreatePlayer(Ruleset ruleset) => new SkinProvidingPlayer(testUserSkin); protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) => new CustomSkinWorkingBeatmap(beatmap, storyboard, Clock, audio, testBeatmapSkin); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs index 5cf571d961..ea006ec607 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs @@ -11,7 +11,6 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Tests.Visual; using osuTK; using System.Collections.Generic; using System.Linq; @@ -44,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Tests base.SetUpSteps(); AddUntilStep("wait for track to start running", () => track.IsRunning); - AddStep("retrieve spinner", () => drawableSpinner = (DrawableSpinner)((TestPlayer)Player).DrawableRuleset.Playfield.AllHitObjects.First()); + AddStep("retrieve spinner", () => drawableSpinner = (DrawableSpinner)Player.DrawableRuleset.Playfield.AllHitObjects.First()); } [Test] @@ -89,7 +88,7 @@ namespace osu.Game.Rulesets.Osu.Tests { AddStep($"seek to {time}", () => track.Seek(time)); - AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, ((TestPlayer)Player).DrawableRuleset.FrameStableClock.CurrentTime, 100)); + AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100)); } protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs index ccacc50de1..303f0163b1 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs @@ -1,23 +1,16 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; using System.Linq; using NUnit.Framework; -using osu.Framework.Allocation; using osu.Game.Beatmaps; -using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects; -using osu.Game.Screens.Play; using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Taiko.Tests { public class TestSceneSwellJudgements : PlayerTestScene { - protected new TestPlayer Player => (TestPlayer)base.Player; - public TestSceneSwellJudgements() : base(new TaikoRuleset()) { @@ -49,25 +42,5 @@ namespace osu.Game.Rulesets.Taiko.Tests return beatmap; } - - protected override Player CreatePlayer(Ruleset ruleset) => new TestPlayer(); - - protected class TestPlayer : Player - { - public readonly List Results = new List(); - - public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; - - public TestPlayer() - : base(false, false) - { - } - - [BackgroundDependencyLoader] - private void load() - { - ScoreProcessor.NewJudgement += r => Results.Add(r); - } - } } } diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs index 140433a523..2ab041e191 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs @@ -4,11 +4,9 @@ using System.Linq; using NUnit.Framework; using osu.Game.Beatmaps; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Beatmaps; using osu.Game.Rulesets.Taiko.Mods; using osu.Game.Rulesets.Taiko.Objects; -using osu.Game.Screens.Play; using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Taiko.Tests @@ -22,10 +20,10 @@ namespace osu.Game.Rulesets.Taiko.Tests protected override bool AllowFail => true; - protected override Player CreatePlayer(Ruleset ruleset) + protected override TestPlayer CreatePlayer(Ruleset ruleset) { SelectedMods.Value = SelectedMods.Value.Concat(new[] { new TaikoModSuddenDeath() }).ToArray(); - return new ScoreAccessiblePlayer(); + return base.CreatePlayer(ruleset); } protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => @@ -49,20 +47,10 @@ namespace osu.Game.Rulesets.Taiko.Tests AddStep("Setup judgements", () => { judged = false; - ((ScoreAccessiblePlayer)Player).ScoreProcessor.NewJudgement += b => judged = true; + Player.ScoreProcessor.NewJudgement += b => judged = true; }); AddUntilStep("swell judged", () => judged); AddAssert("not failed", () => !Player.HasFailed); } - - private class ScoreAccessiblePlayer : TestPlayer - { - public ScoreAccessiblePlayer() - : base(false, false) - { - } - - public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; - } } } diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index 6d014ca1ca..06a155e78b 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -49,7 +49,7 @@ namespace osu.Game.Tests.Visual.Background private DummySongSelect songSelect; private TestPlayerLoader playerLoader; - private TestPlayer player; + private LoadBlockingTestPlayer player; private BeatmapManager manager; private RulesetStore rulesets; @@ -81,7 +81,7 @@ namespace osu.Game.Tests.Visual.Background public void PlayerLoaderSettingsHoverTest() { setupUserSettings(); - AddStep("Start player loader", () => songSelect.Push(playerLoader = new TestPlayerLoader(player = new TestPlayer { BlockLoad = true }))); + AddStep("Start player loader", () => songSelect.Push(playerLoader = new TestPlayerLoader(player = new LoadBlockingTestPlayer { BlockLoad = true }))); AddUntilStep("Wait for Player Loader to load", () => playerLoader?.IsLoaded ?? false); AddAssert("Background retained from song select", () => songSelect.IsBackgroundCurrent()); AddStep("Trigger background preview", () => @@ -268,7 +268,7 @@ namespace osu.Game.Tests.Visual.Background { setupUserSettings(); - AddStep("Start player loader", () => songSelect.Push(playerLoader = new TestPlayerLoader(player = new TestPlayer(allowPause)))); + AddStep("Start player loader", () => songSelect.Push(playerLoader = new TestPlayerLoader(player = new LoadBlockingTestPlayer(allowPause)))); AddUntilStep("Wait for Player Loader to load", () => playerLoader.IsLoaded); AddStep("Move mouse to center of screen", () => InputManager.MoveMouseTo(playerLoader.ScreenPos)); @@ -347,7 +347,7 @@ namespace osu.Game.Tests.Visual.Background public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR); } - private class TestPlayer : Visual.TestPlayer + private class LoadBlockingTestPlayer : TestPlayer { protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value); @@ -360,7 +360,7 @@ namespace osu.Game.Tests.Visual.Background public readonly Bindable ReplacesBackground = new Bindable(); public readonly Bindable IsPaused = new Bindable(); - public TestPlayer(bool allowPause = true) + public LoadBlockingTestPlayer(bool allowPause = true) : base(allowPause) { } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs index 4daab8d137..756f31e0bf 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs @@ -5,7 +5,6 @@ using System.ComponentModel; using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets; -using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; using osu.Game.Storyboards; @@ -14,20 +13,22 @@ namespace osu.Game.Tests.Visual.Gameplay [Description("Player instantiated with an autoplay mod.")] public class TestSceneAutoplay : TestSceneAllRulesetPlayers { + protected new TestPlayer Player => (TestPlayer)base.Player; + private ClockBackedTestWorkingBeatmap.TrackVirtualManual track; protected override Player CreatePlayer(Ruleset ruleset) { SelectedMods.Value = SelectedMods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray(); - return new ScoreAccessiblePlayer(); + return new TestPlayer(false, false); } protected override void AddCheckSteps() { - AddUntilStep("score above zero", () => ((ScoreAccessiblePlayer)Player).ScoreProcessor.TotalScore.Value > 0); - AddUntilStep("key counter counted keys", () => ((ScoreAccessiblePlayer)Player).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 2)); + AddUntilStep("score above zero", () => Player.ScoreProcessor.TotalScore.Value > 0); + AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 2)); AddStep("rewind", () => track.Seek(-10000)); - AddUntilStep("key counter reset", () => ((ScoreAccessiblePlayer)Player).HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0)); + AddUntilStep("key counter reset", () => Player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0)); } protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) @@ -38,18 +39,5 @@ namespace osu.Game.Tests.Visual.Gameplay return working; } - - private class ScoreAccessiblePlayer : TestPlayer - { - public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; - public new HUDOverlay HUDOverlay => base.HUDOverlay; - - public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer; - - public ScoreAccessiblePlayer() - : base(false, false) - { - } - } } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs index 78c3b22fb9..310746d179 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -11,12 +10,8 @@ using osu.Framework.Utils; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Rulesets; -using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.UI; -using osu.Game.Screens.Play; using osu.Game.Storyboards; using osuTK; @@ -24,8 +19,6 @@ namespace osu.Game.Tests.Visual.Gameplay { public class TestSceneGameplayRewinding : PlayerTestScene { - private RulesetExposingPlayer player => (RulesetExposingPlayer)Player; - [Resolved] private AudioManager audioManager { get; set; } @@ -48,13 +41,13 @@ namespace osu.Game.Tests.Visual.Gameplay { AddUntilStep("wait for track to start running", () => track.IsRunning); addSeekStep(3000); - AddAssert("all judged", () => player.DrawableRuleset.Playfield.AllHitObjects.All(h => h.Judged)); - AddUntilStep("key counter counted keys", () => player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses >= 7)); - AddStep("clear results", () => player.AppliedResults.Clear()); + AddAssert("all judged", () => Player.DrawableRuleset.Playfield.AllHitObjects.All(h => h.Judged)); + AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses >= 7)); + AddStep("clear results", () => Player.Results.Clear()); addSeekStep(0); - AddAssert("none judged", () => player.DrawableRuleset.Playfield.AllHitObjects.All(h => !h.Judged)); - AddUntilStep("key counters reset", () => player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0)); - AddAssert("no results triggered", () => player.AppliedResults.Count == 0); + AddAssert("none judged", () => Player.DrawableRuleset.Playfield.AllHitObjects.All(h => !h.Judged)); + AddUntilStep("key counters reset", () => Player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0)); + AddAssert("no results triggered", () => Player.Results.Count == 0); } private void addSeekStep(double time) @@ -62,13 +55,13 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep($"seek to {time}", () => track.Seek(time)); // Allow a few frames of lenience - AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, player.DrawableRuleset.FrameStableClock.CurrentTime, 100)); + AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100)); } - protected override Player CreatePlayer(Ruleset ruleset) + protected override TestPlayer CreatePlayer(Ruleset ruleset) { SelectedMods.Value = SelectedMods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray(); - return new RulesetExposingPlayer(); + return base.CreatePlayer(ruleset); } protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) @@ -89,29 +82,5 @@ namespace osu.Game.Tests.Visual.Gameplay return beatmap; } - - private class RulesetExposingPlayer : Player - { - public readonly List AppliedResults = new List(); - - public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; - - public new HUDOverlay HUDOverlay => base.HUDOverlay; - - public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer; - - public new DrawableRuleset DrawableRuleset => base.DrawableRuleset; - - public RulesetExposingPlayer() - : base(false, false) - { - } - - [BackgroundDependencyLoader] - private void load() - { - ScoreProcessor.NewJudgement += r => AppliedResults.Add(r); - } - } } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index ad5bab4681..944e6ca6be 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -11,7 +11,6 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; -using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; using osuTK; using osuTK.Input; @@ -282,14 +281,10 @@ namespace osu.Game.Tests.Visual.Gameplay protected override bool AllowFail => true; - protected override Player CreatePlayer(Ruleset ruleset) => new PausePlayer(); + protected override TestPlayer CreatePlayer(Ruleset ruleset) => new PausePlayer(); protected class PausePlayer : TestPlayer { - public new HealthProcessor HealthProcessor => base.HealthProcessor; - - public new HUDOverlay HUDOverlay => base.HUDOverlay; - public bool FailOverlayVisible => FailOverlay.State.Value == Visibility.Visible; public bool PauseOverlayVisible => PauseOverlay.State.Value == Visibility.Visible; diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs index 3513b6c25a..a83320048b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs @@ -9,15 +9,12 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; -using osu.Game.Screens.Play; namespace osu.Game.Tests.Visual.Gameplay { [HeadlessTest] // we alter unsafe properties on the game host to test inactive window state. public class TestScenePauseWhenInactive : PlayerTestScene { - protected new TestPlayer Player => (TestPlayer)base.Player; - protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) { var beatmap = (Beatmap)base.CreateBeatmap(ruleset); @@ -46,6 +43,6 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("time of pause is after gameplay start time", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= Player.DrawableRuleset.GameplayStartTime); } - protected override Player CreatePlayer(Ruleset ruleset) => new TestPlayer(true, true, true); + protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(true, true, true); } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index 100f99d130..175f909a5a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -9,7 +9,6 @@ using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Utils; @@ -307,17 +306,7 @@ namespace osu.Game.Tests.Visual.Gameplay public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank; } - private class TestPlayer : Visual.TestPlayer - { - public new Bindable> Mods => base.Mods; - - public TestPlayer(bool allowPause = true, bool showResults = true) - : base(allowPause, showResults) - { - } - } - - protected class SlowLoadPlayer : Visual.TestPlayer + protected class SlowLoadPlayer : TestPlayer { public readonly ManualResetEventSlim AllowLoad = new ManualResetEventSlim(false); diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index 7c5ba7d30f..eee31fe014 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -8,7 +8,6 @@ using osu.Framework.Testing; using osu.Game.Configuration; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; -using osu.Game.Screens.Play; namespace osu.Game.Tests.Visual { @@ -16,7 +15,7 @@ namespace osu.Game.Tests.Visual { private readonly Ruleset ruleset; - protected Player Player; + protected TestPlayer Player; protected PlayerTestScene(Ruleset ruleset) { @@ -69,6 +68,6 @@ namespace osu.Game.Tests.Visual LoadScreen(Player); } - protected virtual Player CreatePlayer(Ruleset ruleset) => new TestPlayer(false, false); + protected virtual TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(false, false); } } diff --git a/osu.Game/Tests/Visual/TestPlayer.cs b/osu.Game/Tests/Visual/TestPlayer.cs index 8e3821f1a0..f016d29f38 100644 --- a/osu.Game/Tests/Visual/TestPlayer.cs +++ b/osu.Game/Tests/Visual/TestPlayer.cs @@ -1,23 +1,51 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; namespace osu.Game.Tests.Visual { + /// + /// A player that exposes many components that would otherwise not be available, for testing purposes. + /// public class TestPlayer : Player { protected override bool PauseOnFocusLost { get; } public new DrawableRuleset DrawableRuleset => base.DrawableRuleset; + /// + /// Mods from *player* (not OsuScreen). + /// + public new Bindable> Mods => base.Mods; + + public new HUDOverlay HUDOverlay => base.HUDOverlay; + public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer; + public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; + + public new HealthProcessor HealthProcessor => base.HealthProcessor; + + public readonly List Results = new List(); + public TestPlayer(bool allowPause = true, bool showResults = true, bool pauseOnFocusLost = false) : base(allowPause, showResults) { PauseOnFocusLost = pauseOnFocusLost; } + + [BackgroundDependencyLoader] + private void load() + { + ScoreProcessor.NewJudgement += r => Results.Add(r); + } } } From 26ce0d05d6f45f07cb6214b3f1036200f98513e6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Mar 2020 11:33:30 +0900 Subject: [PATCH 73/85] Use autoplay mod rather than local replay provider --- .../Mods/TestSceneOsuModDifficultyAdjust.cs | 22 ++-------- osu.Game/Tests/Visual/ModTestScene.cs | 41 ++++++++----------- osu.Game/Tests/Visual/TestReplayPlayer.cs | 24 ----------- 3 files changed, 21 insertions(+), 66 deletions(-) delete mode 100644 osu.Game/Tests/Visual/TestReplayPlayer.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs index e4b1e30bcd..0a98f49526 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs @@ -3,8 +3,6 @@ using NUnit.Framework; using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Rulesets.Scoring; -using osu.Game.Scoring; using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests.Mods @@ -17,11 +15,11 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods } [Test] - public void TestNoAdjustment() => CreateModTest(new ModTestData() + public void TestNoAdjustment() => CreateModTest(new ModTestData { Mod = new OsuModDifficultyAdjust(), Autoplay = true, - PassCondition = () => ((ScoreAccessibleTestPlayer)Player).ScoreProcessor.JudgedHits >= 2 + PassCondition = () => Player.ScoreProcessor.JudgedHits >= 2 }); [Test] @@ -29,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new OsuModDifficultyAdjust { CircleSize = { Value = 10 } }, Autoplay = true, - PassCondition = () => ((ScoreAccessibleTestPlayer)Player).ScoreProcessor.JudgedHits >= 2 + PassCondition = () => Player.ScoreProcessor.JudgedHits >= 2 }); [Test] @@ -37,19 +35,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new OsuModDifficultyAdjust { ApproachRate = { Value = 10 } }, Autoplay = true, - PassCondition = () => ((ScoreAccessibleTestPlayer)Player).ScoreProcessor.JudgedHits >= 2 + PassCondition = () => Player.ScoreProcessor.JudgedHits >= 2 }); - - protected override TestPlayer CreateReplayPlayer(Score score, bool allowFail) => new ScoreAccessibleTestPlayer(score, allowFail); - - private class ScoreAccessibleTestPlayer : TestPlayer - { - public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; - - public ScoreAccessibleTestPlayer(Score score, bool allowFail) - : base(score, allowFail) - { - } - } } } diff --git a/osu.Game/Tests/Visual/ModTestScene.cs b/osu.Game/Tests/Visual/ModTestScene.cs index 3d12001cca..9abe543bf6 100644 --- a/osu.Game/Tests/Visual/ModTestScene.cs +++ b/osu.Game/Tests/Visual/ModTestScene.cs @@ -3,13 +3,10 @@ using System; using System.Collections.Generic; -using System.Linq; using JetBrains.Annotations; using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; -using osu.Game.Scoring; -using osu.Game.Screens.Play; namespace osu.Game.Tests.Visual { @@ -27,52 +24,48 @@ namespace osu.Game.Tests.Visual { } - private ModTestData currentTest; + private ModTestData currentTestData; protected void CreateModTest(ModTestData testData) => CreateTest(() => { - AddStep("set test data", () => currentTest = testData); + AddStep("set test data", () => currentTestData = testData); }); public override void TearDownSteps() { AddUntilStep("test passed", () => { - if (currentTest == null) + if (currentTestData == null) return true; - return currentTest.PassCondition?.Invoke() ?? false; + return currentTestData.PassCondition?.Invoke() ?? false; }); base.TearDownSteps(); } - protected sealed override IBeatmap CreateBeatmap(RulesetInfo ruleset) => currentTest?.Beatmap ?? base.CreateBeatmap(ruleset); + protected sealed override IBeatmap CreateBeatmap(RulesetInfo ruleset) => currentTestData?.Beatmap ?? base.CreateBeatmap(ruleset); - protected sealed override Player CreatePlayer(Ruleset ruleset) + protected sealed override TestPlayer CreatePlayer(Ruleset ruleset) { - SelectedMods.Value = SelectedMods.Value.Append(currentTest.Mod).ToArray(); + var mods = new List(SelectedMods.Value); - var score = currentTest.Autoplay - ? ruleset.GetAutoplayMod().CreateReplayScore(Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo, SelectedMods.Value)) - : null; + if (currentTestData.Mod != null) + mods.Add(currentTestData.Mod); + if (currentTestData.Autoplay) + mods.Add(ruleset.GetAutoplayMod()); - return CreateReplayPlayer(score, AllowFail); + SelectedMods.Value = mods; + + return new ModTestPlayer(AllowFail); } - /// - /// Creates the for a test case. - /// - /// The . - /// Whether the player can fail. - protected virtual TestPlayer CreateReplayPlayer(Score score, bool allowFail) => new TestPlayer(score, allowFail); - - protected class TestPlayer : TestReplayPlayer + protected class ModTestPlayer : TestPlayer { protected override bool AllowFail { get; } - public TestPlayer(Score score, bool allowFail) - : base(score, false, false) + public ModTestPlayer(bool allowFail) + : base(false, false) { AllowFail = allowFail; } diff --git a/osu.Game/Tests/Visual/TestReplayPlayer.cs b/osu.Game/Tests/Visual/TestReplayPlayer.cs deleted file mode 100644 index e99fcc1e37..0000000000 --- a/osu.Game/Tests/Visual/TestReplayPlayer.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Game.Rulesets.UI; -using osu.Game.Scoring; -using osu.Game.Screens.Play; - -namespace osu.Game.Tests.Visual -{ - public class TestReplayPlayer : ReplayPlayer - { - protected override bool PauseOnFocusLost { get; } - - public new DrawableRuleset DrawableRuleset => base.DrawableRuleset; - - public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer; - - public TestReplayPlayer(Score score, bool allowPause = true, bool showResults = true, bool pauseOnFocusLost = false) - : base(score, allowPause, showResults) - { - PauseOnFocusLost = pauseOnFocusLost; - } - } -} From 9a12909f09a377f9348a496f0e95a52c2b51cedf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Mar 2020 11:53:04 +0900 Subject: [PATCH 74/85] Test ModDifficultyAdjust is actually taking effect --- .../Mods/TestSceneOsuModDifficultyAdjust.cs | 51 +++++++++++++++++-- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs index 0a98f49526..69415b70e3 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs @@ -1,8 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using NUnit.Framework; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Framework.Utils; +using osu.Game.Graphics.Containers; using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests.Mods @@ -19,7 +25,15 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new OsuModDifficultyAdjust(), Autoplay = true, - PassCondition = () => Player.ScoreProcessor.JudgedHits >= 2 + PassCondition = checkSomeHit + }); + + [Test] + public void TestCircleSize1() => CreateModTest(new ModTestData + { + Mod = new OsuModDifficultyAdjust { CircleSize = { Value = 1 } }, + Autoplay = true, + PassCondition = () => checkSomeHit() && checkObjectsScale(0.78f) }); [Test] @@ -27,7 +41,15 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new OsuModDifficultyAdjust { CircleSize = { Value = 10 } }, Autoplay = true, - PassCondition = () => Player.ScoreProcessor.JudgedHits >= 2 + PassCondition = () => checkSomeHit() && checkObjectsScale(0.15f) + }); + + [Test] + public void TestApproachRate1() => CreateModTest(new ModTestData + { + Mod = new OsuModDifficultyAdjust { ApproachRate = { Value = 1 } }, + Autoplay = true, + PassCondition = () => checkSomeHit() && checkObjectsPreempt(1680) }); [Test] @@ -35,7 +57,30 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new OsuModDifficultyAdjust { ApproachRate = { Value = 10 } }, Autoplay = true, - PassCondition = () => Player.ScoreProcessor.JudgedHits >= 2 + PassCondition = () => checkSomeHit() && checkObjectsPreempt(450) }); + + private bool checkObjectsPreempt(double target) + { + var objects = Player.ChildrenOfType(); + if (!objects.Any()) + return false; + + return objects.All(o => o.HitObject.TimePreempt == target); + } + + private bool checkObjectsScale(float target) + { + var objects = Player.ChildrenOfType(); + if (!objects.Any()) + return false; + + return objects.All(o => Precision.AlmostEquals(o.ChildrenOfType().First().Children.OfType().Single().Scale.X, target)); + } + + private bool checkSomeHit() + { + return Player.ScoreProcessor.JudgedHits >= 2; + } } } From 7229131d369c19ac8b54ab840c125e3bc83ee9a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Mar 2020 13:18:42 +0900 Subject: [PATCH 75/85] Fix song select max displayable star difficulty getting stuck at wrong maximum --- osu.Game/Configuration/OsuConfigManager.cs | 29 ++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index ce959e9057..c5d68e4efe 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Bindables; using osu.Framework.Configuration; using osu.Framework.Configuration.Tracking; using osu.Framework.Extensions; @@ -126,6 +127,34 @@ namespace osu.Game.Configuration public OsuConfigManager(Storage storage) : base(storage) { + Migrate(); + } + + public void Migrate() + { + // arrives as 2020.123.0 + var rawVersion = Get(OsuSetting.Version); + + if (rawVersion.Length < 6) + return; + + var pieces = rawVersion.Split('.'); + + if (!int.TryParse(pieces[0], out int year)) return; + if (!int.TryParse(pieces[1], out int monthDay)) return; + if (!int.TryParse(pieces[2], out int minor)) return; + + int combined = (year * 10000) + monthDay; + + if (combined < 20200305) + { + // the maximum value of this setting was changed. + // if we don't manually increase this, it causes song select to filter out beatmaps the user expects to see. + var maxStars = (BindableDouble)GetOriginalBindable(OsuSetting.DisplayStarsMaximum); + + if (maxStars.Value == 10) + maxStars.Value = maxStars.MaxValue; + } } public override TrackedSettings CreateTrackedSettings() => new TrackedSettings From 6477a7b73e1d7ede6f77cfb76b15e63d1be20bdb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Mar 2020 13:34:04 +0900 Subject: [PATCH 76/85] Centralise creation of UpdateManagers --- osu.Android/OsuGameAndroid.cs | 7 +------ osu.Desktop/OsuGameDesktop.cs | 19 ++++++++++++------- osu.Game/OsuGame.cs | 4 ++++ osu.iOS/OsuGameIOS.cs | 7 ------- 4 files changed, 17 insertions(+), 20 deletions(-) diff --git a/osu.Android/OsuGameAndroid.cs b/osu.Android/OsuGameAndroid.cs index a91c010809..84f215f930 100644 --- a/osu.Android/OsuGameAndroid.cs +++ b/osu.Android/OsuGameAndroid.cs @@ -30,11 +30,6 @@ namespace osu.Android } } - protected override void LoadComplete() - { - base.LoadComplete(); - - Add(new SimpleUpdateManager()); - } + protected override UpdateManager CreateUpdateManager() => new SimpleUpdateManager(); } } \ No newline at end of file diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index f70cc24159..f05ee48914 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -47,20 +47,25 @@ namespace osu.Desktop return null; } + protected override UpdateManager CreateUpdateManager() + { + switch (RuntimeInfo.OS) + { + case RuntimeInfo.Platform.Windows: + return new SquirrelUpdateManager(); + + default: + return new SimpleUpdateManager(); + } + } + protected override void LoadComplete() { base.LoadComplete(); if (!noVersionOverlay) - { LoadComponentAsync(versionManager = new VersionManager { Depth = int.MinValue }, Add); - if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows) - Add(new SquirrelUpdateManager()); - else - Add(new SimpleUpdateManager()); - } - LoadComponentAsync(new DiscordRichPresence(), Add); } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 5781a7fbc4..916464ff53 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -43,6 +43,7 @@ using osu.Game.Overlays.Volume; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Screens.Select; +using osu.Game.Updater; using osu.Game.Utils; using LogLevel = osu.Framework.Logging.LogLevel; @@ -390,6 +391,8 @@ namespace osu.Game protected virtual Loader CreateLoader() => new Loader(); + protected virtual UpdateManager CreateUpdateManager() => new UpdateManager(); + protected override Container CreateScalingContainer() => new ScalingContainer(ScalingMode.Everything); #region Beatmap progression @@ -528,6 +531,7 @@ namespace osu.Game AddRange(new Drawable[] { + CreateUpdateManager(), new VolumeControlReceptor { RelativeSizeAxes = Axes.Both, diff --git a/osu.iOS/OsuGameIOS.cs b/osu.iOS/OsuGameIOS.cs index e5ff4aec95..3a16f81530 100644 --- a/osu.iOS/OsuGameIOS.cs +++ b/osu.iOS/OsuGameIOS.cs @@ -11,12 +11,5 @@ namespace osu.iOS public class OsuGameIOS : OsuGame { public override Version AssemblyVersion => new Version(NSBundle.MainBundle.InfoDictionary["CFBundleVersion"].ToString()); - - protected override void LoadComplete() - { - base.LoadComplete(); - - Add(new UpdateManager()); - } } } From 74b5e76c0ebec3360fb998f4743e461329a12083 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Mar 2020 13:39:55 +0900 Subject: [PATCH 77/85] Fix dependency initialisation ordering --- osu.Game/OsuGame.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 916464ff53..50aefb4dd2 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -531,7 +531,6 @@ namespace osu.Game AddRange(new Drawable[] { - CreateUpdateManager(), new VolumeControlReceptor { RelativeSizeAxes = Axes.Both, @@ -632,6 +631,7 @@ namespace osu.Game chatOverlay.State.ValueChanged += state => channelManager.HighPollRate.Value = state.NewValue == Visibility.Visible; Add(externalLinkOpener = new ExternalLinkOpener()); + Add(CreateUpdateManager()); // dependency on notification overlay // side overlays which cancel each other. var singleDisplaySideOverlays = new OverlayContainer[] { Settings, notifications }; From 1e6710020e3adc3da750730ab6f66802dc50488f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Mar 2020 13:46:25 +0900 Subject: [PATCH 78/85] Remove minor version for now --- osu.Game/Configuration/OsuConfigManager.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index c5d68e4efe..5b20700086 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -142,7 +142,6 @@ namespace osu.Game.Configuration if (!int.TryParse(pieces[0], out int year)) return; if (!int.TryParse(pieces[1], out int monthDay)) return; - if (!int.TryParse(pieces[2], out int minor)) return; int combined = (year * 10000) + monthDay; From 1e1e8cbcb5915ad1d0db77d34926ebc54d0218aa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Mar 2020 14:46:38 +0900 Subject: [PATCH 79/85] Always update version --- osu.Game/Updater/UpdateManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Updater/UpdateManager.cs b/osu.Game/Updater/UpdateManager.cs index 48505a9891..f7a7795d9b 100644 --- a/osu.Game/Updater/UpdateManager.cs +++ b/osu.Game/Updater/UpdateManager.cs @@ -34,12 +34,12 @@ namespace osu.Game.Updater if (game.IsDeployedBuild && version != lastVersion) { - config.Set(OsuSetting.Version, version); - // only show a notification if we've previously saved a version to the config file (ie. not the first run). if (!string.IsNullOrEmpty(lastVersion)) Notifications.Post(new UpdateCompleteNotification(version)); } + + config.Set(OsuSetting.Version, version); } private class UpdateCompleteNotification : SimpleNotification From a311ace62656d854b8136c93ad7f7dcb91e1e9ce Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Mar 2020 14:46:07 +0900 Subject: [PATCH 80/85] Add migration test --- .../Visual/Navigation/OsuGameTestScene.cs | 23 +++++++---- .../Navigation/TestSettingsMigration.cs | 41 +++++++++++++++++++ osu.Game/OsuGameBase.cs | 2 +- osu.Game/Tests/Visual/OsuTestScene.cs | 2 +- 4 files changed, 58 insertions(+), 10 deletions(-) create mode 100644 osu.Game.Tests/Visual/Navigation/TestSettingsMigration.cs diff --git a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs index 70d71d0952..b0bfb64d61 100644 --- a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs +++ b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs @@ -62,14 +62,7 @@ namespace osu.Game.Tests.Visual.Navigation var frameworkConfig = host.Dependencies.Get(); frameworkConfig.GetBindable(FrameworkSetting.CursorSensitivity).Disabled = false; - Game = new TestOsuGame(LocalStorage, API); - Game.SetHost(host); - - // todo: this can be removed once we can run audio tracks without a device present - // see https://github.com/ppy/osu/issues/1302 - Game.LocalConfig.Set(OsuSetting.IntroSequence, IntroSequence.Circles); - - Add(Game); + CreateGame(); }); AddUntilStep("Wait for load", () => Game.IsLoaded); @@ -78,6 +71,18 @@ namespace osu.Game.Tests.Visual.Navigation ConfirmAtMainMenu(); } + protected void CreateGame() + { + Game = new TestOsuGame(LocalStorage, API); + Game.SetHost(host); + + // todo: this can be removed once we can run audio tracks without a device present + // see https://github.com/ppy/osu/issues/1302 + Game.LocalConfig.Set(OsuSetting.IntroSequence, IntroSequence.Circles); + + Add(Game); + } + protected void PushAndConfirm(Func newScreen) { Screen screen = null; @@ -103,6 +108,8 @@ namespace osu.Game.Tests.Visual.Navigation public new Bindable Ruleset => base.Ruleset; + public override string Version => "test game"; + protected override Loader CreateLoader() => new TestLoader(); public new void PerformFromScreen(Action action, IEnumerable validScreens = null) => base.PerformFromScreen(action, validScreens); diff --git a/osu.Game.Tests/Visual/Navigation/TestSettingsMigration.cs b/osu.Game.Tests/Visual/Navigation/TestSettingsMigration.cs new file mode 100644 index 0000000000..c0b77b580e --- /dev/null +++ b/osu.Game.Tests/Visual/Navigation/TestSettingsMigration.cs @@ -0,0 +1,41 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Utils; +using osu.Game.Configuration; + +namespace osu.Game.Tests.Visual.Navigation +{ + public class TestSettingsMigration : OsuGameTestScene + { + public override void RecycleLocalStorage() + { + base.RecycleLocalStorage(); + + using (var config = new OsuConfigManager(LocalStorage)) + { + config.Set(OsuSetting.Version, "2020.101.0"); + config.Set(OsuSetting.DisplayStarsMaximum, 10.0); + } + } + + [Test] + public void TestDisplayStarsMigration() + { + AddAssert("config has migrated value", () => Precision.AlmostEquals(Game.LocalConfig.Get(OsuSetting.DisplayStarsMaximum), 10.1)); + + AddStep("set value again", () => Game.LocalConfig.Set(OsuSetting.DisplayStarsMaximum, 10)); + + AddStep("force save config", () => Game.LocalConfig.Save()); + + AddStep("remove game", () => Remove(Game)); + + AddStep("create game again", CreateGame); + + AddUntilStep("Wait for load", () => Game.IsLoaded); + + AddAssert("config did not migrate value", () => Precision.AlmostEquals(Game.LocalConfig.Get(OsuSetting.DisplayStarsMaximum), 10)); + } + } +} diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index a890331f05..33333e592e 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -97,7 +97,7 @@ namespace osu.Game public bool IsDeployedBuild => AssemblyVersion.Major > 0; - public string Version + public virtual string Version { get { diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index b203557fab..f102e2ece3 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -104,7 +104,7 @@ namespace osu.Game.Tests.Visual base.Content.Add(content = new DrawSizePreservingFillContainer()); } - public void RecycleLocalStorage() + public virtual void RecycleLocalStorage() { if (localStorage?.IsValueCreated == true) { From 507af4fa72cb9c7e98733eb5925198ab4caac756 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Mar 2020 15:35:27 +0900 Subject: [PATCH 81/85] Add comment about rationale behind always updating version in config --- osu.Game/Updater/UpdateManager.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Updater/UpdateManager.cs b/osu.Game/Updater/UpdateManager.cs index f7a7795d9b..28a295215f 100644 --- a/osu.Game/Updater/UpdateManager.cs +++ b/osu.Game/Updater/UpdateManager.cs @@ -39,6 +39,8 @@ namespace osu.Game.Updater Notifications.Post(new UpdateCompleteNotification(version)); } + // debug / local compilations will reset to a non-release string. + // can be useful to check when an install has transitioned between release and otherwise (see OsuConfigManager's migrations). config.Set(OsuSetting.Version, version); } From 5b8037ea7d2b69e29ae82eb54aba16e2bd07efe9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Mar 2020 15:36:36 +0900 Subject: [PATCH 82/85] Add note about early migration return on non-release transitions --- osu.Game/Configuration/OsuConfigManager.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 5b20700086..21de654670 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -140,6 +140,8 @@ namespace osu.Game.Configuration var pieces = rawVersion.Split('.'); + // on a fresh install or when coming from a non-release build, execution will end here. + // we don't want to run migrations in such cases. if (!int.TryParse(pieces[0], out int year)) return; if (!int.TryParse(pieces[1], out int monthDay)) return; From 646c8fe077eca5b6630b397e38fc5b3c6330e7ac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Mar 2020 15:40:48 +0900 Subject: [PATCH 83/85] Add note about version override --- osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs index b0bfb64d61..ea8a06e990 100644 --- a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs +++ b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs @@ -108,6 +108,7 @@ namespace osu.Game.Tests.Visual.Navigation public new Bindable Ruleset => base.Ruleset; + // if we don't do this, when running under nUnit the version that gets populated is that of nUnit. public override string Version => "test game"; protected override Loader CreateLoader() => new TestLoader(); From 9307caa3bfb60e7636033fe78311549da1653487 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 5 Mar 2020 16:58:07 +0900 Subject: [PATCH 84/85] Fix typos --- osu.Game/OsuGame.cs | 2 +- osu.Game/OsuGameBase.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 0be9e6cdaa..e54bbaabb2 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -410,7 +410,7 @@ namespace osu.Game void trackCompleted(WorkingBeatmap b) { - // the source of track completion is the audio thread, so the beatmap may have changed before a firing. + // the source of track completion is the audio thread, so the beatmap may have changed before firing. if (Beatmap.Value != b) return; diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 67aa4a8d4d..1048b37348 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -214,7 +214,7 @@ namespace osu.Game // ScheduleAfterChildren is safety against something in the current frame accessing the previous beatmap's track // and potentially causing a reload of it after just unloading. - // Note that the reason for this being added *has* been resolved, so it may be feasible to remover this if required. + // Note that the reason for this being added *has* been resolved, so it may be feasible to removed this if required. Beatmap.BindValueChanged(b => ScheduleAfterChildren(() => { // compare to last beatmap as sometimes the two may share a track representation (optimisation, see WorkingBeatmap.TransferTo) From 0c1775b52281517e8f6f0518fc930b42b0f26aca Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 5 Mar 2020 17:12:14 +0900 Subject: [PATCH 85/85] Fix incorrect condition and add test --- .../Visual/Navigation/OsuGameTestScene.cs | 2 ++ .../Navigation/TestSceneScreenNavigation.cs | 16 ++++++++++++++++ osu.Game/OsuGame.cs | 10 +++++----- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs index 70d71d0952..e984806dc9 100644 --- a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs +++ b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs @@ -97,6 +97,8 @@ namespace osu.Game.Tests.Visual.Navigation public new SettingsPanel Settings => base.Settings; + public new MusicController MusicController => base.MusicController; + public new OsuConfigManager LocalConfig => base.LocalConfig; public new Bindable Beatmap => base.Beatmap; diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 8258cc9465..9d603ac471 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -114,6 +114,22 @@ namespace osu.Game.Tests.Visual.Navigation AddAssert("Options overlay was closed", () => Game.Settings.State.Value == Visibility.Hidden); } + [Test] + public void TestWaitForNextTrackInMenu() + { + bool trackCompleted = false; + + AddUntilStep("Wait for music controller", () => Game.MusicController.IsLoaded); + AddStep("Seek close to end", () => + { + Game.MusicController.SeekTo(Game.Beatmap.Value.Track.Length - 1000); + Game.Beatmap.Value.Track.Completed += () => trackCompleted = true; + }); + + AddUntilStep("Track was completed", () => trackCompleted); + AddUntilStep("Track was restarted", () => Game.Beatmap.Value.Track.IsRunning); + } + private void pushEscape() => AddStep("Press escape", () => pressAndRelease(Key.Escape)); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index e54bbaabb2..19602d524e 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -414,8 +414,8 @@ namespace osu.Game if (Beatmap.Value != b) return; - if (Beatmap.Value.Track.Looping && !Beatmap.Disabled) - musicController.NextTrack(); + if (!Beatmap.Value.Track.Looping && !Beatmap.Disabled) + MusicController.NextTrack(); } } @@ -588,7 +588,7 @@ namespace osu.Game loadComponentSingleFile(new OnScreenDisplay(), Add, true); - loadComponentSingleFile(musicController = new MusicController(), Add, true); + loadComponentSingleFile(MusicController = new MusicController(), Add, true); loadComponentSingleFile(notifications = new NotificationOverlay { @@ -896,7 +896,7 @@ namespace osu.Game private ScalingContainer screenContainer; - private MusicController musicController; + protected MusicController MusicController { get; private set; } protected override bool OnExiting() { @@ -954,7 +954,7 @@ namespace osu.Game { OverlayActivationMode.Value = newOsuScreen.InitialOverlayActivationMode; - musicController.AllowRateAdjustments = newOsuScreen.AllowRateAdjustments; + MusicController.AllowRateAdjustments = newOsuScreen.AllowRateAdjustments; if (newOsuScreen.HideOverlaysOnEnter) CloseAllOverlays();