From e803b0215f146e449f12a77fae1f09db4fdae30f Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 30 Dec 2023 01:38:08 +0100 Subject: [PATCH 01/62] flip along grid axis --- .../Edit/OsuSelectionHandler.cs | 19 ++++++++++--------- osu.Game/Utils/GeometryUtils.cs | 11 +++++++++++ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index cea2adc6e2..021c735ebd 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.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 System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; @@ -15,7 +16,6 @@ using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Utils; using osuTK; @@ -28,6 +28,9 @@ namespace osu.Game.Rulesets.Osu.Edit [Resolved(CanBeNull = true)] private IDistanceSnapProvider? snapProvider { get; set; } + [Resolved] + private OsuGridToolboxGroup gridToolbox { get; set; } = null!; + /// /// During a transform, the initial path types of a single selected slider are stored so they /// can be maintained throughout the operation. @@ -104,13 +107,16 @@ namespace osu.Game.Rulesets.Osu.Edit { var hitObjects = selectedMovableObjects; - var flipQuad = flipOverOrigin ? new Quad(0, 0, OsuPlayfield.BASE_SIZE.X, OsuPlayfield.BASE_SIZE.Y) : GeometryUtils.GetSurroundingQuad(hitObjects); + var flipQuad = flipOverOrigin ? new Quad(gridToolbox.StartPositionX.Value, gridToolbox.StartPositionY.Value, 0, 0) : GeometryUtils.GetSurroundingQuad(hitObjects); + var flipAxis = flipOverOrigin ? new Vector2(MathF.Cos(MathHelper.DegreesToRadians(gridToolbox.GridLinesRotation.Value)), MathF.Sin(MathHelper.DegreesToRadians(gridToolbox.GridLinesRotation.Value))) : Vector2.UnitX; + flipAxis = direction == Direction.Vertical ? flipAxis.PerpendicularLeft : flipAxis; + var controlPointFlipQuad = new Quad(); bool didFlip = false; foreach (var h in hitObjects) { - var flippedPosition = GeometryUtils.GetFlippedPosition(direction, flipQuad, h.Position); + var flippedPosition = GeometryUtils.GetFlippedPosition(flipAxis, flipQuad, h.Position); if (!Precision.AlmostEquals(flippedPosition, h.Position)) { @@ -123,12 +129,7 @@ namespace osu.Game.Rulesets.Osu.Edit didFlip = true; foreach (var cp in slider.Path.ControlPoints) - { - cp.Position = new Vector2( - (direction == Direction.Horizontal ? -1 : 1) * cp.Position.X, - (direction == Direction.Vertical ? -1 : 1) * cp.Position.Y - ); - } + cp.Position = GeometryUtils.GetFlippedPosition(flipAxis, controlPointFlipQuad, cp.Position); } } diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs index e0d217dd48..aacf9b91f9 100644 --- a/osu.Game/Utils/GeometryUtils.cs +++ b/osu.Game/Utils/GeometryUtils.cs @@ -70,6 +70,17 @@ namespace osu.Game.Utils return position; } + /// + /// Given a flip axis vector, a surrounding quad for all selected objects, and a position, + /// will return the flipped position in screen space coordinates. + /// + public static Vector2 GetFlippedPosition(Vector2 axis, Quad quad, Vector2 position) + { + var centre = quad.Centre; + + return position - 2 * Vector2.Dot(position - centre, axis) * axis; + } + /// /// Given a scale vector, a surrounding quad for all selected objects, and a position, /// will return the scaled position in screen space coordinates. From 078fe5a78ca5b0744d12c6f4fa8f242a0730986c Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 30 Dec 2023 01:53:19 +0100 Subject: [PATCH 02/62] Rotate popover rotates around grid center --- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 2 +- osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs | 8 +++++--- osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs | 4 +++- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 84d5adbc52..02e98d75a7 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -104,7 +104,7 @@ namespace osu.Game.Rulesets.Osu.Edit RightToolbox.AddRange(new EditorToolboxGroup[] { OsuGridToolboxGroup, - new TransformToolboxGroup { RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler, }, + new TransformToolboxGroup { RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler, GridToolbox = OsuGridToolboxGroup }, FreehandlSliderToolboxGroup } ); diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs index f09d6b78e6..02a8ff5872 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.UserInterfaceV2; -using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Edit.Components.RadioButtons; using osu.Game.Screens.Edit.Compose.Components; using osuTK; @@ -19,14 +18,17 @@ namespace osu.Game.Rulesets.Osu.Edit { private readonly SelectionRotationHandler rotationHandler; + private readonly OsuGridToolboxGroup gridToolbox; + private readonly Bindable rotationInfo = new Bindable(new PreciseRotationInfo(0, RotationOrigin.PlayfieldCentre)); private SliderWithTextBoxInput angleInput = null!; private EditorRadioButtonCollection rotationOrigin = null!; - public PreciseRotationPopover(SelectionRotationHandler rotationHandler) + public PreciseRotationPopover(SelectionRotationHandler rotationHandler, OsuGridToolboxGroup gridToolbox) { this.rotationHandler = rotationHandler; + this.gridToolbox = gridToolbox; AllowableAnchors = new[] { Anchor.CentreLeft, Anchor.CentreRight }; } @@ -78,7 +80,7 @@ namespace osu.Game.Rulesets.Osu.Edit rotationInfo.BindValueChanged(rotation => { - rotationHandler.Update(rotation.NewValue.Degrees, rotation.NewValue.Origin == RotationOrigin.PlayfieldCentre ? OsuPlayfield.BASE_SIZE / 2 : null); + rotationHandler.Update(rotation.NewValue.Degrees, rotation.NewValue.Origin == RotationOrigin.PlayfieldCentre ? gridToolbox.StartPosition.Value : null); }); } diff --git a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs index 3da9f5b69b..f8df45f545 100644 --- a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs @@ -24,6 +24,8 @@ namespace osu.Game.Rulesets.Osu.Edit public SelectionRotationHandler RotationHandler { get; init; } = null!; + public OsuGridToolboxGroup GridToolbox { get; init; } = null!; + public TransformToolboxGroup() : base("transform") { @@ -41,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Edit { rotateButton = new EditorToolButton("Rotate", () => new SpriteIcon { Icon = FontAwesome.Solid.Undo }, - () => new PreciseRotationPopover(RotationHandler)), + () => new PreciseRotationPopover(RotationHandler, GridToolbox)), // TODO: scale } }; From 09852bc46b6b0b7a78914e19ba3414efab60afdd Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 31 Dec 2023 21:23:13 +0100 Subject: [PATCH 03/62] fix horizontal vs vertical flips being mixed up when rotation angle is too big --- osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 021c735ebd..1cb206c2f8 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.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; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; @@ -107,9 +106,13 @@ namespace osu.Game.Rulesets.Osu.Edit { var hitObjects = selectedMovableObjects; + // If we're flipping over the origin, we take the grid origin position from the grid toolbox. var flipQuad = flipOverOrigin ? new Quad(gridToolbox.StartPositionX.Value, gridToolbox.StartPositionY.Value, 0, 0) : GeometryUtils.GetSurroundingQuad(hitObjects); - var flipAxis = flipOverOrigin ? new Vector2(MathF.Cos(MathHelper.DegreesToRadians(gridToolbox.GridLinesRotation.Value)), MathF.Sin(MathHelper.DegreesToRadians(gridToolbox.GridLinesRotation.Value))) : Vector2.UnitX; + // If we're flipping over the origin, we take the grid rotation from the grid toolbox. + // We want to normalize the rotation angle to -45 to 45 degrees, so horizontal vs vertical flip is not mixed up by the rotation and it stays intuitive to use. + var flipAxis = flipOverOrigin ? GeometryUtils.RotateVector(Vector2.UnitX, -((gridToolbox.GridLinesRotation.Value + 405) % 90 - 45)) : Vector2.UnitX; flipAxis = direction == Direction.Vertical ? flipAxis.PerpendicularLeft : flipAxis; + var controlPointFlipQuad = new Quad(); bool didFlip = false; From 236f029dad22722ebf73c02d40dc19b312b16642 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 1 Feb 2024 16:56:57 +0100 Subject: [PATCH 04/62] Remove Masking from PositionSnapGrid This caused issues in rendering the outline of the grid because the outline was getting masked at some resolutions. --- .../Components/LinedPositionSnapGrid.cs | 128 +++++++++++++++--- .../Compose/Components/PositionSnapGrid.cs | 2 - 2 files changed, 106 insertions(+), 24 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs index ebdd76a4e2..8a7f6b5344 100644 --- a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs @@ -15,18 +15,29 @@ namespace osu.Game.Screens.Edit.Compose.Components { protected void GenerateGridLines(Vector2 step, Vector2 drawSize) { + if (Precision.AlmostEquals(step, Vector2.Zero)) + return; + int index = 0; - var currentPosition = StartPosition.Value; // Make lines the same width independent of display resolution. float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width; - float lineLength = drawSize.Length * 2; + float rotation = MathHelper.RadiansToDegrees(MathF.Atan2(step.Y, step.X)); List generatedLines = new List(); - while (lineDefinitelyIntersectsBox(currentPosition, step.PerpendicularLeft, drawSize) || - isMovingTowardsBox(currentPosition, step, drawSize)) + while (true) { + Vector2 currentPosition = StartPosition.Value + index++ * step; + + if (!lineDefinitelyIntersectsBox(currentPosition, step.PerpendicularLeft, drawSize, out var p1, out var p2)) + { + if (!isMovingTowardsBox(currentPosition, step, drawSize)) + break; + + continue; + } + var gridLine = new Box { Colour = Colour4.White, @@ -34,15 +45,12 @@ namespace osu.Game.Screens.Edit.Compose.Components Origin = Anchor.Centre, RelativeSizeAxes = Axes.None, Width = lineWidth, - Height = lineLength, - Position = currentPosition, - Rotation = MathHelper.RadiansToDegrees(MathF.Atan2(step.Y, step.X)), + Height = Vector2.Distance(p1, p2), + Position = (p1 + p2) / 2, + Rotation = rotation, }; generatedLines.Add(gridLine); - - index += 1; - currentPosition = StartPosition.Value + index * step; } if (generatedLines.Count == 0) @@ -59,23 +67,99 @@ namespace osu.Game.Screens.Edit.Compose.Components (currentPosition + step - box).LengthSquared < (currentPosition - box).LengthSquared; } - private bool lineDefinitelyIntersectsBox(Vector2 lineStart, Vector2 lineDir, Vector2 box) + /// + /// Determines if the line starting at and going in the direction of + /// definitely intersects the box on (0, 0) with the given width and height and returns the intersection points if it does. + /// + /// The start point of the line. + /// The direction of the line. + /// The width and height of the box. + /// The first intersection point. + /// The second intersection point. + /// Whether the line definitely intersects the box. + private bool lineDefinitelyIntersectsBox(Vector2 lineStart, Vector2 lineDir, Vector2 box, out Vector2 p1, out Vector2 p2) { - var p2 = lineStart + lineDir; + p1 = Vector2.Zero; + p2 = Vector2.Zero; - double d1 = det(Vector2.Zero); - double d2 = det(new Vector2(box.X, 0)); - double d3 = det(new Vector2(0, box.Y)); - double d4 = det(box); + if (Precision.AlmostEquals(lineDir.X, 0)) + { + // If the line is vertical, we only need to check if the X coordinate of the line is within the box. + if (!Precision.DefinitelyBigger(lineStart.X, 0) || !Precision.DefinitelyBigger(box.X, lineStart.X)) + return false; - return definitelyDifferentSign(d1, d2) || definitelyDifferentSign(d3, d4) || - definitelyDifferentSign(d1, d3) || definitelyDifferentSign(d2, d4); + p1 = new Vector2(lineStart.X, 0); + p2 = new Vector2(lineStart.X, box.Y); + return true; + } - double det(Vector2 p) => (p.X - lineStart.X) * (p2.Y - lineStart.Y) - (p.Y - lineStart.Y) * (p2.X - lineStart.X); + if (Precision.AlmostEquals(lineDir.Y, 0)) + { + // If the line is horizontal, we only need to check if the Y coordinate of the line is within the box. + if (!Precision.DefinitelyBigger(lineStart.Y, 0) || !Precision.DefinitelyBigger(box.Y, lineStart.Y)) + return false; - bool definitelyDifferentSign(double a, double b) => !Precision.AlmostEquals(a, 0) && - !Precision.AlmostEquals(b, 0) && - Math.Sign(a) != Math.Sign(b); + p1 = new Vector2(0, lineStart.Y); + p2 = new Vector2(box.X, lineStart.Y); + return true; + } + + float m = lineDir.Y / lineDir.X; + float mInv = lineDir.X / lineDir.Y; // Use this to improve numerical stability if X is close to zero. + float b = lineStart.Y - m * lineStart.X; + + // Calculate intersection points with the sides of the box. + var p = new List(4); + + if (0 <= b && b <= box.Y) + p.Add(new Vector2(0, b)); + if (0 <= (box.Y - b) * mInv && (box.Y - b) * mInv <= box.X) + p.Add(new Vector2((box.Y - b) * mInv, box.Y)); + if (0 <= m * box.X + b && m * box.X + b <= box.Y) + p.Add(new Vector2(box.X, m * box.X + b)); + if (0 <= -b * mInv && -b * mInv <= box.X) + p.Add(new Vector2(-b * mInv, 0)); + + switch (p.Count) + { + case 4: + // If there are 4 intersection points, the line is a diagonal of the box. + if (m > 0) + { + p1 = Vector2.Zero; + p2 = box; + } + else + { + p1 = new Vector2(0, box.Y); + p2 = new Vector2(box.X, 0); + } + + break; + + case 3: + // If there are 3 intersection points, the line goes through a corner of the box. + if (p[0] == p[1]) + { + p1 = p[0]; + p2 = p[2]; + } + else + { + p1 = p[0]; + p2 = p[1]; + } + + break; + + case 2: + p1 = p[0]; + p2 = p[1]; + + break; + } + + return !Precision.AlmostEquals(p1, p2); } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs index 36687ef73a..e576ac1e49 100644 --- a/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs @@ -21,8 +21,6 @@ namespace osu.Game.Screens.Edit.Compose.Components protected PositionSnapGrid() { - Masking = true; - StartPosition.BindValueChanged(_ => GridCache.Invalidate()); AddLayout(GridCache); From b4b5cdfcf2d84527ec4d5cb9e504152be59b7e19 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 1 Feb 2024 17:07:03 +0100 Subject: [PATCH 05/62] Fix masking in circular snap grid --- .../Edit/Compose/Components/CircularPositionSnapGrid.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs index 403a270359..791cb33439 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs @@ -82,7 +82,12 @@ namespace osu.Game.Screens.Edit.Compose.Components generatedCircles.First().Alpha = 0.8f; - AddRangeInternal(generatedCircles); + AddInternal(new Container + { + Masking = true, + RelativeSizeAxes = Axes.Both, + Children = generatedCircles, + }); } public override Vector2 GetSnappedPosition(Vector2 original) From b15028a918ececff597d694c3315d732cf784cdc Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 3 Jul 2024 12:36:12 +0200 Subject: [PATCH 06/62] fixes --- osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 8d80ed651e..7720afe60a 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; @@ -23,18 +24,9 @@ namespace osu.Game.Rulesets.Osu.Edit { public partial class OsuSelectionHandler : EditorSelectionHandler { - [Resolved(CanBeNull = true)] - private IDistanceSnapProvider? snapProvider { get; set; } - [Resolved] private OsuGridToolboxGroup gridToolbox { get; set; } = null!; - /// - /// During a transform, the initial path types of a single selected slider are stored so they - /// can be maintained throughout the operation. - /// - private List? referencePathTypes; - protected override void OnSelectionChanged() { base.OnSelectionChanged(); From 5f8512896e0eee0eda8ec0c9410704422d475182 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 3 Jul 2024 12:40:22 +0200 Subject: [PATCH 07/62] use grid origin in scale tool --- osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs | 8 +++++--- osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index a299eebbce..65a07e2e2f 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; -using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Edit.Components.RadioButtons; using osuTK; @@ -20,6 +19,8 @@ namespace osu.Game.Rulesets.Osu.Edit { private readonly OsuSelectionScaleHandler scaleHandler; + private readonly OsuGridToolboxGroup gridToolbox; + private readonly Bindable scaleInfo = new Bindable(new PreciseScaleInfo(1, ScaleOrigin.PlayfieldCentre, true, true)); private SliderWithTextBoxInput scaleInput = null!; @@ -32,9 +33,10 @@ namespace osu.Game.Rulesets.Osu.Edit private OsuCheckbox xCheckBox = null!; private OsuCheckbox yCheckBox = null!; - public PreciseScalePopover(OsuSelectionScaleHandler scaleHandler) + public PreciseScalePopover(OsuSelectionScaleHandler scaleHandler, OsuGridToolboxGroup gridToolbox) { this.scaleHandler = scaleHandler; + this.gridToolbox = gridToolbox; AllowableAnchors = new[] { Anchor.CentreLeft, Anchor.CentreRight }; } @@ -179,7 +181,7 @@ namespace osu.Game.Rulesets.Osu.Edit updateAxisCheckBoxesEnabled(); } - private Vector2? getOriginPosition(PreciseScaleInfo scale) => scale.Origin == ScaleOrigin.PlayfieldCentre ? OsuPlayfield.BASE_SIZE / 2 : null; + private Vector2? getOriginPosition(PreciseScaleInfo scale) => scale.Origin == ScaleOrigin.PlayfieldCentre ? gridToolbox.StartPosition.Value : null; private void setAxis(bool x, bool y) { diff --git a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs index 2e4d7e8b91..a41412cbe3 100644 --- a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Osu.Edit () => new PreciseRotationPopover(RotationHandler, GridToolbox)), scaleButton = new EditorToolButton("Scale", () => new SpriteIcon { Icon = FontAwesome.Solid.ArrowsAlt }, - () => new PreciseScalePopover(ScaleHandler)) + () => new PreciseScalePopover(ScaleHandler, GridToolbox)) } }; } From d0715c5f12a98e9a31465600d1cc69bb0efe1df2 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 3 Jul 2024 16:23:19 +0200 Subject: [PATCH 08/62] scale along rotated axis --- .../Edit/OsuSelectionScaleHandler.cs | 104 ++++++++++++++---- .../Edit/PreciseScalePopover.cs | 8 +- .../Editing/TestSceneComposeSelectBox.cs | 2 +- .../SkinEditor/SkinSelectionScaleHandler.cs | 2 +- .../Components/SelectionScaleHandler.cs | 8 +- osu.Game/Utils/GeometryUtils.cs | 4 +- 6 files changed, 97 insertions(+), 31 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index f4fd48f183..cfcf90e5f5 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Osu.Edit defaultOrigin = OriginalSurroundingQuad.Value.Centre; } - public override void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both) + public override void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both, float axisRotation = 0) { if (!OperationInProgress.Value) throw new InvalidOperationException($"Cannot {nameof(Update)} a scale operation without calling {nameof(Begin)} first!"); @@ -94,6 +94,7 @@ namespace osu.Game.Rulesets.Osu.Edit Debug.Assert(objectsInScale != null && defaultOrigin != null && OriginalSurroundingQuad != null); Vector2 actualOrigin = origin ?? defaultOrigin.Value; + scale = clampScaleToAdjustAxis(scale, adjustAxis); // for the time being, allow resizing of slider paths only if the slider is // the only hit object selected. with a group selection, it's likely the user @@ -102,15 +103,15 @@ namespace osu.Game.Rulesets.Osu.Edit { var originalInfo = objectsInScale[slider]; Debug.Assert(originalInfo.PathControlPointPositions != null && originalInfo.PathControlPointTypes != null); - scaleSlider(slider, scale, originalInfo.PathControlPointPositions, originalInfo.PathControlPointTypes); + scaleSlider(slider, scale, originalInfo.PathControlPointPositions, originalInfo.PathControlPointTypes, axisRotation); } else { - scale = ClampScaleToPlayfieldBounds(scale, actualOrigin); + scale = ClampScaleToPlayfieldBounds(scale, actualOrigin, adjustAxis, axisRotation); foreach (var (ho, originalState) in objectsInScale) { - ho.Position = GeometryUtils.GetScaledPosition(scale, actualOrigin, originalState.Position); + ho.Position = GeometryUtils.GetScaledPosition(scale, actualOrigin, originalState.Position, axisRotation); } } @@ -134,14 +135,34 @@ namespace osu.Game.Rulesets.Osu.Edit private IEnumerable selectedMovableObjects => selectedItems.Cast() .Where(h => h is not Spinner); - private void scaleSlider(Slider slider, Vector2 scale, Vector2[] originalPathPositions, PathType?[] originalPathTypes) + private Vector2 clampScaleToAdjustAxis(Vector2 scale, Axes adjustAxis) + { + switch (adjustAxis) + { + case Axes.Y: + scale.X = 1; + break; + + case Axes.X: + scale.Y = 1; + break; + + case Axes.None: + scale = Vector2.One; + break; + } + + return scale; + } + + private void scaleSlider(Slider slider, Vector2 scale, Vector2[] originalPathPositions, PathType?[] originalPathTypes, float axisRotation = 0) { scale = Vector2.ComponentMax(scale, new Vector2(Precision.FLOAT_EPSILON)); // Maintain the path types in case they were defaulted to bezier at some point during scaling for (int i = 0; i < slider.Path.ControlPoints.Count; i++) { - slider.Path.ControlPoints[i].Position = originalPathPositions[i] * scale; + slider.Path.ControlPoints[i].Position = GeometryUtils.GetScaledPosition(scale, Vector2.Zero, originalPathPositions[i], axisRotation); slider.Path.ControlPoints[i].Type = originalPathTypes[i]; } @@ -176,11 +197,13 @@ namespace osu.Game.Rulesets.Osu.Edit /// /// The origin from which the scale operation is performed /// The scale to be clamped + /// The axes to adjust the scale in. + /// The rotation of the axes in degrees /// The clamped scale vector - public Vector2 ClampScaleToPlayfieldBounds(Vector2 scale, Vector2? origin = null) + public Vector2 ClampScaleToPlayfieldBounds(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both, float axisRotation = 0) { //todo: this is not always correct for selections involving sliders. This approximation assumes each point is scaled independently, but sliderends move with the sliderhead. - if (objectsInScale == null) + if (objectsInScale == null || adjustAxis == Axes.None) return scale; Debug.Assert(defaultOrigin != null && OriginalSurroundingQuad != null); @@ -188,24 +211,63 @@ namespace osu.Game.Rulesets.Osu.Edit if (objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider) origin = slider.Position; + scale = clampScaleToAdjustAxis(scale, adjustAxis); Vector2 actualOrigin = origin ?? defaultOrigin.Value; var selectionQuad = OriginalSurroundingQuad.Value; - var tl1 = Vector2.Divide(-actualOrigin, selectionQuad.TopLeft - actualOrigin); - var tl2 = Vector2.Divide(OsuPlayfield.BASE_SIZE - actualOrigin, selectionQuad.TopLeft - actualOrigin); - var br1 = Vector2.Divide(-actualOrigin, selectionQuad.BottomRight - actualOrigin); - var br2 = Vector2.Divide(OsuPlayfield.BASE_SIZE - actualOrigin, selectionQuad.BottomRight - actualOrigin); - - if (!Precision.AlmostEquals(selectionQuad.TopLeft.X - actualOrigin.X, 0)) - scale.X = selectionQuad.TopLeft.X - actualOrigin.X < 0 ? MathHelper.Clamp(scale.X, tl2.X, tl1.X) : MathHelper.Clamp(scale.X, tl1.X, tl2.X); - if (!Precision.AlmostEquals(selectionQuad.TopLeft.Y - actualOrigin.Y, 0)) - scale.Y = selectionQuad.TopLeft.Y - actualOrigin.Y < 0 ? MathHelper.Clamp(scale.Y, tl2.Y, tl1.Y) : MathHelper.Clamp(scale.Y, tl1.Y, tl2.Y); - if (!Precision.AlmostEquals(selectionQuad.BottomRight.X - actualOrigin.X, 0)) - scale.X = selectionQuad.BottomRight.X - actualOrigin.X < 0 ? MathHelper.Clamp(scale.X, br2.X, br1.X) : MathHelper.Clamp(scale.X, br1.X, br2.X); - if (!Precision.AlmostEquals(selectionQuad.BottomRight.Y - actualOrigin.Y, 0)) - scale.Y = selectionQuad.BottomRight.Y - actualOrigin.Y < 0 ? MathHelper.Clamp(scale.Y, br2.Y, br1.Y) : MathHelper.Clamp(scale.Y, br1.Y, br2.Y); + scale = clampToBound(scale, selectionQuad.BottomRight, OsuPlayfield.BASE_SIZE.X, Axes.X); + scale = clampToBound(scale, selectionQuad.BottomRight, OsuPlayfield.BASE_SIZE.Y, Axes.Y); + scale = clampToBound(scale, selectionQuad.TopLeft, 0, Axes.X); + scale = clampToBound(scale, selectionQuad.TopLeft, 0, Axes.Y); return Vector2.ComponentMax(scale, new Vector2(Precision.FLOAT_EPSILON)); + + Vector2 clampToBound(Vector2 s, Vector2 p, float bound, Axes axis) + { + float px = p.X - actualOrigin.X; + float py = p.Y - actualOrigin.Y; + float c = axis == Axes.X ? bound - actualOrigin.X : bound - actualOrigin.Y; + float cos = MathF.Cos(float.DegreesToRadians(-axisRotation)); + float sin = MathF.Sin(float.DegreesToRadians(-axisRotation)); + float a, b; + + if (axis == Axes.X) + { + a = cos * cos * px - sin * cos * py; + b = sin * sin * px + sin * cos * py; + } + else + { + a = -sin * cos * px + sin * sin * py; + b = sin * cos * px + cos * cos * py; + } + + switch (adjustAxis) + { + case Axes.X: + if (Precision.AlmostEquals(a, 0) || (c - b) / a < 0) + break; + + s.X = MathF.Min(scale.X, (c - b) / a); + break; + + case Axes.Y: + if (Precision.AlmostEquals(b, 0) || (c - a) / b < 0) + break; + + s.Y = MathF.Min(scale.Y, (c - a) / b); + break; + + case Axes.Both: + if (Precision.AlmostEquals(a + b, 0) || c / (a * s.X + b * s.Y) < 0) + break; + + s = Vector2.ComponentMin(s, s * c / (a * s.X + b * s.Y)); + break; + } + + return s; + } } private void moveSelectionInBounds() diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index 65a07e2e2f..a1907a2fd5 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -130,8 +130,8 @@ namespace osu.Game.Rulesets.Osu.Edit scaleInfo.BindValueChanged(scale => { - var newScale = new Vector2(scale.NewValue.XAxis ? scale.NewValue.Scale : 1, scale.NewValue.YAxis ? scale.NewValue.Scale : 1); - scaleHandler.Update(newScale, getOriginPosition(scale.NewValue)); + var newScale = new Vector2(scale.NewValue.Scale, scale.NewValue.Scale); + scaleHandler.Update(newScale, getOriginPosition(scale.NewValue), getAdjustAxis(scale.NewValue), gridToolbox.GridLinesRotation.Value); }); } @@ -164,7 +164,7 @@ namespace osu.Game.Rulesets.Osu.Edit return; const float max_scale = 10; - var scale = scaleHandler.ClampScaleToPlayfieldBounds(new Vector2(max_scale), getOriginPosition(scaleInfo.Value)); + var scale = scaleHandler.ClampScaleToPlayfieldBounds(new Vector2(max_scale), getOriginPosition(scaleInfo.Value), getAdjustAxis(scaleInfo.Value), gridToolbox.GridLinesRotation.Value); if (!scaleInfo.Value.XAxis) scale.X = max_scale; @@ -183,6 +183,8 @@ namespace osu.Game.Rulesets.Osu.Edit private Vector2? getOriginPosition(PreciseScaleInfo scale) => scale.Origin == ScaleOrigin.PlayfieldCentre ? gridToolbox.StartPosition.Value : null; + private Axes getAdjustAxis(PreciseScaleInfo scale) => scale.XAxis ? scale.YAxis ? Axes.Both : Axes.X : Axes.Y; + private void setAxis(bool x, bool y) { scaleInfo.Value = scaleInfo.Value with { XAxis = x, YAxis = y }; diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs index 28763051e3..30f397f518 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs @@ -134,7 +134,7 @@ namespace osu.Game.Tests.Visual.Editing OriginalSurroundingQuad = new Quad(targetContainer!.X, targetContainer.Y, targetContainer.Width, targetContainer.Height); } - public override void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both) + public override void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both, float axisRotation = 0) { if (targetContainer == null) throw new InvalidOperationException($"Cannot {nameof(Update)} a scale operation without calling {nameof(Begin)} first!"); diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs index 4bfa7fba81..977aaade99 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionScaleHandler.cs @@ -73,7 +73,7 @@ namespace osu.Game.Overlays.SkinEditor isFlippedY = false; } - public override void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both) + public override void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both, float axisRotation = 0) { if (objectsInScale == null) throw new InvalidOperationException($"Cannot {nameof(Update)} a scale operation without calling {nameof(Begin)} first!"); diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs index c91362219c..177de9df33 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs @@ -52,10 +52,11 @@ namespace osu.Game.Screens.Edit.Compose.Components /// If the default value is supplied, a sane implementation-defined default will be used. /// /// The axes to adjust the scale in. - public void ScaleSelection(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both) + /// The rotation of the axes in degrees. + public void ScaleSelection(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both, float axisRotation = 0) { Begin(); - Update(scale, origin, adjustAxis); + Update(scale, origin, adjustAxis, axisRotation); Commit(); } @@ -91,7 +92,8 @@ namespace osu.Game.Screens.Edit.Compose.Components /// If the default value is supplied, a sane implementation-defined default will be used. /// /// The axes to adjust the scale in. - public virtual void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both) + /// The rotation of the axes in degrees. + public virtual void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both, float axisRotation = 0) { } diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs index bf1addf6c8..f6e7e81007 100644 --- a/osu.Game/Utils/GeometryUtils.cs +++ b/osu.Game/Utils/GeometryUtils.cs @@ -104,9 +104,9 @@ namespace osu.Game.Utils /// Given a scale multiplier, an origin, and a position, /// will return the scaled position in screen space coordinates. /// - public static Vector2 GetScaledPosition(Vector2 scale, Vector2 origin, Vector2 position) + public static Vector2 GetScaledPosition(Vector2 scale, Vector2 origin, Vector2 position, float axisRotation = 0) { - return origin + (position - origin) * scale; + return origin + RotateVector(RotateVector(position - origin, axisRotation) * scale, -axisRotation); } /// From 979a5e9f3e5de341bcfe785ca7978f5449e0fd78 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 3 Jul 2024 16:41:41 +0200 Subject: [PATCH 09/62] simplify code --- .../Edit/OsuSelectionScaleHandler.cs | 46 ++++++------------- 1 file changed, 13 insertions(+), 33 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index cfcf90e5f5..2cf5a604ed 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -194,7 +194,7 @@ namespace osu.Game.Rulesets.Osu.Edit /// /// Clamp scale for multi-object-scaling where selection does not exceed playfield bounds or flip. - /// + /// The origin from which the scale operation is performed /// The scale to be clamped /// The axes to adjust the scale in. @@ -215,54 +215,34 @@ namespace osu.Game.Rulesets.Osu.Edit Vector2 actualOrigin = origin ?? defaultOrigin.Value; var selectionQuad = OriginalSurroundingQuad.Value; - scale = clampToBound(scale, selectionQuad.BottomRight, OsuPlayfield.BASE_SIZE.X, Axes.X); - scale = clampToBound(scale, selectionQuad.BottomRight, OsuPlayfield.BASE_SIZE.Y, Axes.Y); - scale = clampToBound(scale, selectionQuad.TopLeft, 0, Axes.X); - scale = clampToBound(scale, selectionQuad.TopLeft, 0, Axes.Y); + scale = clampToBound(scale, selectionQuad.BottomRight, OsuPlayfield.BASE_SIZE); + scale = clampToBound(scale, selectionQuad.TopLeft, Vector2.Zero); return Vector2.ComponentMax(scale, new Vector2(Precision.FLOAT_EPSILON)); - Vector2 clampToBound(Vector2 s, Vector2 p, float bound, Axes axis) + float minPositiveComponent(Vector2 v) => MathF.Min(v.X < 0 ? float.PositiveInfinity : v.X, v.Y < 0 ? float.PositiveInfinity : v.Y); + + Vector2 clampToBound(Vector2 s, Vector2 p, Vector2 bound) { - float px = p.X - actualOrigin.X; - float py = p.Y - actualOrigin.Y; - float c = axis == Axes.X ? bound - actualOrigin.X : bound - actualOrigin.Y; + p -= actualOrigin; + bound -= actualOrigin; float cos = MathF.Cos(float.DegreesToRadians(-axisRotation)); float sin = MathF.Sin(float.DegreesToRadians(-axisRotation)); - float a, b; - - if (axis == Axes.X) - { - a = cos * cos * px - sin * cos * py; - b = sin * sin * px + sin * cos * py; - } - else - { - a = -sin * cos * px + sin * sin * py; - b = sin * cos * px + cos * cos * py; - } + var a = new Vector2(cos * cos * p.X - sin * cos * p.Y, -sin * cos * p.X + sin * sin * p.Y); + var b = new Vector2(sin * sin * p.X + sin * cos * p.Y, sin * cos * p.X + cos * cos * p.Y); switch (adjustAxis) { case Axes.X: - if (Precision.AlmostEquals(a, 0) || (c - b) / a < 0) - break; - - s.X = MathF.Min(scale.X, (c - b) / a); + s.X = MathF.Min(scale.X, minPositiveComponent(Vector2.Divide(bound - b, a))); break; case Axes.Y: - if (Precision.AlmostEquals(b, 0) || (c - a) / b < 0) - break; - - s.Y = MathF.Min(scale.Y, (c - a) / b); + s.Y = MathF.Min(scale.Y, minPositiveComponent(Vector2.Divide(bound - a, b))); break; case Axes.Both: - if (Precision.AlmostEquals(a + b, 0) || c / (a * s.X + b * s.Y) < 0) - break; - - s = Vector2.ComponentMin(s, s * c / (a * s.X + b * s.Y)); + s = Vector2.ComponentMin(s, s * minPositiveComponent(Vector2.Divide(bound, a * s.X + b * s.Y))); break; } From 0797d942aecde4e267fca11fbec5bf73611fc9b8 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 3 Jul 2024 16:41:57 +0200 Subject: [PATCH 10/62] fix warning --- osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index 2cf5a604ed..8b87246456 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -194,7 +194,7 @@ namespace osu.Game.Rulesets.Osu.Edit /// /// Clamp scale for multi-object-scaling where selection does not exceed playfield bounds or flip. - /// /// The origin from which the scale operation is performed /// The scale to be clamped /// The axes to adjust the scale in. From 4165ded8134d05f4d6b934255a5678a6a7d74bca Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 3 Jul 2024 19:03:15 +0200 Subject: [PATCH 11/62] fix incorrect rotated bound checking --- .../Edit/OsuSelectionScaleHandler.cs | 53 +++++++++++++++---- osu.Game/Utils/GeometryUtils.cs | 20 ++++--- 2 files changed, 56 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index 8b87246456..d336261499 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -80,12 +80,32 @@ namespace osu.Game.Rulesets.Osu.Edit changeHandler?.BeginChange(); objectsInScale = selectedMovableObjects.ToDictionary(ho => ho, ho => new OriginalHitObjectState(ho)); - OriginalSurroundingQuad = objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider - ? GeometryUtils.GetSurroundingQuad(slider.Path.ControlPoints.Select(p => slider.Position + p.Position)) - : GeometryUtils.GetSurroundingQuad(objectsInScale.Keys); + OriginalSurroundingQuad = getOriginalSurroundingQuad()!; defaultOrigin = OriginalSurroundingQuad.Value.Centre; } + private Quad? getOriginalSurroundingQuad(float axisRotation = 0) + { + if (objectsInScale == null) + return null; + + return objectsInScale.Count == 1 && objectsInScale.First().Value.PathControlPointPositions != null + ? GeometryUtils.GetSurroundingQuad(objectsInScale.First().Value.PathControlPointPositions!.Select(p => objectsInScale.First().Value.Position + p), axisRotation) + : GeometryUtils.GetSurroundingQuad(objectsInScale.Values.SelectMany(s => + { + if (s.EndPosition.HasValue) + { + return new[] + { + s.Position, + s.Position + s.EndPosition.Value + }; + } + + return new[] { s.Position }; + }), axisRotation); + } + public override void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both, float axisRotation = 0) { if (!OperationInProgress.Value) @@ -213,10 +233,23 @@ namespace osu.Game.Rulesets.Osu.Edit scale = clampScaleToAdjustAxis(scale, adjustAxis); Vector2 actualOrigin = origin ?? defaultOrigin.Value; - var selectionQuad = OriginalSurroundingQuad.Value; + var selectionQuad = axisRotation == 0 ? OriginalSurroundingQuad.Value : getOriginalSurroundingQuad(axisRotation)!.Value; + var points = new[] + { + selectionQuad.TopLeft, + selectionQuad.TopRight, + selectionQuad.BottomLeft, + selectionQuad.BottomRight + }; - scale = clampToBound(scale, selectionQuad.BottomRight, OsuPlayfield.BASE_SIZE); - scale = clampToBound(scale, selectionQuad.TopLeft, Vector2.Zero); + float cos = MathF.Cos(float.DegreesToRadians(-axisRotation)); + float sin = MathF.Sin(float.DegreesToRadians(-axisRotation)); + + foreach (var point in points) + { + scale = clampToBound(scale, point, Vector2.Zero); + scale = clampToBound(scale, point, OsuPlayfield.BASE_SIZE); + } return Vector2.ComponentMax(scale, new Vector2(Precision.FLOAT_EPSILON)); @@ -226,19 +259,17 @@ namespace osu.Game.Rulesets.Osu.Edit { p -= actualOrigin; bound -= actualOrigin; - float cos = MathF.Cos(float.DegreesToRadians(-axisRotation)); - float sin = MathF.Sin(float.DegreesToRadians(-axisRotation)); var a = new Vector2(cos * cos * p.X - sin * cos * p.Y, -sin * cos * p.X + sin * sin * p.Y); var b = new Vector2(sin * sin * p.X + sin * cos * p.Y, sin * cos * p.X + cos * cos * p.Y); switch (adjustAxis) { case Axes.X: - s.X = MathF.Min(scale.X, minPositiveComponent(Vector2.Divide(bound - b, a))); + s.X = MathF.Min(s.X, minPositiveComponent(Vector2.Divide(bound - b, a))); break; case Axes.Y: - s.Y = MathF.Min(scale.Y, minPositiveComponent(Vector2.Divide(bound - a, b))); + s.Y = MathF.Min(s.Y, minPositiveComponent(Vector2.Divide(bound - a, b))); break; case Axes.Both: @@ -275,12 +306,14 @@ namespace osu.Game.Rulesets.Osu.Edit public Vector2 Position { get; } public Vector2[]? PathControlPointPositions { get; } public PathType?[]? PathControlPointTypes { get; } + public Vector2? EndPosition { get; } public OriginalHitObjectState(OsuHitObject hitObject) { Position = hitObject.Position; PathControlPointPositions = (hitObject as IHasPath)?.Path.ControlPoints.Select(p => p.Position).ToArray(); PathControlPointTypes = (hitObject as IHasPath)?.Path.ControlPoints.Select(p => p.Type).ToArray(); + EndPosition = (hitObject as IHasPath)?.Path.PositionAt(1); } } } diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs index f6e7e81007..23c25cfffa 100644 --- a/osu.Game/Utils/GeometryUtils.cs +++ b/osu.Game/Utils/GeometryUtils.cs @@ -113,7 +113,8 @@ namespace osu.Game.Utils /// Returns a quad surrounding the provided points. /// /// The points to calculate a quad for. - public static Quad GetSurroundingQuad(IEnumerable points) + /// The rotation in degrees of the axis to align the quad to. + public static Quad GetSurroundingQuad(IEnumerable points, float axisRotation = 0) { if (!points.Any()) return new Quad(); @@ -124,20 +125,25 @@ namespace osu.Game.Utils // Go through all hitobjects to make sure they would remain in the bounds of the editor after movement, before any movement is attempted foreach (var p in points) { - minPosition = Vector2.ComponentMin(minPosition, p); - maxPosition = Vector2.ComponentMax(maxPosition, p); + var pr = RotateVector(p, axisRotation); + minPosition = Vector2.ComponentMin(minPosition, pr); + maxPosition = Vector2.ComponentMax(maxPosition, pr); } - Vector2 size = maxPosition - minPosition; + var p1 = RotateVector(minPosition, -axisRotation); + var p2 = RotateVector(new Vector2(minPosition.X, maxPosition.Y), -axisRotation); + var p3 = RotateVector(maxPosition, -axisRotation); + var p4 = RotateVector(new Vector2(maxPosition.X, minPosition.Y), -axisRotation); - return new Quad(minPosition.X, minPosition.Y, size.X, size.Y); + return new Quad(p1, p2, p3, p4); } /// /// Returns a gamefield-space quad surrounding the provided hit objects. /// /// The hit objects to calculate a quad for. - public static Quad GetSurroundingQuad(IEnumerable hitObjects) => + /// The rotation in degrees of the axis to align the quad to. + public static Quad GetSurroundingQuad(IEnumerable hitObjects, float axisRotation = 0) => GetSurroundingQuad(hitObjects.SelectMany(h => { if (h is IHasPath path) @@ -151,6 +157,6 @@ namespace osu.Game.Utils } return new[] { h.Position }; - })); + }), axisRotation); } } From dfe6c70996b06fa0e597fffbf1aec75e5b1d508c Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 3 Jul 2024 19:08:31 +0200 Subject: [PATCH 12/62] prevent flipping objects far offscreen --- osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 7720afe60a..7d6ef66909 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -15,6 +15,7 @@ using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Utils; using osuTK; @@ -119,6 +120,9 @@ namespace osu.Game.Rulesets.Osu.Edit { var flippedPosition = GeometryUtils.GetFlippedPosition(flipAxis, flipQuad, h.Position); + // Clamp the flipped position inside the playfield bounds, because the flipped position might be outside the playfield bounds if the origin is not centered. + flippedPosition = Vector2.Clamp(flippedPosition, Vector2.Zero, OsuPlayfield.BASE_SIZE); + if (!Precision.AlmostEquals(flippedPosition, h.Position)) { h.Position = flippedPosition; From ae380027772867b45baf80acc910c98cb39fac46 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 14 Jul 2024 15:46:40 +0200 Subject: [PATCH 13/62] Revert "fix incorrect rotated bound checking" This reverts commit 4165ded8134d05f4d6b934255a5678a6a7d74bca. --- .../Edit/OsuSelectionScaleHandler.cs | 53 ++++--------------- osu.Game/Utils/GeometryUtils.cs | 20 +++---- 2 files changed, 17 insertions(+), 56 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index d336261499..8b87246456 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -80,32 +80,12 @@ namespace osu.Game.Rulesets.Osu.Edit changeHandler?.BeginChange(); objectsInScale = selectedMovableObjects.ToDictionary(ho => ho, ho => new OriginalHitObjectState(ho)); - OriginalSurroundingQuad = getOriginalSurroundingQuad()!; + OriginalSurroundingQuad = objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider + ? GeometryUtils.GetSurroundingQuad(slider.Path.ControlPoints.Select(p => slider.Position + p.Position)) + : GeometryUtils.GetSurroundingQuad(objectsInScale.Keys); defaultOrigin = OriginalSurroundingQuad.Value.Centre; } - private Quad? getOriginalSurroundingQuad(float axisRotation = 0) - { - if (objectsInScale == null) - return null; - - return objectsInScale.Count == 1 && objectsInScale.First().Value.PathControlPointPositions != null - ? GeometryUtils.GetSurroundingQuad(objectsInScale.First().Value.PathControlPointPositions!.Select(p => objectsInScale.First().Value.Position + p), axisRotation) - : GeometryUtils.GetSurroundingQuad(objectsInScale.Values.SelectMany(s => - { - if (s.EndPosition.HasValue) - { - return new[] - { - s.Position, - s.Position + s.EndPosition.Value - }; - } - - return new[] { s.Position }; - }), axisRotation); - } - public override void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both, float axisRotation = 0) { if (!OperationInProgress.Value) @@ -233,23 +213,10 @@ namespace osu.Game.Rulesets.Osu.Edit scale = clampScaleToAdjustAxis(scale, adjustAxis); Vector2 actualOrigin = origin ?? defaultOrigin.Value; - var selectionQuad = axisRotation == 0 ? OriginalSurroundingQuad.Value : getOriginalSurroundingQuad(axisRotation)!.Value; - var points = new[] - { - selectionQuad.TopLeft, - selectionQuad.TopRight, - selectionQuad.BottomLeft, - selectionQuad.BottomRight - }; + var selectionQuad = OriginalSurroundingQuad.Value; - float cos = MathF.Cos(float.DegreesToRadians(-axisRotation)); - float sin = MathF.Sin(float.DegreesToRadians(-axisRotation)); - - foreach (var point in points) - { - scale = clampToBound(scale, point, Vector2.Zero); - scale = clampToBound(scale, point, OsuPlayfield.BASE_SIZE); - } + scale = clampToBound(scale, selectionQuad.BottomRight, OsuPlayfield.BASE_SIZE); + scale = clampToBound(scale, selectionQuad.TopLeft, Vector2.Zero); return Vector2.ComponentMax(scale, new Vector2(Precision.FLOAT_EPSILON)); @@ -259,17 +226,19 @@ namespace osu.Game.Rulesets.Osu.Edit { p -= actualOrigin; bound -= actualOrigin; + float cos = MathF.Cos(float.DegreesToRadians(-axisRotation)); + float sin = MathF.Sin(float.DegreesToRadians(-axisRotation)); var a = new Vector2(cos * cos * p.X - sin * cos * p.Y, -sin * cos * p.X + sin * sin * p.Y); var b = new Vector2(sin * sin * p.X + sin * cos * p.Y, sin * cos * p.X + cos * cos * p.Y); switch (adjustAxis) { case Axes.X: - s.X = MathF.Min(s.X, minPositiveComponent(Vector2.Divide(bound - b, a))); + s.X = MathF.Min(scale.X, minPositiveComponent(Vector2.Divide(bound - b, a))); break; case Axes.Y: - s.Y = MathF.Min(s.Y, minPositiveComponent(Vector2.Divide(bound - a, b))); + s.Y = MathF.Min(scale.Y, minPositiveComponent(Vector2.Divide(bound - a, b))); break; case Axes.Both: @@ -306,14 +275,12 @@ namespace osu.Game.Rulesets.Osu.Edit public Vector2 Position { get; } public Vector2[]? PathControlPointPositions { get; } public PathType?[]? PathControlPointTypes { get; } - public Vector2? EndPosition { get; } public OriginalHitObjectState(OsuHitObject hitObject) { Position = hitObject.Position; PathControlPointPositions = (hitObject as IHasPath)?.Path.ControlPoints.Select(p => p.Position).ToArray(); PathControlPointTypes = (hitObject as IHasPath)?.Path.ControlPoints.Select(p => p.Type).ToArray(); - EndPosition = (hitObject as IHasPath)?.Path.PositionAt(1); } } } diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs index 23c25cfffa..f6e7e81007 100644 --- a/osu.Game/Utils/GeometryUtils.cs +++ b/osu.Game/Utils/GeometryUtils.cs @@ -113,8 +113,7 @@ namespace osu.Game.Utils /// Returns a quad surrounding the provided points. /// /// The points to calculate a quad for. - /// The rotation in degrees of the axis to align the quad to. - public static Quad GetSurroundingQuad(IEnumerable points, float axisRotation = 0) + public static Quad GetSurroundingQuad(IEnumerable points) { if (!points.Any()) return new Quad(); @@ -125,25 +124,20 @@ namespace osu.Game.Utils // Go through all hitobjects to make sure they would remain in the bounds of the editor after movement, before any movement is attempted foreach (var p in points) { - var pr = RotateVector(p, axisRotation); - minPosition = Vector2.ComponentMin(minPosition, pr); - maxPosition = Vector2.ComponentMax(maxPosition, pr); + minPosition = Vector2.ComponentMin(minPosition, p); + maxPosition = Vector2.ComponentMax(maxPosition, p); } - var p1 = RotateVector(minPosition, -axisRotation); - var p2 = RotateVector(new Vector2(minPosition.X, maxPosition.Y), -axisRotation); - var p3 = RotateVector(maxPosition, -axisRotation); - var p4 = RotateVector(new Vector2(maxPosition.X, minPosition.Y), -axisRotation); + Vector2 size = maxPosition - minPosition; - return new Quad(p1, p2, p3, p4); + return new Quad(minPosition.X, minPosition.Y, size.X, size.Y); } /// /// Returns a gamefield-space quad surrounding the provided hit objects. /// /// The hit objects to calculate a quad for. - /// The rotation in degrees of the axis to align the quad to. - public static Quad GetSurroundingQuad(IEnumerable hitObjects, float axisRotation = 0) => + public static Quad GetSurroundingQuad(IEnumerable hitObjects) => GetSurroundingQuad(hitObjects.SelectMany(h => { if (h is IHasPath path) @@ -157,6 +151,6 @@ namespace osu.Game.Utils } return new[] { h.Position }; - }), axisRotation); + })); } } From 58eb7f6fe174ba72f06304d0334b0668dde14c74 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 14 Jul 2024 16:58:05 +0200 Subject: [PATCH 14/62] fix rotated scale bounds again --- .../Edit/OsuSelectionScaleHandler.cs | 31 ++++++++++-- osu.Game/Utils/GeometryUtils.cs | 49 ++++++++++++++++++- 2 files changed, 73 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index 8b87246456..56c3ba9315 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -69,6 +69,7 @@ namespace osu.Game.Rulesets.Osu.Edit private Dictionary? objectsInScale; private Vector2? defaultOrigin; + private List? originalConvexHull; public override void Begin() { @@ -84,6 +85,9 @@ namespace osu.Game.Rulesets.Osu.Edit ? GeometryUtils.GetSurroundingQuad(slider.Path.ControlPoints.Select(p => slider.Position + p.Position)) : GeometryUtils.GetSurroundingQuad(objectsInScale.Keys); defaultOrigin = OriginalSurroundingQuad.Value.Centre; + originalConvexHull = objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider2 + ? GeometryUtils.GetConvexHull(slider2.Path.ControlPoints.Select(p => slider2.Position + p.Position)) + : GeometryUtils.GetConvexHull(objectsInScale.Keys); } public override void Update(Vector2 scale, Vector2? origin = null, Axes adjustAxis = Axes.Both, float axisRotation = 0) @@ -211,12 +215,31 @@ namespace osu.Game.Rulesets.Osu.Edit if (objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider) origin = slider.Position; + float cos = MathF.Cos(float.DegreesToRadians(-axisRotation)); + float sin = MathF.Sin(float.DegreesToRadians(-axisRotation)); scale = clampScaleToAdjustAxis(scale, adjustAxis); Vector2 actualOrigin = origin ?? defaultOrigin.Value; - var selectionQuad = OriginalSurroundingQuad.Value; + IEnumerable points; - scale = clampToBound(scale, selectionQuad.BottomRight, OsuPlayfield.BASE_SIZE); - scale = clampToBound(scale, selectionQuad.TopLeft, Vector2.Zero); + if (axisRotation == 0) + { + var selectionQuad = OriginalSurroundingQuad.Value; + points = new[] + { + selectionQuad.TopLeft, + selectionQuad.TopRight, + selectionQuad.BottomLeft, + selectionQuad.BottomRight + }; + } + else + points = originalConvexHull!; + + foreach (var point in points) + { + scale = clampToBound(scale, point, Vector2.Zero); + scale = clampToBound(scale, point, OsuPlayfield.BASE_SIZE); + } return Vector2.ComponentMax(scale, new Vector2(Precision.FLOAT_EPSILON)); @@ -226,8 +249,6 @@ namespace osu.Game.Rulesets.Osu.Edit { p -= actualOrigin; bound -= actualOrigin; - float cos = MathF.Cos(float.DegreesToRadians(-axisRotation)); - float sin = MathF.Sin(float.DegreesToRadians(-axisRotation)); var a = new Vector2(cos * cos * p.X - sin * cos * p.Y, -sin * cos * p.X + sin * sin * p.Y); var b = new Vector2(sin * sin * p.X + sin * cos * p.Y, sin * cos * p.X + cos * cos * p.Y); diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs index f6e7e81007..5a8ca9722e 100644 --- a/osu.Game/Utils/GeometryUtils.cs +++ b/osu.Game/Utils/GeometryUtils.cs @@ -138,7 +138,52 @@ namespace osu.Game.Utils /// /// The hit objects to calculate a quad for. public static Quad GetSurroundingQuad(IEnumerable hitObjects) => - GetSurroundingQuad(hitObjects.SelectMany(h => + GetSurroundingQuad(enumerateStartAndEndPositions(hitObjects)); + + /// + /// Returns the points that make up the convex hull of the provided points. + /// + /// The points to calculate a convex hull. + public static List GetConvexHull(IEnumerable points) + { + List p = points.ToList(); + + if (p.Count <= 1) + return p; + + int n = p.Count, k = 0; + List hull = new List(new Vector2[2 * n]); + + p.Sort((a, b) => a.X == b.X ? a.Y.CompareTo(b.Y) : a.X.CompareTo(b.X)); + + // Build lower hull + for (int i = 0; i < n; ++i) + { + while (k >= 2 && cross(hull[k - 2], hull[k - 1], p[i]) <= 0) + k--; + hull[k] = p[i]; + k++; + } + + // Build upper hull + for (int i = n - 2, t = k + 1; i >= 0; i--) + { + while (k >= t && cross(hull[k - 2], hull[k - 1], p[i]) <= 0) + k--; + hull[k] = p[i]; + k++; + } + + return hull.Take(k - 1).ToList(); + + float cross(Vector2 o, Vector2 a, Vector2 b) => (a.X - o.X) * (b.Y - o.Y) - (a.Y - o.Y) * (b.X - o.X); + } + + public static List GetConvexHull(IEnumerable hitObjects) => + GetConvexHull(enumerateStartAndEndPositions(hitObjects)); + + private static IEnumerable enumerateStartAndEndPositions(IEnumerable hitObjects) => + hitObjects.SelectMany(h => { if (h is IHasPath path) { @@ -151,6 +196,6 @@ namespace osu.Game.Utils } return new[] { h.Position }; - })); + }); } } From 7a319a6d74ee29fbf3e7b5dbc2b7c6c9ca8e4990 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 14 Jul 2024 17:03:17 +0200 Subject: [PATCH 15/62] dont rotate scale when in selection origin mode --- osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index a1907a2fd5..0f04efcfa5 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -131,7 +131,7 @@ namespace osu.Game.Rulesets.Osu.Edit scaleInfo.BindValueChanged(scale => { var newScale = new Vector2(scale.NewValue.Scale, scale.NewValue.Scale); - scaleHandler.Update(newScale, getOriginPosition(scale.NewValue), getAdjustAxis(scale.NewValue), gridToolbox.GridLinesRotation.Value); + scaleHandler.Update(newScale, getOriginPosition(scale.NewValue), getAdjustAxis(scale.NewValue), getRotation(scale.NewValue)); }); } @@ -164,7 +164,7 @@ namespace osu.Game.Rulesets.Osu.Edit return; const float max_scale = 10; - var scale = scaleHandler.ClampScaleToPlayfieldBounds(new Vector2(max_scale), getOriginPosition(scaleInfo.Value), getAdjustAxis(scaleInfo.Value), gridToolbox.GridLinesRotation.Value); + var scale = scaleHandler.ClampScaleToPlayfieldBounds(new Vector2(max_scale), getOriginPosition(scaleInfo.Value), getAdjustAxis(scaleInfo.Value), getRotation(scaleInfo.Value)); if (!scaleInfo.Value.XAxis) scale.X = max_scale; @@ -185,6 +185,8 @@ namespace osu.Game.Rulesets.Osu.Edit private Axes getAdjustAxis(PreciseScaleInfo scale) => scale.XAxis ? scale.YAxis ? Axes.Both : Axes.X : Axes.Y; + private float getRotation(PreciseScaleInfo scale) => scale.Origin == ScaleOrigin.PlayfieldCentre ? gridToolbox.GridLinesRotation.Value : 0; + private void setAxis(bool x, bool y) { scaleInfo.Value = scaleInfo.Value with { XAxis = x, YAxis = y }; From 9e5d099b1b1113304294c17155f42ab4a8ea76cd Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 14 Jul 2024 17:13:22 +0200 Subject: [PATCH 16/62] rename playfield centre origin to grid centre --- .../Edit/PreciseRotationPopover.cs | 12 ++++++------ .../Edit/PreciseScalePopover.cs | 16 ++++++++-------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs index 6a3e326c2b..4a1ccc4b61 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Edit private readonly OsuGridToolboxGroup gridToolbox; - private readonly Bindable rotationInfo = new Bindable(new PreciseRotationInfo(0, RotationOrigin.PlayfieldCentre)); + private readonly Bindable rotationInfo = new Bindable(new PreciseRotationInfo(0, RotationOrigin.GridCentre)); private SliderWithTextBoxInput angleInput = null!; private EditorRadioButtonCollection rotationOrigin = null!; @@ -60,9 +60,9 @@ namespace osu.Game.Rulesets.Osu.Edit RelativeSizeAxes = Axes.X, Items = new[] { - new RadioButton("Playfield centre", - () => rotationInfo.Value = rotationInfo.Value with { Origin = RotationOrigin.PlayfieldCentre }, - () => new SpriteIcon { Icon = FontAwesome.Regular.Square }), + new RadioButton("Grid centre", + () => rotationInfo.Value = rotationInfo.Value with { Origin = RotationOrigin.GridCentre }, + () => new SpriteIcon { Icon = FontAwesome.Regular.PlusSquare }), selectionCentreButton = new RadioButton("Selection centre", () => rotationInfo.Value = rotationInfo.Value with { Origin = RotationOrigin.SelectionCentre }, () => new SpriteIcon { Icon = FontAwesome.Solid.VectorSquare }) @@ -95,7 +95,7 @@ namespace osu.Game.Rulesets.Osu.Edit rotationInfo.BindValueChanged(rotation => { - rotationHandler.Update(rotation.NewValue.Degrees, rotation.NewValue.Origin == RotationOrigin.PlayfieldCentre ? gridToolbox.StartPosition.Value : null); + rotationHandler.Update(rotation.NewValue.Degrees, rotation.NewValue.Origin == RotationOrigin.GridCentre ? gridToolbox.StartPosition.Value : null); }); } @@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Osu.Edit public enum RotationOrigin { - PlayfieldCentre, + GridCentre, SelectionCentre } diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index 0f04efcfa5..15ed4c59c3 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Edit private readonly OsuGridToolboxGroup gridToolbox; - private readonly Bindable scaleInfo = new Bindable(new PreciseScaleInfo(1, ScaleOrigin.PlayfieldCentre, true, true)); + private readonly Bindable scaleInfo = new Bindable(new PreciseScaleInfo(1, ScaleOrigin.GridCentre, true, true)); private SliderWithTextBoxInput scaleInput = null!; private BindableNumber scaleInputBindable = null!; @@ -68,9 +68,9 @@ namespace osu.Game.Rulesets.Osu.Edit RelativeSizeAxes = Axes.X, Items = new[] { - playfieldCentreButton = new RadioButton("Playfield centre", - () => setOrigin(ScaleOrigin.PlayfieldCentre), - () => new SpriteIcon { Icon = FontAwesome.Regular.Square }), + playfieldCentreButton = new RadioButton("Grid centre", + () => setOrigin(ScaleOrigin.GridCentre), + () => new SpriteIcon { Icon = FontAwesome.Regular.PlusSquare }), selectionCentreButton = new RadioButton("Selection centre", () => setOrigin(ScaleOrigin.SelectionCentre), () => new SpriteIcon { Icon = FontAwesome.Solid.VectorSquare }) @@ -137,7 +137,7 @@ namespace osu.Game.Rulesets.Osu.Edit private void updateAxisCheckBoxesEnabled() { - if (scaleInfo.Value.Origin == ScaleOrigin.PlayfieldCentre) + if (scaleInfo.Value.Origin == ScaleOrigin.GridCentre) { toggleAxisAvailable(xCheckBox.Current, true); toggleAxisAvailable(yCheckBox.Current, true); @@ -181,11 +181,11 @@ namespace osu.Game.Rulesets.Osu.Edit updateAxisCheckBoxesEnabled(); } - private Vector2? getOriginPosition(PreciseScaleInfo scale) => scale.Origin == ScaleOrigin.PlayfieldCentre ? gridToolbox.StartPosition.Value : null; + private Vector2? getOriginPosition(PreciseScaleInfo scale) => scale.Origin == ScaleOrigin.GridCentre ? gridToolbox.StartPosition.Value : null; private Axes getAdjustAxis(PreciseScaleInfo scale) => scale.XAxis ? scale.YAxis ? Axes.Both : Axes.X : Axes.Y; - private float getRotation(PreciseScaleInfo scale) => scale.Origin == ScaleOrigin.PlayfieldCentre ? gridToolbox.GridLinesRotation.Value : 0; + private float getRotation(PreciseScaleInfo scale) => scale.Origin == ScaleOrigin.GridCentre ? gridToolbox.GridLinesRotation.Value : 0; private void setAxis(bool x, bool y) { @@ -210,7 +210,7 @@ namespace osu.Game.Rulesets.Osu.Edit public enum ScaleOrigin { - PlayfieldCentre, + GridCentre, SelectionCentre } From a80e3337860773790c88010a7592219984a31570 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 14 Jul 2024 17:27:04 +0200 Subject: [PATCH 17/62] add playfield origin as third origin option --- .../Edit/PreciseRotationPopover.cs | 17 ++++++++++++- .../Edit/PreciseScalePopover.cs | 24 ++++++++++++++++--- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs index 4a1ccc4b61..352debf500 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.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 System; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -8,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Edit.Components.RadioButtons; using osu.Game.Screens.Edit.Compose.Components; using osuTK; @@ -63,6 +65,9 @@ namespace osu.Game.Rulesets.Osu.Edit new RadioButton("Grid centre", () => rotationInfo.Value = rotationInfo.Value with { Origin = RotationOrigin.GridCentre }, () => new SpriteIcon { Icon = FontAwesome.Regular.PlusSquare }), + new RadioButton("Playfield centre", + () => rotationInfo.Value = rotationInfo.Value with { Origin = RotationOrigin.PlayfieldCentre }, + () => new SpriteIcon { Icon = FontAwesome.Regular.Square }), selectionCentreButton = new RadioButton("Selection centre", () => rotationInfo.Value = rotationInfo.Value with { Origin = RotationOrigin.SelectionCentre }, () => new SpriteIcon { Icon = FontAwesome.Solid.VectorSquare }) @@ -95,10 +100,19 @@ namespace osu.Game.Rulesets.Osu.Edit rotationInfo.BindValueChanged(rotation => { - rotationHandler.Update(rotation.NewValue.Degrees, rotation.NewValue.Origin == RotationOrigin.GridCentre ? gridToolbox.StartPosition.Value : null); + rotationHandler.Update(rotation.NewValue.Degrees, getOriginPosition(rotation.NewValue)); }); } + private Vector2? getOriginPosition(PreciseRotationInfo rotation) => + rotation.Origin switch + { + RotationOrigin.GridCentre => gridToolbox.StartPosition.Value, + RotationOrigin.PlayfieldCentre => OsuPlayfield.BASE_SIZE / 2, + RotationOrigin.SelectionCentre => null, + _ => throw new ArgumentOutOfRangeException(nameof(rotation)) + }; + protected override void PopIn() { base.PopIn(); @@ -117,6 +131,7 @@ namespace osu.Game.Rulesets.Osu.Edit public enum RotationOrigin { GridCentre, + PlayfieldCentre, SelectionCentre } diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index 15ed4c59c3..dff370d259 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Edit.Components.RadioButtons; using osuTK; @@ -27,6 +28,7 @@ namespace osu.Game.Rulesets.Osu.Edit private BindableNumber scaleInputBindable = null!; private EditorRadioButtonCollection scaleOrigin = null!; + private RadioButton gridCentreButton = null!; private RadioButton playfieldCentreButton = null!; private RadioButton selectionCentreButton = null!; @@ -68,9 +70,12 @@ namespace osu.Game.Rulesets.Osu.Edit RelativeSizeAxes = Axes.X, Items = new[] { - playfieldCentreButton = new RadioButton("Grid centre", + gridCentreButton = new RadioButton("Grid centre", () => setOrigin(ScaleOrigin.GridCentre), () => new SpriteIcon { Icon = FontAwesome.Regular.PlusSquare }), + playfieldCentreButton = new RadioButton("Playfield centre", + () => setOrigin(ScaleOrigin.PlayfieldCentre), + () => new SpriteIcon { Icon = FontAwesome.Regular.Square }), selectionCentreButton = new RadioButton("Selection centre", () => setOrigin(ScaleOrigin.SelectionCentre), () => new SpriteIcon { Icon = FontAwesome.Solid.VectorSquare }) @@ -99,6 +104,10 @@ namespace osu.Game.Rulesets.Osu.Edit }, } }; + gridCentreButton.Selected.DisabledChanged += isDisabled => + { + gridCentreButton.TooltipText = isDisabled ? "The current selection cannot be scaled relative to grid centre." : string.Empty; + }; playfieldCentreButton.Selected.DisabledChanged += isDisabled => { playfieldCentreButton.TooltipText = isDisabled ? "The current selection cannot be scaled relative to playfield centre." : string.Empty; @@ -125,6 +134,7 @@ namespace osu.Game.Rulesets.Osu.Edit selectionCentreButton.Selected.Disabled = !(scaleHandler.CanScaleX.Value || scaleHandler.CanScaleY.Value); playfieldCentreButton.Selected.Disabled = scaleHandler.IsScalingSlider.Value && !selectionCentreButton.Selected.Disabled; + gridCentreButton.Selected.Disabled = playfieldCentreButton.Selected.Disabled; scaleOrigin.Items.First(b => !b.Selected.Disabled).Select(); @@ -137,7 +147,7 @@ namespace osu.Game.Rulesets.Osu.Edit private void updateAxisCheckBoxesEnabled() { - if (scaleInfo.Value.Origin == ScaleOrigin.GridCentre) + if (scaleInfo.Value.Origin != ScaleOrigin.SelectionCentre) { toggleAxisAvailable(xCheckBox.Current, true); toggleAxisAvailable(yCheckBox.Current, true); @@ -181,7 +191,14 @@ namespace osu.Game.Rulesets.Osu.Edit updateAxisCheckBoxesEnabled(); } - private Vector2? getOriginPosition(PreciseScaleInfo scale) => scale.Origin == ScaleOrigin.GridCentre ? gridToolbox.StartPosition.Value : null; + private Vector2? getOriginPosition(PreciseScaleInfo scale) => + scale.Origin switch + { + ScaleOrigin.GridCentre => gridToolbox.StartPosition.Value, + ScaleOrigin.PlayfieldCentre => OsuPlayfield.BASE_SIZE / 2, + ScaleOrigin.SelectionCentre => null, + _ => throw new ArgumentOutOfRangeException(nameof(scale)) + }; private Axes getAdjustAxis(PreciseScaleInfo scale) => scale.XAxis ? scale.YAxis ? Axes.Both : Axes.X : Axes.Y; @@ -211,6 +228,7 @@ namespace osu.Game.Rulesets.Osu.Edit public enum ScaleOrigin { GridCentre, + PlayfieldCentre, SelectionCentre } From 2bbaa8e43ccce0a1bf42f2c5790ad469a81a195e Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 14 Jul 2024 18:12:55 +0200 Subject: [PATCH 18/62] make flips grid-type aware --- .../Edit/OsuSelectionHandler.cs | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 7d6ef66909..1334dbdbec 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -107,10 +107,28 @@ namespace osu.Game.Rulesets.Osu.Edit // If we're flipping over the origin, we take the grid origin position from the grid toolbox. var flipQuad = flipOverOrigin ? new Quad(gridToolbox.StartPositionX.Value, gridToolbox.StartPositionY.Value, 0, 0) : GeometryUtils.GetSurroundingQuad(hitObjects); - // If we're flipping over the origin, we take the grid rotation from the grid toolbox. - // We want to normalize the rotation angle to -45 to 45 degrees, so horizontal vs vertical flip is not mixed up by the rotation and it stays intuitive to use. - var flipAxis = flipOverOrigin ? GeometryUtils.RotateVector(Vector2.UnitX, -((gridToolbox.GridLinesRotation.Value + 405) % 90 - 45)) : Vector2.UnitX; - flipAxis = direction == Direction.Vertical ? flipAxis.PerpendicularLeft : flipAxis; + Vector2 flipAxis = direction == Direction.Vertical ? Vector2.UnitY : Vector2.UnitX; + + if (flipOverOrigin) + { + // If we're flipping over the origin, we take one of the axes of the grid. + // Take the axis closest to the direction we want to flip over. + switch (gridToolbox.GridType.Value) + { + case PositionSnapGridType.Square: + flipAxis = GeometryUtils.RotateVector(Vector2.UnitX, -((gridToolbox.GridLinesRotation.Value + 405) % 90 - 45)); + flipAxis = direction == Direction.Vertical ? flipAxis.PerpendicularLeft : flipAxis; + break; + + case PositionSnapGridType.Triangle: + // Hex grid has 3 axes, so you can not directly flip over one of the axes, + // however it's still possible to achieve that flip by combining multiple flips over the other axes. + flipAxis = direction == Direction.Vertical + ? GeometryUtils.RotateVector(Vector2.UnitX, -((gridToolbox.GridLinesRotation.Value + 390) % 60 + 60)) + : GeometryUtils.RotateVector(Vector2.UnitX, -((gridToolbox.GridLinesRotation.Value + 390) % 60 - 60)); + break; + } + } var controlPointFlipQuad = new Quad(); From c18814817b7d5b1f907574b6cca89f4fdc0efa53 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 16 Jul 2024 11:17:54 +0200 Subject: [PATCH 19/62] fix test --- osu.Game.Rulesets.Osu.Tests/Editor/TestScenePreciseRotation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePreciseRotation.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePreciseRotation.cs index 30e0dbbf2e..d14bd1fc87 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePreciseRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePreciseRotation.cs @@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor () => EditorBeatmap.HitObjects.OfType().ElementAt(1).Position, () => Is.EqualTo(OsuPlayfield.BASE_SIZE - new Vector2(200))); - AddStep("change rotation origin", () => getPopover().ChildrenOfType().ElementAt(1).TriggerClick()); + AddStep("change rotation origin", () => getPopover().ChildrenOfType().ElementAt(2).TriggerClick()); AddAssert("first object rotated 90deg around selection centre", () => EditorBeatmap.HitObjects.OfType().ElementAt(0).Position, () => Is.EqualTo(new Vector2(200, 200))); AddAssert("second object rotated 90deg around selection centre", From 7dc006f9bab4f22e326c5692a40a2afc5bfdc566 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 16 Jul 2024 13:19:01 +0200 Subject: [PATCH 20/62] fix horizontal flip rotation --- osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 1334dbdbec..2dc43deee1 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -125,7 +125,7 @@ namespace osu.Game.Rulesets.Osu.Edit // however it's still possible to achieve that flip by combining multiple flips over the other axes. flipAxis = direction == Direction.Vertical ? GeometryUtils.RotateVector(Vector2.UnitX, -((gridToolbox.GridLinesRotation.Value + 390) % 60 + 60)) - : GeometryUtils.RotateVector(Vector2.UnitX, -((gridToolbox.GridLinesRotation.Value + 390) % 60 - 60)); + : GeometryUtils.RotateVector(Vector2.UnitX, -((gridToolbox.GridLinesRotation.Value + 360) % 60 - 30)); break; } } From 0a9b11d3a76445cbf56cba4f367964340df91e2a Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Mon, 5 Aug 2024 15:57:02 +0300 Subject: [PATCH 21/62] removed default difficulty multiplier --- osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs | 2 +- .../Difficulty/Skills/Flashlight.cs | 4 ++-- .../Difficulty/Skills/OsuStrainSkill.cs | 12 +----------- osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 3 +-- 4 files changed, 5 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index 3f6b22bbb1..f0be2440c1 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills private double currentStrain; - private double skillMultiplier => 23.55; + private double skillMultiplier => 23.55 * 1.06; private double strainDecayBase => 0.15; private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000); diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 3d6d3f99c1..8caaae665a 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills hasHiddenMod = mods.Any(m => m is OsuModHidden); } - private double skillMultiplier => 0.052; + private double skillMultiplier => 0.052 * 1.06; private double strainDecayBase => 0.15; private double currentStrain; @@ -41,6 +41,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills return currentStrain; } - public override double DifficultyValue() => GetCurrentStrainPeaks().Sum() * OsuStrainSkill.DEFAULT_DIFFICULTY_MULTIPLIER; + public override double DifficultyValue() => GetCurrentStrainPeaks().Sum(); } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs index 4a6328010b..d7ceb63d36 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs @@ -12,12 +12,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills { public abstract class OsuStrainSkill : StrainSkill { - /// - /// The default multiplier applied by to the final difficulty value after all other calculations. - /// May be overridden via . - /// - public const double DEFAULT_DIFFICULTY_MULTIPLIER = 1.06; - /// /// The number of sections with the highest strains, which the peak strain reductions will apply to. /// This is done in order to decrease their impact on the overall difficulty of the map for this skill. @@ -29,10 +23,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills /// protected virtual double ReducedStrainBaseline => 0.75; - /// - /// The final multiplier to be applied to after all other calculations. - /// - protected virtual double DifficultyMultiplier => DEFAULT_DIFFICULTY_MULTIPLIER; protected OsuStrainSkill(Mod[] mods) : base(mods) @@ -65,7 +55,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills weight *= DecayWeight; } - return difficulty * DifficultyMultiplier; + return difficulty; } } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 40aac013ab..f54f135f63 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -16,14 +16,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills /// public class Speed : OsuStrainSkill { - private double skillMultiplier => 1375; + private double skillMultiplier => 1375 * 1.04; private double strainDecayBase => 0.3; private double currentStrain; private double currentRhythm; protected override int ReducedSectionCount => 5; - protected override double DifficultyMultiplier => 1.04; private readonly List objectStrains = new List(); From 8431e62c470dbd1e71a27e0e22e27282344b9255 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Mon, 5 Aug 2024 16:14:32 +0300 Subject: [PATCH 22/62] fixed CI --- osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs index d7ceb63d36..c007c1abd2 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs @@ -23,7 +23,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills /// protected virtual double ReducedStrainBaseline => 0.75; - protected OsuStrainSkill(Mod[] mods) : base(mods) { From a28913af7a332737a06089fdf99826660f31e702 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Tue, 6 Aug 2024 14:47:05 +0300 Subject: [PATCH 23/62] multiplied numbers in multipliers --- osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs | 2 +- osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs | 2 +- osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index f0be2440c1..1fbe03395c 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills private double currentStrain; - private double skillMultiplier => 23.55 * 1.06; + private double skillMultiplier => 24.963; private double strainDecayBase => 0.15; private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000); diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 8caaae665a..9ca6a35d3d 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills hasHiddenMod = mods.Any(m => m is OsuModHidden); } - private double skillMultiplier => 0.052 * 1.06; + private double skillMultiplier => 0.05512; private double strainDecayBase => 0.15; private double currentStrain; diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index f54f135f63..93e6e2d1e4 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills /// public class Speed : OsuStrainSkill { - private double skillMultiplier => 1375 * 1.04; + private double skillMultiplier => 1430; private double strainDecayBase => 0.3; private double currentStrain; From b18706274784430b26526fc4b3eecb75c8ef058e Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 13 Aug 2024 12:58:52 +0200 Subject: [PATCH 24/62] clarify meaning of flip axis vector --- osu.Game/Utils/GeometryUtils.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs index 5a8ca9722e..810eda9950 100644 --- a/osu.Game/Utils/GeometryUtils.cs +++ b/osu.Game/Utils/GeometryUtils.cs @@ -51,6 +51,9 @@ namespace osu.Game.Utils /// Given a flip direction, a surrounding quad for all selected objects, and a position, /// will return the flipped position in screen space coordinates. /// + /// The direction to flip towards. + /// The quad surrounding all selected objects. The center of this determines the position of the axis. + /// The position to flip. public static Vector2 GetFlippedPosition(Direction direction, Quad quad, Vector2 position) { var centre = quad.Centre; @@ -73,6 +76,9 @@ namespace osu.Game.Utils /// Given a flip axis vector, a surrounding quad for all selected objects, and a position, /// will return the flipped position in screen space coordinates. /// + /// The vector indicating the direction to flip towards. This is perpendicular to the mirroring axis. + /// The quad surrounding all selected objects. The center of this determines the position of the axis. + /// The position to flip. public static Vector2 GetFlippedPosition(Vector2 axis, Quad quad, Vector2 position) { var centre = quad.Centre; From ae47671d17e6322a688f24a090020f6188539c1e Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 13 Aug 2024 14:21:42 +0200 Subject: [PATCH 25/62] clarify angle ranges in HandleFlip --- osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index da98da5238..bac0a5e273 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -138,15 +138,17 @@ namespace osu.Game.Rulesets.Osu.Edit switch (gridToolbox.GridType.Value) { case PositionSnapGridType.Square: - flipAxis = GeometryUtils.RotateVector(Vector2.UnitX, -((gridToolbox.GridLinesRotation.Value + 405) % 90 - 45)); + flipAxis = GeometryUtils.RotateVector(Vector2.UnitX, -((gridToolbox.GridLinesRotation.Value + 360 + 45) % 90 - 45)); flipAxis = direction == Direction.Vertical ? flipAxis.PerpendicularLeft : flipAxis; break; case PositionSnapGridType.Triangle: // Hex grid has 3 axes, so you can not directly flip over one of the axes, // however it's still possible to achieve that flip by combining multiple flips over the other axes. + // Angle degree range for vertical = (-120, -60] + // Angle degree range for horizontal = [-30, 30) flipAxis = direction == Direction.Vertical - ? GeometryUtils.RotateVector(Vector2.UnitX, -((gridToolbox.GridLinesRotation.Value + 390) % 60 + 60)) + ? GeometryUtils.RotateVector(Vector2.UnitX, -((gridToolbox.GridLinesRotation.Value + 360 + 30) % 60 + 60)) : GeometryUtils.RotateVector(Vector2.UnitX, -((gridToolbox.GridLinesRotation.Value + 360) % 60 - 30)); break; } From 09ca190b8ddb44d27b16763f6a6c19d71332e001 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 21 Aug 2024 00:10:15 +0200 Subject: [PATCH 26/62] re-implement ConvexHull 100% original --- osu.Game/Utils/GeometryUtils.cs | 39 +++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs index 810eda9950..d4c1dc2db7 100644 --- a/osu.Game/Utils/GeometryUtils.cs +++ b/osu.Game/Utils/GeometryUtils.cs @@ -154,33 +154,38 @@ namespace osu.Game.Utils { List p = points.ToList(); - if (p.Count <= 1) + if (p.Count < 3) return p; - int n = p.Count, k = 0; - List hull = new List(new Vector2[2 * n]); - p.Sort((a, b) => a.X == b.X ? a.Y.CompareTo(b.Y) : a.X.CompareTo(b.X)); - // Build lower hull - for (int i = 0; i < n; ++i) + List upper = new List(); + List lower = new List(); + + // Build the lower hull + for (int i = 0; i < p.Count; i++) { - while (k >= 2 && cross(hull[k - 2], hull[k - 1], p[i]) <= 0) - k--; - hull[k] = p[i]; - k++; + while (lower.Count >= 2 && cross(lower[^2], lower[^1], p[i]) <= 0) + lower.RemoveAt(lower.Count - 1); + + lower.Add(p[i]); } - // Build upper hull - for (int i = n - 2, t = k + 1; i >= 0; i--) + // Build the upper hull + for (int i = p.Count - 1; i >= 0; i--) { - while (k >= t && cross(hull[k - 2], hull[k - 1], p[i]) <= 0) - k--; - hull[k] = p[i]; - k++; + while (upper.Count >= 2 && cross(upper[^2], upper[^1], p[i]) <= 0) + upper.RemoveAt(upper.Count - 1); + + upper.Add(p[i]); } - return hull.Take(k - 1).ToList(); + // Remove the last point of each half because it's a duplicate of the first point of the other half + lower.RemoveAt(lower.Count - 1); + upper.RemoveAt(upper.Count - 1); + + lower.AddRange(upper); + return lower; float cross(Vector2 o, Vector2 a, Vector2 b) => (a.X - o.X) * (b.Y - o.Y) - (a.Y - o.Y) * (b.X - o.X); } From ff3bffc7d9e0e955afe3ad3ad498c2d3259e6877 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 21 Aug 2024 00:30:57 +0200 Subject: [PATCH 27/62] add test --- osu.Game.Tests/Utils/GeometryUtilsTest.cs | 33 +++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 osu.Game.Tests/Utils/GeometryUtilsTest.cs diff --git a/osu.Game.Tests/Utils/GeometryUtilsTest.cs b/osu.Game.Tests/Utils/GeometryUtilsTest.cs new file mode 100644 index 0000000000..ded4656ac1 --- /dev/null +++ b/osu.Game.Tests/Utils/GeometryUtilsTest.cs @@ -0,0 +1,33 @@ +// 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.Game.Utils; +using osuTK; + +namespace osu.Game.Tests.Utils +{ + [TestFixture] + public class GeometryUtilsTest + { + [TestCase(new int[] { }, new int[] { })] + [TestCase(new[] { 0, 0 }, new[] { 0, 0 })] + [TestCase(new[] { 0, 0, 1, 1, 1, -1, 2, 0 }, new[] { 0, 0, 1, 1, 2, 0, 1, -1 })] + [TestCase(new[] { 0, 0, 1, 1, 1, -1, 2, 0, 1, 0 }, new[] { 0, 0, 1, 1, 2, 0, 1, -1 })] + [TestCase(new[] { 0, 0, 1, 1, 2, -1, 2, 0, 1, 0, 4, 10 }, new[] { 0, 0, 4, 10, 2, -1 })] + public void TestConvexHull(int[] values, int[] expected) + { + var points = new Vector2[values.Length / 2]; + for (int i = 0; i < values.Length; i += 2) + points[i / 2] = new Vector2(values[i], values[i + 1]); + + var expectedPoints = new Vector2[expected.Length / 2]; + for (int i = 0; i < expected.Length; i += 2) + expectedPoints[i / 2] = new Vector2(expected[i], expected[i + 1]); + + var hull = GeometryUtils.GetConvexHull(points); + + Assert.That(hull, Is.EquivalentTo(expectedPoints)); + } + } +} From 2c19b7994c70a3b1d0799add0b1018bf9ad7fa6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 6 Sep 2024 11:38:50 +0200 Subject: [PATCH 28/62] Implement "form" check box control --- .../UserInterface/TestSceneFormControls.cs | 16 +- .../Graphics/UserInterfaceV2/FormCheckBox.cs | 155 ++++++++++++++++++ 2 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Graphics/UserInterfaceV2/FormCheckBox.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs index f5bc40c869..9c05a34010 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Localisation; using osuTK; namespace osu.Game.Tests.Visual.UserInterface @@ -53,9 +54,22 @@ namespace osu.Game.Tests.Visual.UserInterface PlaceholderText = "Mine is 42!", TabbableContentContainer = this, }, + new FormCheckBox + { + Caption = EditorSetupStrings.LetterboxDuringBreaks, + HintText = EditorSetupStrings.LetterboxDuringBreaksDescription, + OnText = "Letterbox", + OffText = "Do not letterbox", + }, + new FormCheckBox + { + Caption = EditorSetupStrings.LetterboxDuringBreaks, + HintText = EditorSetupStrings.LetterboxDuringBreaksDescription, + Current = { Disabled = true }, + }, }, }, - }, + } }; } } diff --git a/osu.Game/Graphics/UserInterfaceV2/FormCheckBox.cs b/osu.Game/Graphics/UserInterfaceV2/FormCheckBox.cs new file mode 100644 index 0000000000..587aa921f5 --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/FormCheckBox.cs @@ -0,0 +1,155 @@ +// 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.Audio; +using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; +using osu.Framework.Localisation; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + public partial class FormCheckBox : CompositeDrawable, IHasCurrentValue + { + public Bindable Current + { + get => current.Current; + set => current.Current = value; + } + + private readonly BindableWithCurrent current = new BindableWithCurrent(); + + public LocalisableString Caption { get; init; } + public LocalisableString HintText { get; init; } + public LocalisableString OnText { get; init; } = "On"; + public LocalisableString OffText { get; init; } = "Off"; + + private Box background = null!; + private FormFieldCaption caption = null!; + private OsuSpriteText text = null!; + private Nub checkbox = null!; + + private Sample? sampleChecked; + private Sample? sampleUnchecked; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + RelativeSizeAxes = Axes.X; + Height = 50; + + Masking = true; + CornerRadius = 5; + CornerExponent = 2.5f; + + InternalChildren = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background5, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(9), + Children = new Drawable[] + { + caption = new FormFieldCaption + { + Caption = Caption, + TooltipText = HintText, + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + }, + text = new OsuSpriteText + { + RelativeSizeAxes = Axes.X, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + }, + checkbox = new Nub + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Current = Current, + } + }, + }, + }; + + sampleChecked = audio.Samples.Get(@"UI/check-on"); + sampleUnchecked = audio.Samples.Get(@"UI/check-off"); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + current.BindValueChanged(_ => + { + updateState(); + playSamples(); + background.FlashColour(ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark2), 800, Easing.OutQuint); + }); + current.BindDisabledChanged(_ => updateState(), true); + } + + private void playSamples() + { + if (Current.Value) + sampleChecked?.Play(); + else + sampleUnchecked?.Play(); + } + + protected override bool OnHover(HoverEvent e) + { + updateState(); + return true; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + updateState(); + } + + protected override bool OnClick(ClickEvent e) + { + if (!Current.Disabled) + Current.Value = !Current.Value; + return true; + } + + private void updateState() + { + background.Colour = Current.Disabled ? colourProvider.Background4 : colourProvider.Background5; + caption.Colour = Current.Disabled ? colourProvider.Foreground1 : colourProvider.Content2; + checkbox.Colour = Current.Disabled ? colourProvider.Foreground1 : colourProvider.Content1; + text.Colour = Current.Disabled ? colourProvider.Foreground1 : colourProvider.Content1; + + text.Text = Current.Value ? OnText : OffText; + + if (!Current.Disabled) + { + BorderThickness = IsHovered ? 2 : 0; + + if (IsHovered) + BorderColour = colourProvider.Light4; + } + } + } +} From 4ff72c5331c3f1d4007f08d5defa2ea0131cb94c Mon Sep 17 00:00:00 2001 From: smallketchup82 Date: Mon, 9 Sep 2024 03:04:16 -0400 Subject: [PATCH 29/62] Add beatmap icon to windows beatmap files --- osu.Desktop/Windows/Icons.cs | 2 ++ .../Windows/WindowsAssociationManager.cs | 4 ++-- osu.Desktop/beatmap.ico | Bin 0 -> 59403 bytes 3 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 osu.Desktop/beatmap.ico diff --git a/osu.Desktop/Windows/Icons.cs b/osu.Desktop/Windows/Icons.cs index 67915c101a..9d37a21b49 100644 --- a/osu.Desktop/Windows/Icons.cs +++ b/osu.Desktop/Windows/Icons.cs @@ -13,5 +13,7 @@ namespace osu.Desktop.Windows private static readonly string icon_directory = Path.GetDirectoryName(typeof(Icons).Assembly.Location)!; public static string Lazer => Path.Join(icon_directory, "lazer.ico"); + + public static string Beatmap => Path.Join(icon_directory, "beatmap.ico"); } } diff --git a/osu.Desktop/Windows/WindowsAssociationManager.cs b/osu.Desktop/Windows/WindowsAssociationManager.cs index b32c01433d..92cffd0987 100644 --- a/osu.Desktop/Windows/WindowsAssociationManager.cs +++ b/osu.Desktop/Windows/WindowsAssociationManager.cs @@ -40,8 +40,8 @@ namespace osu.Desktop.Windows private static readonly FileAssociation[] file_associations = { - new FileAssociation(@".osz", WindowsAssociationManagerStrings.OsuBeatmap, Icons.Lazer), - new FileAssociation(@".olz", WindowsAssociationManagerStrings.OsuBeatmap, Icons.Lazer), + new FileAssociation(@".osz", WindowsAssociationManagerStrings.OsuBeatmap, Icons.Beatmap), + new FileAssociation(@".olz", WindowsAssociationManagerStrings.OsuBeatmap, Icons.Beatmap), new FileAssociation(@".osr", WindowsAssociationManagerStrings.OsuReplay, Icons.Lazer), new FileAssociation(@".osk", WindowsAssociationManagerStrings.OsuSkin, Icons.Lazer), }; diff --git a/osu.Desktop/beatmap.ico b/osu.Desktop/beatmap.ico new file mode 100644 index 0000000000000000000000000000000000000000..410ccfd73d6e42edbf3fc6ccc01bc9e00d6cd9a9 GIT binary patch literal 59403 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 literal 0 HcmV?d00001 From 652a59061164dadb2db1302d9c896e5cefecdec9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 13 Sep 2024 09:59:20 +0200 Subject: [PATCH 30/62] Attempt to address design concerns --- .../UserInterface/TestSceneFormControls.cs | 2 -- .../Graphics/UserInterfaceV2/FormCheckBox.cs | 33 +++++++++++++------ 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs index 9c05a34010..be2ba860d3 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs @@ -58,8 +58,6 @@ namespace osu.Game.Tests.Visual.UserInterface { Caption = EditorSetupStrings.LetterboxDuringBreaks, HintText = EditorSetupStrings.LetterboxDuringBreaksDescription, - OnText = "Letterbox", - OffText = "Do not letterbox", }, new FormCheckBox { diff --git a/osu.Game/Graphics/UserInterfaceV2/FormCheckBox.cs b/osu.Game/Graphics/UserInterfaceV2/FormCheckBox.cs index 587aa921f5..6054e898fe 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FormCheckBox.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FormCheckBox.cs @@ -14,7 +14,9 @@ using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Localisation; using osu.Game.Overlays; +using osuTK; namespace osu.Game.Graphics.UserInterfaceV2 { @@ -30,8 +32,6 @@ namespace osu.Game.Graphics.UserInterfaceV2 public LocalisableString Caption { get; init; } public LocalisableString HintText { get; init; } - public LocalisableString OnText { get; init; } = "On"; - public LocalisableString OffText { get; init; } = "Off"; private Box background = null!; private FormFieldCaption caption = null!; @@ -74,17 +74,30 @@ namespace osu.Game.Graphics.UserInterfaceV2 Anchor = Anchor.TopLeft, Origin = Anchor.TopLeft, }, - text = new OsuSpriteText + new FillFlowContainer { RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - }, - checkbox = new Nub - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Current = Current, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(7), + Children = new Drawable[] + { + checkbox = new Nub + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Current = Current, + Margin = new MarginPadding { Top = 2, }, + }, + text = new OsuSpriteText + { + RelativeSizeAxes = Axes.X, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + } } }, }, @@ -141,7 +154,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 checkbox.Colour = Current.Disabled ? colourProvider.Foreground1 : colourProvider.Content1; text.Colour = Current.Disabled ? colourProvider.Foreground1 : colourProvider.Content1; - text.Text = Current.Value ? OnText : OffText; + text.Text = Current.Value ? CommonStrings.Enabled : CommonStrings.Disabled; if (!Current.Disabled) { From 929ea87975520450ead9e385d9560d105c2f1063 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 13 Sep 2024 14:53:59 +0200 Subject: [PATCH 31/62] Revert to checkbox on right --- .../Graphics/UserInterfaceV2/FormCheckBox.cs | 30 +++++-------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/osu.Game/Graphics/UserInterfaceV2/FormCheckBox.cs b/osu.Game/Graphics/UserInterfaceV2/FormCheckBox.cs index 6054e898fe..797ff09800 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FormCheckBox.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FormCheckBox.cs @@ -16,7 +16,6 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; using osu.Game.Overlays; -using osuTK; namespace osu.Game.Graphics.UserInterfaceV2 { @@ -74,31 +73,18 @@ namespace osu.Game.Graphics.UserInterfaceV2 Anchor = Anchor.TopLeft, Origin = Anchor.TopLeft, }, - new FillFlowContainer + text = new OsuSpriteText { RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(7), - Children = new Drawable[] - { - checkbox = new Nub - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Current = Current, - Margin = new MarginPadding { Top = 2, }, - }, - text = new OsuSpriteText - { - RelativeSizeAxes = Axes.X, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - }, - } - } + }, + checkbox = new Nub + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Current = Current, + }, }, }, }; From f71ce8869e8cccd50651f3cbe52355093b0c09e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 13 Sep 2024 14:54:07 +0200 Subject: [PATCH 32/62] Limit width of test scene controls To better reflect what the widths should be in actual usage. --- osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs index be2ba860d3..eb8a8b3fe9 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs @@ -26,7 +26,10 @@ namespace osu.Game.Tests.Visual.UserInterface RelativeSizeAxes = Axes.Both, Child = new FillFlowContainer { - RelativeSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.Y, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 400, Direction = FillDirection.Vertical, Spacing = new Vector2(5), Padding = new MarginPadding(10), From a4f6d4a300e2da5ba12d271965272f15634b8437 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 13 Sep 2024 15:58:41 +0200 Subject: [PATCH 33/62] Backpopulate missing ranked/submitted dates using new local metadata cache People keep asking why https://github.com/ppy/osu/pull/29553 didn't fix their databases (as stated in the PR, it didn't intend to), so this should do it for them. --- .../LocalCachedBeatmapMetadataSource.cs | 17 ++- .../Database/BackgroundDataStoreProcessor.cs | 104 ++++++++++++++++++ 2 files changed, 119 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/LocalCachedBeatmapMetadataSource.cs b/osu.Game/Beatmaps/LocalCachedBeatmapMetadataSource.cs index 96817571f6..eaa4d8ebfb 100644 --- a/osu.Game/Beatmaps/LocalCachedBeatmapMetadataSource.cs +++ b/osu.Game/Beatmaps/LocalCachedBeatmapMetadataSource.cs @@ -3,6 +3,7 @@ using System; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Threading.Tasks; using Microsoft.Data.Sqlite; @@ -78,7 +79,7 @@ namespace osu.Game.Beatmaps // cached database exists on disk. && storage.Exists(cache_database_name); - public bool TryLookup(BeatmapInfo beatmapInfo, out OnlineBeatmapMetadata? onlineMetadata) + public bool TryLookup(BeatmapInfo beatmapInfo, [NotNullWhen(true)] out OnlineBeatmapMetadata? onlineMetadata) { Debug.Assert(beatmapInfo.BeatmapSet != null); @@ -98,7 +99,7 @@ namespace osu.Game.Beatmaps try { - using (var db = new SqliteConnection(string.Concat(@"Data Source=", storage.GetFullPath(@"online.db", true)))) + using (var db = getConnection()) { db.Open(); @@ -125,6 +126,9 @@ namespace osu.Game.Beatmaps return false; } + private SqliteConnection getConnection() => + new SqliteConnection(string.Concat(@"Data Source=", storage.GetFullPath(@"online.db", true))); + private void prepareLocalCache() { bool isRefetch = storage.Exists(cache_database_name); @@ -191,6 +195,15 @@ namespace osu.Game.Beatmaps }); } + public int GetCacheVersion() + { + using (var connection = getConnection()) + { + connection.Open(); + return getCacheVersion(connection); + } + } + private int getCacheVersion(SqliteConnection connection) { using (var cmd = connection.CreateCommand()) diff --git a/osu.Game/Database/BackgroundDataStoreProcessor.cs b/osu.Game/Database/BackgroundDataStoreProcessor.cs index 16ff766ea4..59ef9a3ae1 100644 --- a/osu.Game/Database/BackgroundDataStoreProcessor.cs +++ b/osu.Game/Database/BackgroundDataStoreProcessor.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -11,6 +12,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Logging; +using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.Extensions; using osu.Game.Online.API; @@ -61,6 +63,9 @@ namespace osu.Game.Database [Resolved] private IAPIProvider api { get; set; } = null!; + [Resolved] + private Storage storage { get; set; } = null!; + protected virtual int TimeToSleepDuringGameplay => 30000; protected override void LoadComplete() @@ -78,6 +83,7 @@ namespace osu.Game.Database processScoresWithMissingStatistics(); convertLegacyTotalScoreToStandardised(); upgradeScoreRanks(); + backpopulateMissingSubmissionAndRankDates(); }, TaskCreationOptions.LongRunning).ContinueWith(t => { if (t.Exception?.InnerException is ObjectDisposedException) @@ -443,6 +449,104 @@ namespace osu.Game.Database completeNotification(notification, processedCount, scoreIds.Count, failedCount); } + private void backpopulateMissingSubmissionAndRankDates() + { + var localMetadataSource = new LocalCachedBeatmapMetadataSource(storage); + + if (!localMetadataSource.Available) + { + Logger.Log("Cannot backpopulate missing submission/rank dates because the local metadata cache is missing."); + return; + } + + try + { + if (localMetadataSource.GetCacheVersion() < 2) + { + Logger.Log("Cannot backpopulate missing submission/rank dates because the local metadata cache is too old."); + return; + } + } + catch (Exception ex) + { + Logger.Log($"Error when trying to query version of local metadata cache: {ex}"); + return; + } + + Logger.Log("Querying for beatmap sets that contain missing submission/rank date..."); + + HashSet beatmapSetIds = realmAccess.Run(r => new HashSet( + r.All() + .Where(b => b.StatusInt > 0 && (b.DateRanked == null || b.DateSubmitted == null)) + .AsEnumerable() + .Select(b => b.ID))); + + Logger.Log($"Found {beatmapSetIds.Count} beatmap sets with missing submission/rank date."); + + if (beatmapSetIds.Count == 0) + return; + + var notification = showProgressNotification(beatmapSetIds.Count, "Populating missing submission and rank dates", "beatmap sets now have correct submission and rank dates."); + + int processedCount = 0; + int failedCount = 0; + + foreach (var id in beatmapSetIds) + { + if (notification?.State == ProgressNotificationState.Cancelled) + break; + + updateNotificationProgress(notification, processedCount, beatmapSetIds.Count); + + sleepIfRequired(); + + try + { + // Can't use async overload because we're not on the update thread. + // ReSharper disable once MethodHasAsyncOverload + bool succeeded = realmAccess.Write(r => + { + BeatmapSetInfo beatmapSet = r.Find(id)!; + + // we want any ranked representative of the set. + // the reason for checking ranked status of the difficulty is that it can be locally modified, + // at which point the lookup will fail - but there might still be another unmodified difficulty on which it will work. + if (beatmapSet.Beatmaps.FirstOrDefault(b => b.Status >= BeatmapOnlineStatus.Ranked) is not BeatmapInfo beatmap) + return false; + + bool lookupSucceeded = localMetadataSource.TryLookup(beatmap, out var result); + + if (lookupSucceeded) + { + Debug.Assert(result != null); + beatmapSet.DateRanked = result.DateRanked; + beatmapSet.DateSubmitted = result.DateSubmitted; + return true; + } + + Logger.Log($"Could not find {beatmapSet.GetDisplayString()} in local cache while backpopulating missing submission/rank date"); + return false; + }); + + if (succeeded) + ++processedCount; + else + ++failedCount; + } + catch (ObjectDisposedException) + { + throw; + } + catch (Exception e) + { + Logger.Log($"Failed to update ranked/submitted dates for beatmap set {id}: {e}"); + ++failedCount; + } + } + + completeNotification(notification, processedCount, beatmapSetIds.Count, failedCount); + } + private void updateNotificationProgress(ProgressNotification? notification, int processedCount, int totalCount) { if (notification == null) From 562a5006eae0d9f964d7891daa0f203834e0dc3e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 14 Sep 2024 02:19:52 +0900 Subject: [PATCH 34/62] Change log output to only output when matches are found (in line with other methods) --- osu.Game/Database/BackgroundDataStoreProcessor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/BackgroundDataStoreProcessor.cs b/osu.Game/Database/BackgroundDataStoreProcessor.cs index 59ef9a3ae1..0fa785e494 100644 --- a/osu.Game/Database/BackgroundDataStoreProcessor.cs +++ b/osu.Game/Database/BackgroundDataStoreProcessor.cs @@ -481,11 +481,11 @@ namespace osu.Game.Database .AsEnumerable() .Select(b => b.ID))); - Logger.Log($"Found {beatmapSetIds.Count} beatmap sets with missing submission/rank date."); - if (beatmapSetIds.Count == 0) return; + Logger.Log($"Found {beatmapSetIds.Count} beatmap sets with missing submission/rank date."); + var notification = showProgressNotification(beatmapSetIds.Count, "Populating missing submission and rank dates", "beatmap sets now have correct submission and rank dates."); int processedCount = 0; From 1204136af81596dffdfd7f4a8afc7d31d8788fea Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 14 Sep 2024 02:46:19 +0900 Subject: [PATCH 35/62] Update icon file with new design --- osu.Desktop/beatmap.ico | Bin 59403 -> 356968 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/osu.Desktop/beatmap.ico b/osu.Desktop/beatmap.ico index 410ccfd73d6e42edbf3fc6ccc01bc9e00d6cd9a9..58ab198bc273205c90a73d1efbdb16c0751f811f 100644 GIT binary patch literal 356968 zcmd?S2Y6J~+BUqWr2?UY6af_lY@p{H@CbsUh)O4o&=Ca$rK%_$>Aly45JE?~fOL}3 z34!$9dzqQcWHNo``LBEJy(g0pis$&f|NDRcb6x9NyR5zXUgatG;y4NC&egBaaT2aA zH&M=U+c=JE-kiT*;m&b(Imfka%ikZa&vDD@aa`xl!hL&=d#4S@_3FjncldIN;{SYY^^{SKuA^jOvj?@1r=Q?m4SD)j!w!(8p za*k`{$vcrg`Q#IB{P^*lr>Ccc<2V_|aSD#(T%O|aPNq;OByYd{HrJv>3vMAY#&NQD z-g&$6(&fK(J9s2?;PF#OeNUV^>UaFqQ9t-6P9I~}hmP(av~u;bfB)kj?>EIaBS(&8 z*_b+YYRmNWjBWYp<$0-*%8JwnDt+pGl|Dsq$#<1{_Uxguq9DDzAS*L#&%Aka`J!>0 zBsD#GLVBdeeEhd83Z9Wj>-}PA^N4ub{&ga)^@*l6-j8WxKs>GWc}%+|B~r?ra$9D0 z#!QamP;m;SQn~9!a1o{6)lylmfwqrNpc}!3RG6x#Lvzw--Oza2;G4+& zrBokk+N`mAhfc4%tCgB#7uFY2_}&sKOfk^D8R-;rSw*1RhstQ9Pcm)tOJQ!!pm;jI zD4Wz}+6!a89$D|BULSg>m6{XhRu|Gn?=%YAQ%Yx+7tr1x(kVQogu;W%XtQrRZSm*b zCZ9AqHa~|{rP@nVr%(3!=;Q9}`?D(xY5ky7_I<+j3c7u;jH1q{DJ-~@HV#dr?E%@e zJs^v=`eo7SB}JqztGG06=J#GbSo+!%f37W~ZGO3QYGDaII95*gkCjvGB`saqT0&cV zvT5gtJlZuPk9Gv)(YY0+q*3WE%=~F;-5$OE?V(m`Ph483rkw$Wv}<@F?HZX!`z96B z;pxS6U|Ip)Kcc1Nuu4ja&{J}lo(d98q^qdhfj)rpm#efVF0a>6;P4XKJ))R)4=dT6^r;CLINhP*Tvy zQVJfeqCKOOw0B$?Wks6k>>@P<4p&k{ppLX9=6H3vsyDtpdh{skyIQSw=j}ZP+A~~D zA*0J_-xv)Ak5beAZ^|kAp_$Gt(bBzxm8932@2NFq|HQX}fq|^=;#yOo{yIC-V)=c# zjt+iPN&Cm?m^<=aCFRFhnbTL8FO{l_Iw1Ul1q;}>Ae+s`Wn`u`H0VuRl-bsToG5E$ z=3R>sF8hJiXwcYH7OOe9pfImBzV-3(aeOP1N>j?Eq$RrRjOxxNo36jfrt>q|bp9q= zrC)_k^HF+wdYy_2E!P}_QVqHx7@#xQDA-x31j7 z!$aP%VM7_5r>Cd1dGqFyH{N)ImFFM-_y>z~;J|^L$z(W&A2vg+DKeoT`@K`=-Rbw$At?Q zei6Jkc+|0D$0nUTdE)z%r%pcA9sB**q>zx1(My&r`Ko8no^K&9E-o&vuf6t~q+Y#x z+{u$CIfX(Ylgs4|j~_oiqO`0e%xE;081-hoq0(YD=qwgPMRljIFf+&A>C9%M-fS?L zjAcqy*_{g)E_~ahNs}gWxm^C$S6^{2zx*=l$@Rp^6JHq&hP;9lJzd?MPY32CQ|M1g zbl{gXIxsuc$)!>7^aR>FGm#Gdl1BSyr_la6iFA2q9_7U8$YeB?Ub%8*7{_sL4H`5+ z?~u5-xOkP67KbQvjkM>76xug4l@8BKqvZo5Y4zY3S~ED7Hu%TW#$oZaYG5=i?t7nq zJ|vdle(U>ymiK!^JHJh$!W2EJ%T;Io^{;<5W?hTpB(rACYHcv;A3naIq60r=khauJ zR*Ri3?#QMU{T{Pt8BrCK9j&8%vyxdF8+;QSZtc)GTGsC&tsN3icaN2l$!tvBym>RG zN|yg!yLNRnTTB@dp(S*DaSmBbb~c8`T~o98ADvgSdrz&M%2p4~(Hz z17hjQ_Cm5)EXAi!pZ=E^|KY=j-?doGc{hSeXzPen3JWfw?8lW9e@#Pcha}LopnS61 z_#`qSb+k1gmCYHO{Zbrmqi-^;_$r3ZuP-2r*`m69ENoRP^w)n&Pi&DJMoxsdsd=!8@5Y`=kqV8|F9O zZS~8hi|b3tY_gW$xN+l&{D%jrX}fp^PkMt2!bg;vy&h7zlLJYSJ1gtMJ%4+uM6qG_eFGQS`i(ZUd-IVsYP`6poT0KYjH$G_;3TgMqA_^Q?L^}ftXq#^?dxqybhvm~&-z*B-RZc}ICeoHzNvEuG6=ha3 zRocm9GTl9V_;9l)y^#@@t{*FgkJKL>KhW+j}urm(iZlWfVA~gmw=vrr^;|_W{L} zc*Q_vS!OygNl8J&%P26QjP_4ZQo!{s@OUpa0I^|*gHl=dq=DI`!PzE@9ZcO1&u1DJtLKLbXqxO-ZzoWW-BW%S5FBJ z4tBkD>lW9&dw1^r_upspd}wHBJB!tn{P>iS_Kqs2;87~tJ4#IHZR&A{Af73D;k zDC>cl<^9+!HRZ>e$!53ZDV56MvuFPzKXvL9Unv5i9CPQ+t#2|Lu4X;3Q0O=Mpo1`Q=UckX2IIn$<1n{X>F zFIUetR@m*~+YNMR0@f*&6gsAo_KmKfeWP{k`oPzfl>5j^+F~1N3T$MxS)%gt^SUE~ zMT-_WN9&*t9XhZyA4NTCVKSRenDusRQKF5~!mX5e(L!8x0>LVoSqwR%xeQH>uze%vfNI$Cs1 zuU)%VXUdc*lKJ!JKQk_o$~iCY1}EbdauUHkkN9xH)Wj`h-@0-OIWKM@*M?ijE#Pi& z#AQi{Gs=jq!>}RZ2RmHxE1#PCy_$r&S+QaT*RNkc?qC1g?IbY+M_#QsO<6FehSSFLzb8~ZZ#rpOi|M&;i7g#@X z$j>XUyuzU_U0ht;unv^VCVYRd%wOh=>i~7{vdAvk}=%R;#&OtJNgN#Kdfy zJbChm$b+Y+rz_G#;+HR9=Fp!wj`JKndh|O51qEmAcDv13X`}q4N{YQ!M)%JY(Sy?^ zbniql-F=D+Ka@wej}@{o_fM73y;Fr0bG3|e<8`FhBKu@lDwQ|q&Yk;jj^paqt5;9< z(n~L~HgI)ym80y5iHXaI?6!jBN;4vw0dwftr-%--0C4Q4u@we z20UV6Ryl=P-v1#j>U)R9abR{DWkzY)LM}TyJFtHJ`pv*+Xw|9}hyMb+mC2JQzoDzt zJu>QSbaZ|Ot@n+i6U(yb;3ak*)<6 zlEuU?J~JO{X^k%*58g%WFCv6XiYTW&-zTwk`g)(Lb+x$0=GXA|S)MogCs4{A4Vu9i z5fR~!y~oQhzbx_c@?z=VzI{6Y{Usr+oHh?jqO^N_eSypd8-@OuLQDEa(S?oqWHs~a za8S~1HEkS*v=ghjjlPL&Exw}vV_G@jF>UZpqz5O<*f$9Y3DdDpLHaMf^b*o{jgF3< zKt#4j=Tx-8Cz);?C?%VfPsd`k(eDd$XvybMbo+2Ii$f%Jfr)}9rn7bUCO`0Xs=Ez7 zNwndD1fXazHrJ{{KDYRou8eQI6L=VrXsJzHb zCl=??>H!IKXl@!ADp-GJgm-(s%cM=dskGTYl{WhcKCT6~c5pn~Biz`-r=Ojjy@KO7 z4~#kO+O=clPfSdlfe5kI-Rzr2oBh(*o+0p?Oe#({(#gg7?0(JQcsjj2m$2tbiqKM7 zj*0eum&L+w@lUJOA+8l)#nPp%`7F=5xw)&6euoYn*c^fSPfAMS(?70e>+G$=GH9!R z1_e*drjkq(UD;kjLEmK2)_`CPb)m1Y?! z=-UhymhEi)`t_+`@d-F*KO%m2zNuVAjMmj3-?D%v*WJ%V>T{PStUpiDYB zzl8GQOk_4xjf+nstJPZg;K2hdekCfEit)#s>;KUy9ov6_-^$!5?8k~|yI&p!juiHM zqe|E{>U95$Cf|JX zO;&#NL97K55)#<_6aS}?-$#y7v;8Xe-Pk|kUUb+i!(qRfc*V%lzqC$6yN2;$5&qhy z3aTi@{B1Ysbh>R9FJ5elwf*qn!#O`cKcV(H?z`{4!?i3rI@%v$Q^QRZGNzn4?3v-j zd-!`tt6AFc1BaJU(lrCw?L?Q>XlbWk340g5tAY#~8`;TTsnKYb9655t3u%Aw!3W$w z|B0nX6|Gu@p@5U#zJ2>+JK4$$@9V?5FvCua;dW-!QWD zuddTGKK=cJm1Nf2$!@o)RVvlY&6_uSAnkGE#xcI_Q%JIZ|NgdSi#gVyvD3K)dI}z) zVQGhs(^2SH-eJ#;eDQm5JgcCTFca(h*gwadHj&j#;29K_l$4B{K7G0i(!O)&4tNE% zCJlL;AZv3eFE3w-#>|bf(5cy#Y~PK&c<5-}g^U#T{zZ#moBv>>mcM2{C|IcED1R-K0cwn zUa#L`v)i(;R%C^H4%zK?jnQa4l$4b8Hsbi=i!Zq4%a=RScoDK#EZ|3Rg@uLOmMvRc zl9Q8PFD)(YuhD2GRa8{`qSNUX>2$iKm6cTv&$L?Y_l1RpJuh6iP#0xEf5txsIl=!F zAzf$pSIEwBJgJ7h&;*vA5&;^ZI*HEM*g@&*kWK(;0WFQ<9)=2Daodto;>H}F7Ss0iq9__tBF;P=$4S5FREm>W;v zyMPx{7km%7Tn^_Yl}f$f!zmOBFPTin9PY*Inm)e4Ga(MdQ&-^ofWPDJ?(Xi2`hm=_ zY15_>iQ_mh#4$GhHx6-N z(SkS;H_|{_ZBQPWOx6%QH}uICEn3Jv{P07J1zcMwx(EcBDIEAc=&RuCNu|=ps3*kt z^2;y3KYsl9ueNU8`pv0Rr+&S0Gl4&5wOS1ZgQ2jfsOZ-9>(`eI z9y}OwzqV4T6lL&i+__ZripE3)40Vg`y#bk(XY2 zN!G1fH@0pApA0f3XVI}GDRgjtyd&(HwORDX`fNJ8K7-C|NTFLtiYWeuii*F?$xUo^b;^|D5O$py}EVlwn+><*KOga7y45hMJp$+zDD(L4YqeT+ z=gyrQ1%9rpt1J4Dbk?j{Y>nBtabqd^6?p8A9zB|k{|M!dzNBLOR`6DXe@LZUhl?rt zqLT4mR}P7!g`eGZ@Ki+}EOZA%-346Z+tov&Y3?UCS$Ws{#8>}Dch-Mjw*NWFC+g3>Ywgt)-hbZ7PEvi&QFYUQkf5A7Rnw&#um)wS(hm z@6=Q(O5^vVApECM=SmqL8f_20Eb_HwWFlSOQ9x<;wUqHtOJN~Jw0&F>E&lvItA8;Z z!mjs?=Y3rJK<_Pq^{M&2Z!z9C-mmkHr~R|isZzu5MKl`Ct-gKxVpHJ>9UZL2Q0leXV`aI8LT98i{^;$)Jb%)ej5;grosmQfKE2C$%$tTK(t}fFWV2Ka zCKy*@t|)0~|NFFdNL)2OaCN?L6`y$NmywKb&6hQSc6^;eC7FyrLq?-9W5I$2?{gei z2V*^Cg;-a3dU_)No)afd>{MA<8H;?X@=X*tA(b}xBvSN6wR6@Pp$*Qi&ZC9B@6qyp zkLl3tEYfS9oEl69D;@tWlh%9ld~4{D@OgLACvm!7=bbly^FV1e-AqwO^JzZHv83-qT0Jn9Hv6Yg{B_7T*#2Ml=bwKLMgFm- zd;Rs-p{wM`|F&)0J~W%n1qcgWmhGd!Z-;JADs3B?M#tvn(wXJ?jQ5ZHpI=u<8-4gT zS~2huCER$jpMh>2fNp7ugYPf$|DWYUE`c(jEU}k){;17n(_g!G4Z2*I!&v=e4bix9 zW7dCm@8129*=#OCnmN&xw0&f{ga5zTKaJ@YZ5WbD*8@u&`QPM|%47qp`p40hQStQX z4>gr$8>uwgz;t9W5+nc6OM03d;c3rT^oyZY{bT9z1-@RemI(_B8!q&J#js(+z~{w3 z{6zktZ?$98lX_3kvAq$*%Ye?WEn@t5=t4o3vFr=TlaiS%Z0o3G`eRig>u=C`a@Ngt z)}eaZHDnxU+a;erqI)NK*^+bqn>A~u=-IQUq;KE8ke}C*e?9FS1wATfy{Zh_;+sKX zd&>BF-LIsO$vL!ZEMyazv~ExmE$Q=!mVF*g8-}FOnPp=BRaLT=T4X5CdbV~@B0V@= z%KD*m{_+1}?+m^07A;z^{09aGI{HucV?AvjnMqsx(yO^m-syBBkdF`lv9{Dgg~#jO5s9Z=FHAH-8#&y1JJ@H%Ir?paMAvXkfwj{n!u zf3S{*9t8FRO`A4-qW&`;R?xa3iL|O;Jgw?qJop^lf=WSdB@0iTW^ zmmt^FmRQ&v0)o!@$}b_Si={OK5}5o*l;6M?WlPZ8uDa%R+#qX;xxkN~HR~UI3+(T) z*Twi>?f+%{C!4nUJISm3vuF$S#r7!K|9f#m2}^&^_*^^Hqh=d&_k~-cY?g>$`(HVrI{v*zg$7LLe;FV@B1N(_D;yBog;H-?Vuz&x2l+} zC+;39r;S6>Y2Bb?A>Y;GtWB;Z%E?g1*gq>A{XZfi0{QnuokOQ((4axJ{=dk7;26lc z@~Sz=vi40XpoFVBR==i78*2-!d!QqLI?szW(&O`Lio2rW|MO54t?8dk+x)Yu#Z@!z zr`^Y2;nxo~o6T_Z=1uIyJX*GFDgWVzADC_q)rSb?!fW{$Z!!B=2*U{o#6e0d4ioq4fjPn9L2&_KYp0 z{ZoqQ#GFz(zp{)%r{%D*6zvB6OSqu1g-qA;;19eW=i!;fbfgxC=fBS`p~7US+AuzP z(Wz6XK7d^6wbx#ghJ=JL9U(6-uUhMW&sfL<3!mzCjVPo|Lo=C(vi=D&8jX=lmMnoj6m-qm{t>*w z+Wz0%7!w7LE2h8^RkBV|<|+D+y>9W&W^zyQnX?bM=*A(Lw8bZjBKDM1rKUe5p&kAO z^yeBS`?nd{iRsg-@+?%8Vy1!wGv&vbC@-eU7|!g{sRL8dx+~#KL2G*CJVW&vxDpwd4n9=`MG$F=Xk$6poC8TswRCo-)~T- z2fkBM;P6srKP1+LFD@>g-oJl;=qgGfN0>Z$GB;rYMEx8$ zbm&m5f7t$a_wL<}^?yOUnf867q~K9i@?}wmywkssYRaC)ckp)(E23QikV7l!@;WVB z1A(AV&dZITjC0S(GCKZ~n(|_pOq>yPoQsNzei%A*D0u2p$VxVD+{kU)2FpdBnl^2U z`bYlVw{6?jRos6SC0S_y1YWlq`qaVDs~)3b@^|D9`NZ>@4*5X&P;SVWBX;W?{EvGF zD`x^*!)?05u zNaMbG_3F+hlPLq=m!@0j;3QtIEy~xC*Ifa{ERT+7PV#rizR_nOgU55^|IR*s?g8O? zrys_&QWlog%P1B7xm-)C+^W4l{?+vK^g-a6gD(qx5cKu<`1oph2PGyZLUxY)yUv+2 zr-Q*@h(}t`FF5*xhRMW5IX!d_(09fAnm+Q&-j6P4eFt*;JNx*#9~2Q-!Jh5(FJ|S0 z9tHFZDoU!xRkPU~m5`7C9)kihB4e%`m48A3nUbPN%ztv@lkmnq9%<>aepz zo}gF2Wc*_wgRkyT7U=&&&mQmK@A5CD`vj!>8GDcq0^7`@w2FY{rZq8LZ4aDx^?S@<>lo^5Eu3)m)BHM$S7V%A$TOO^Y5&) z;C#*O0Y<8;JqsGHqI(CN`p;ei?H#41{o}RlIczfovg!5uGuN+Q2Y*)9uU|h2c!TIS zqI}|M1p7GbVVX5-#{S{_{QNm$q%mhr6gsAY={F#6qK!dKANmhs-ksl}?9TOnXOEE% zeygL{vnI0eOB(R_EjpcU-|5q*ArO-O```bThFLQ_$BrHG4=C>5z5B7% zYGoTlZIO-6&J%PtMr$4RmZIII=tFlSq?Y%gqjeN{Sj;HVqvIw@x?(1qFwEHPcB4k4 z*%TNU2wiUJ7hilK5p^bBltlS3++iQ_&O7f& zzWnk_miB)`F)=ahe}UbvU`HUkd-rY!tya6jYPH7O?e?mH@IN5C-Ck}m7!Jq9#r;br zlgaVF&73(?f_W9P+rNwO&tJK61$r`QQ`C)1SXfxQ+}zx+)N1t%oldt-uh)kd42I)I zqwx=u$#l_VGF`64T{0StXAK6!K8;2*KPxM%_tvdjp?f0z_~VZyt5>h)e){PrCja@n z3AWlO$C@>3IQ&A6 z*r-t>HXcBK4DtocKS@bRTzGgmmzkN#-2foP-$-QSI4|NjCW$1Dvm)-E99ItCi{rB3 zyH@!ubs1OP7wy{rs?Sx~!;5zDf7KU>efPZdVS|qkL?1TxxEFoc+vC2c;4ct-;(dwW z%ZM+(tKg&lP%mwWucw~G*WUu-I3wF@%nFxZn}Z-ehKc21u@o%`mSZ(;H=dbM7?deF0j?iund2X7sEY<23?fl`+%Y~=)9GU#SibMEf$xOeyP04#&Mn^Y=w zgRCFd(B%T*9^b)tCb?YhCX>lnSiJM}^n}mSL)y?`gU+0wdnaq%x;3+rLEoxdw=U=6 z;=3qZ*M%@O@z$30(>g4-X0U*P!RE z+r{^oE2L5>(^*4*hD;Ro4)qdrXOuxIl{S*eWKCo;8FcTU%hp`blY_pT=vqP_PteJO zF5MINHJ^#$YxvOXV@^l|`gqW{Xa@Z$=ygFaPtaWiKL>T;0{I*M57@PWpNaB`Wvq>& zqN3RQp+kp4FA;SIoldkj_$;XRMiPkxX||NfWG_pl(soj*^i_#O@|r{|p82KwlukDx!x^h42~pce&96X+|V%qXi+K2cBc z9YH1dJ*ias4-XHIf7P#Fzgy$RjX!A8q{#>HYjff|__bX-e}nf88Z_wU>FL>3CX;~= z0DdVH)sZ&z9FZ5)U3-~KCh9i=pQSE_3XD^)zy7)u`gPFB1aF`w0Sl{ftcF(`nlB9)RT#ciHD(I85S0{_W1GRzph-la_r#2gZnmU&;VdM zozO;dxx6*>BZYqg)E<>>-RTp+B7aM zE^ePnrHU~cjb%0)vo)@^5Br}co6Tl1o6Y4KjV2{6E$!sNg9oQ|?b;P~%Wuf#@|Q72 zNhFeb9v&X9ty;B$ZV>u5)+DS7fDr*2F=7PfdFY!dpsxrWUAbHi+oSg1fB*fLsi~=l zO(qj>5cj{BtX8YOprGK|=FOW&Din$iXg|4J-pJL})zj0{)1_Uzb~3DAuwMW{pB;qt z490pv*R-x&F30Bam38aZ`KVN?C-&P8vJ>z;$fUP2-7me?!t}~?Dl=956{i%!E6t=S zHIlZ>{Qw} zD}_R4BvbITWD1@Nm-G~0e9wp3Bi!%$E{;|YyG=VL#tPr@aqXRvOd&HL?UVW>Z`AI%+Ag}=v;PVrIn)2l~L%AsmxA(&CnR8TL|6NS>3MCvVM_*y*+I2 zc?TU!-VUGl#e4YtHSde}(4qb1qZ@*4e$}_)d-%|0{ORALpVs>V>nf&7-*sp#tB1LtL@dc5!iO0DKLM@q!&K;~T*4273*xYq5@UadByr zm6a7F)*oc^s7jk-QxzW9Q2rdFTR5=GwjB}=NGBGy!_69 z0RymBz?uL$x^k=qM1BzLHw64T=&3gxGiJ_t4Wtd7uXl{cqem zz7y+72j-z>>+~n)aDGZ)(8mB8-p$dMx) z^wm&)j1Q=z;NakX7K_CJmX;BzrOm^6U1Z=w+&)rLO^+S~z28ea@>uzoeDR=44_VMh z-aI0a&aBE|y4%2&K!5suQ5K62ZH9VV@?|8`@7+*ChgsBRcJ`qM3?1oTKMH5#fa4oM zzxmG5D&1u0jYmdC{sfhHH#fJs(2)e*4R{D3=qh2~gf%njC_FrTlHG212wd+SFQxU~ z2~2PL=)BtXfkD7_*zs)~{rXV^tsN5U&{f_xI*DP=nT=KQI1q3lZXPVAW&Iw|ypM0O z_))IE;ue2?kLLHj&FV({4tS?$R_9j3-N?$y0&vbtuCA{2unvF?7%*UA;|rSs!Cnm; z<)-oR@rzgn!DNT6@U{Y4H#C9OLHNGvcpA=x`RDwGJevRU9YI$+k?C3E8|cN>Mp!o- zT9C-_`Ks%FSH}l=+B@iv&;9ro!yOdEpsf!6oJj^AM}|>JNy*7pty;Cg9Dubw)_UMq zfna9|sqeiWZi;Itn`NThZ=;v%+&)heewv9?sYi5uX#ezVhA~uApP$DZN~K-nGN>qx|JTIwi(!G6{ac?%I=wuP3X%kV@qG8!nN{TvEEVdC4=>suEdMf^g1*h7-xuW2 zvES0^$geqceOC$716Dc8UwazP={@fnxKh9M;jzEcBdh*9=knLDU*D}umoBm&e)s{R z1ML4GM*yDO6YY=k@1K^#bp30yCqRCHc~mU6Kd@z?)9=sYLE#(NUM%SoMeBwn(XzhL zwCsx*hL!Z^hGI63s`AXV_q*)swhaF1Rq2Uw!546*ob%#HOL)I_Py#Le@*$<(=j~%a zMx!xPX#e`)WuyI}UjsZrtOYUtqyN{|{`;rp(pEpD4VwhsZi7!hli7m=jn8(J|Ir`i zbZ}-a?fEvF;T$1Pu9aPb=ZuEwj(d>56hxmW3nkHx@!D) z>i=Ho|83f|kpcU(W5BEG1Hz&8MJ0V60IGW#OyC%gW+%klW6Pk z6lN0!0v_A(1^I+=;L%wPW!$f1bECG*Ty0P8Oep`(v1u%R*yUi1s0|m|*|z4H*Tb@D z_n4fT<%)9jd9q>49lV5Z}X@)o#(lP1u2>S z_*S26hT*nzL=M9W-}7BMon2PU>~QY=Udr&VMLQgffBPoqGdmFA!oi+JjQ?rAvz<=O zFnsxQnXQUQ82^_oTL$a|uex>Xx4eADU6fmtVk`W9hML-X19E>pUt* zL(hn+>Sx*z&dGF{uQbd3L@lk;ig&~#eW zFM-)MiSgI86LLJS1+&h%9sMBRw$v(= z|Jk-hcMg_U+i!!cCOca{V*i(MU(f0Vn0h$BWB0ddbah9mP<{)Ae3wV7`z7*rShbby zuRIILrJ!;7%w|U)$N61zgYff*j&|kx7jk#(nPbIAFMAGSW*9Ht209mFxbaSBFfwd3tR>1#&PCs}IF-$lAF24@gAb(8WrhAE_@C(iX#d*g zfBgSPXO%JhN@p83{u=gS)$QPzZQI}sf;}N`J68R@b9i8cqWr)q4IG<8NjLNko1njw z%w}`qkt0XI`*H2msgrE{`0=doE0s!SYX)ARxc;p<|2fJ(qRJLdw27`ehU1F;mow@0`t#ejZ-)Y?3*>&k{PGKHdtmU1gz*ou-=~%T_aBu{V7yk@*zvfp zfg?+3i+2vgey#bg<~_=cu)u`HyO?uT`xFo`O4+#~BTAV~mWU7Q@JI4?e(>4(AY)4D z?tzLbTTFpWCR60in>WGpazVd`PU(+7{>XHsMLn?Q&6`8_fvx{A|99!qrL9`6cH)28 z==UF06g0BBt)ggKDEhDmSd7dk;ng^5-3G;DU!*6e(hYVkZ7 zFY*-nt&(BzT8!1(-DEO7ym#*&&irtNz9?|ep!ZS}*A(`S&|$4#zdp)e_nmj%X`@o9 z&Wm*jd#@9-VKY}&&6d&Gt`Y6B%_py#4I}c7FrqJpN82MT+74&D9h<2pZE=-+05`a-LrT{meIkD4wm`D=MKjA6bkybUF;VHe44BL&+@;Q3ZvJK1!O zQqm5;0*8$#?EX&A(~z;Es_k&T9{Li04xZDuPS^PE=rlE@-{!Gwod}r8S8m+6frBF0 zJP$cH>}N##C$Zl<6ZBz#TLLV-7A;z|EG;cPCdLU{nltloo*r)}%Ff{tY({yz(K1#h zw2||3b}ibGif8u>FQFZ$zi6Oc!-@r4RKX5aOb_)sN<}Ar z)=+MA)juPUO{dcxKXc{`WIQh5Zw?wX2pFIoZ0?>#-rnBOd4!%MbVr*N7Z>jnBgH!8 z++rTPxSCBV>`O;k5bW2e;$Kc{1&!(7gtqO`*&mhFtGMzVMp@z zs*doZ)O2RPmWorV%Mu`~uBfO8+`D&gJJ{O2_uhN5fddD^ZjBYYwl)_D?_kq|^4G0f z7y1v43knLhiRH&$?ec0J?H$QuOgr1A!j2hjg?ey)UfVV52sXH|7e!s*8g{G~R+LxU zBSxL5q+R|cto*10go6WC_8-5gNtIJIR)Fkwd!<^fUbA7t1{j1Yu(lP>h+uXJFGAmZ z^9^*JafS`B<{D&XW-bxSkM%_O4!uzRD%)0+1Nq`HudANdw0{+CUUv^GaoE5zo7sR; zR{qOtD;#@4(4&)j3L4JaSo3Aa`Bo}sFRm%7UUt~umzI{!ojP?Ya4i+Rdi7#w)c~^_ z_Murn+%)aZ{Y}Fr$tcd3#^rdXItA(4G-0X4lKgD?VpF>Vgl0 zcQ{)M{bbJwWmVX*{8?DA1HQ6OSIrhL`gB!V_&+cY-rTKcc6`ocwOWe`3JRtS7%%|( z_6p45BS(&8dVP>-yeM_*)QO!B)~Qn`HV0sT93CD%z-F^KgeU2@%@q1IUv}8h!Y&nU zBw~xRaywz3J743RF%}l}QX@=IfQsR;i@X3(^l1~pCKh$b+Y{>JP z+qd_GErySek96_k#jMDp-Rg@H^qxUj9|(9!?mKquc*krur-*5m=iBJSOq?yopJj%2 z!C2&MQ(W6M&N@Rs+3Uby=f4-j!alZU{|6>J&N4eRNk=hfOx4Clki}w&PfAMaFO^E& zfpr5uPUp^@**KP(n);%}u15Fn-9tUVh6ejU*KXaqHPL7^%w8OX_4vI|BZZF9Ic%y? z2GLIWX+HV~?An1xFNOg=y?6~9;cM&l)!YA+iH=UGtY*(FlF4Mc|M20%p1{dz)259a zI9;%X2R-fFwHG1Si9#nHc2^xccEms6o|>9E+SxW$Q)r_Lzrkj^T(HT;Ik``FC^P#0 zzA>mLA>7#N;em&LeS@*u|DS%-LYa3hHTQ9LqtSRZJUkrw?JnKAbz`>lz{pKVNO+MV zK9gX-27;Z)n{U1e-B6b$OO~`Y7z~%hx(5NX|N3S<9i9Z6ZoOdJttVElv3lA+4vsG) z`Y><1uBQWdj`u7K-t*VBmEUUOb*`MqT3K0n;_TV8uyJrf{$|aZB>~10Xztv(%>Qpm z95ZpgRNJ;~Q6a7g2?<|XtycaJAc1gZNkOcY9vw5&wRJ{1H_u2XXBz1D?+tWxvVo3F z!nuTcIxxP14t=Yq!`~WecM}Z+cXKCy219LRx7$rxt#)@vNC@_?inrc+O9sBBSk`B) zYyK00Z6Pq1p$`LFfp+cMc@`8Dd~37Wo>Y1RZ^Fde?rRF|q|CBWNs5gM<8AD`=)A{P zb_OHPVXSgC$2HEC#`%icVgNQjS$ezOu2rd2%U7>n4GaTds>yKv4(u(#zxY2zIHPg+ z@ZrEelfL%aYx3T`dwXVOW%-!R=17P%p3LdrPdF2!u&{8#=+UELs3Y&uqldI_-@dHf zpNISL-xAKpRIAmF@eq4w{CAfwU3y)qRQ_T%oA1Krz9#;_-$8b}U8UFS4@E~uf7r5R zOW@1P`}FA}4G0L3Xf&F?y}v(?aMoW?P!Q%AoNEo6RF^}C4z)^6P5rd2tZbT2r`uvM z7*3f?rf`eJ@(^<~a1_vfR;xAVIW7xf@g490G#bs)w6wHO7cE+ZGrX~9mjMrJ<;s;D z&OQ3O|Mlk)(!&{-@NvE-{)qtt24H;v+X*=6&;u`^-ty(k8y`G)u+`bKXJ5H|`SR;G zZrtb)9v=Q?L`1|}wYi%&Z@w8G9^UcFl`HL!968c*%9JV4NdW%^81_xsXXH~dBfV1V+t990D4Vw~lJ zc5~oFZ20?eBK`_t+=X2VY?X1=E_f8s(|}z%*8DXIYuB3lUdVNO_QY1Hc7om$Fi>C@ z4*n=GxkQ^#oc|!&M`CXP>>lJPHa3>64`D|Q3>mz`xm5`NznR4GTl9eqxNqM+RtMmJ zKo4Zhm@$k8fNlWUIQZq`#*LGJcaAfIfXxF;B*Au)g@ecpyf)Ypfq#JWnB;Ofdk6dl z=vQMufqhlOh7BcHy8}l8du_yn{VQ-n5HB!;kuJ^`#5cfhLmW777I6bpe#D3ouvuc5 zAY;aiVd0TS@qFh0&4hJjT3Q;*EAX9BcHo^195@hIQ&Qkt33}MD!;@j32bmSHKY#@S zeKqLp1BU`QPZEg)_W5$G5h2%u?JD>o;NwcAn3Cmk*t)iF-=1N001F;GPGBOqxVW$| z0%nFBaRO@{dmdnrKsOwiC5Rv2Azj=9n*`V<;ISedw<}7u=2AZ{{JwEV<^TA zVD|t|82aVN6YOr$cTvxfH$vwE<%B*i@Jr-!IUe9FGT@nb!d4MBi87gtT|+)4kw}<> ztj^ut9Y#Bjv*dB_e7?MJ?ihRT=H|xEn#Xzb_y!Jfz(+Xo9+)l&1Bdrwc;o?ja&vQI zdBIsP$fui|8yq?a!>K`eP%g9$+61ZHD&X5R1GbJ_E(iXK!vP0J!sERF^9AR*F(1#M(jwyI;NJP2 z_+0!3?`t}|6XHQS4BH2Jz%|ang9AnY@+#yR`ECr{1ffr$ZD4B;d?K_5fCVttxnRB$ zFtC8BB*i@b-_(6sSs7c`0H+h{5#S+Xz5;fS@E@Yy@!tZc2^c=eBkCG?XY~lYB8fzT zGyiY~AQp3Qz>$G#E0IWWP8;(j5=lGJaUAEk7I1i8!8=@wVPrCy_>Mi3O8GGO);XN` zoe+=Xd!#Ls$sFkmd184(UU3$mOeO>N4={;{$i6RO%zyD zNTpKXePB+Mq^GB|dTh`D?3WiKoWY7UCC+CYFkk@Idr}PWu%!lWQk^<=FwOu}12$*u zoJQE!3VB7oQI1yx93x;Ty)KnX-;l{=o#5nhIdd|Z3^+IJnfMOR z#B0$PpE>)uc8*89cMgN^aqr|G7}!kLd4 zX90o+Tqev9Y)r!30L(n>WB%&A@a?zXvhyXO!;E*ypl>ARPq**f^@tL!a>(0nC@-CDC zWkK69EGUUY@``}d#o7VmEbu)9JrD3#U2twI>_5SS!X5+qpf5tuo5k7|*cq5pu(koV z3dT8%H^NwhaUJd73gduWE(bmp`YkZ49JpB=$NfX0P;_n4qD7B4-+c4qZr!?l*1LD_ zzWw|6?>Bt-@WJE8jq{!~X_DWxY14*Hn>H;#aKolfohsgoKHiDf@R^%Bb*lJ{voD5Y z-w1Kl{D$5C@WT(ozW(}apOGU+4jD9P(16cA`|OME-Mjbc+`027?b@~b$iu?}mFA4g z)fsIf{A2h>SUaE(V(bMD9_DJSnP7K;IRSi6>;+$>?nPM@=31<$TwPt+n1g=mp_C)y{S6*6rH6cke!P=FAzffB*g;BO)R;#Ky+%&&bF)UrDr1U@itYeoEFmG`=>7Zmcb+|acF~$OYrY8x2pG`1b!!NQ(YNq_ys1zqUdKG5 zP$)1@V7|sW3Ts?oEV^KyhcyfK;b;SKEm<4kY(3ZxV;>LQIbeq%4y@lX*P!k(ry@PM zTn@atx1>^Omp*;^d=?rSIz2u<{y8vQ0SGxPkP zfBv~-(xgd4>eZ`j28}_rX-9dX|uH`uH zH7_r(cTb)?IisSYBJHUe`~Ml?-%m(L*weRf-yR&ty{=Fw+PJ#9Hgk1#tq)!Rcv0XV zfftE?K&*3T8as9@(-8r0$=w~*E(f0qdb8jWVBEnTrqh`-XQmkp26krV^9c6R!1q(; z8z?)zoYJGzl=eVFNw?IL5T>Hot4elW?4v)+DC&G^b$71Dz4+ewdChCwJ3YI1x{$&T zXVL8w`Sh^HyGIwwSlY2ylYkfo) zO>4btI^f-j&z^t1MhNThhep$!kHct2*Ne2|t9!JT#esN)xR9okTjv);%Ld$|Up~G< zD~8;qof8sh@APE){kIIdvMZ0GFDcpi*fp`XoC#;KM@B}jZqT3s)+KG-+}xT0A5xqL zz#9gC$hqHxL>Uus1HhLDFBWSa4-XG~3q7570|yTLproYas&je{vJ>ks5eEus@3dsv z5CAMe;hb=Wbr{X?+@}5WPv-Mjf?{1%_t!Yz19On!4#qgX!Fz`j-Z{bu?@-4}zPiuq z9&NCCDB7_qF1`&APqffpd>&R0eoV7_T&EdbFVV8EsxTi>2K1L5-z2c}*MX5~G1sEp z#N%-kgoK2Q##rs@>e^D^9bzHr3SPXsyE|kF)d*PLSeFWT4Q%Ye8Ww9F5v%pqty}YO z9-^bp1%e(o@cMwEiE=SaO0ixXbs0;m20x~sdt9ek|Gq*i21Yq>1*^hTZwDTCP<#fw z!rHFIXS^?_i~0iA<*W}bS7AO@%ae0_&T+)j(teRNv+E^>nTUT#%m?B|*>JA=*0Bi` zaj=MCSk*>_g@xxo{P06yBE9V5;?e|jDDY1O3|xjQU#$Unf?N)3e(ZI@^TwL9G4Lfv zj2Q9n^78TrwWWo+-}!A)b?nTioG-uR%lkC5>t&kP`=(G=&pwC#Y4JSo`QpC!SUSL* zd|ud^?@`al-^^~8XvtR(o_LS&2m{Q_>w5}nJ;T~!vEbb7Z-CE=F$nW8#uLb#1lg3M z{>42n&PovMm4QW%0|tO+1gzdR7cN|wWVKqW4h9pbEXP10Gg295C$J*LvaoC5mktr! z&{&3DiG0ufID%$4f6*08jTw_emy5AC$w7ROn2N)VfzYs+*Bu= zP98@!j<%0Wq(gJk+4;!F7G=`zNy)U{H;$I~kD?#{eU;&2qCLbizmN}%Rj0ISyIBTL148K*cu`o@ikMW9d_)hG%_{UH;cqYCR<8t<$)2exmvHq8yVGIiw|CAV)7$1KhPaFN? zDfY^f|A5VAtGsyeA`ZQ51-y9h>m?Ei!^y_^BESd7o_Fikt?c|KoTY~K5!whcQ}CBj z|AB#lgUn_#e*m}>0ULMIFdjQr%sa4PfvNg597*7aW&c8@TI@rrVa-#dKZ z#0q$?8~qX}WLg?s-cdmJPnOcdvt@K6xR4Id$)L@{6B)KDs|)WqhL`%&hu0ZqF}??G zsv}OJkGzmCq`&m5hxFsWuhF7Dcc1)Dz_JA<>-mj&wXTPfl9JY9{Fck*P2JtyA;-jd zCvw=OV+_Ky3%NS@&A_SwzZ6Vn$Zp-;-7)vJ3JVMS0q2N2RXww(4+|-x_1=8HU+=@0 z^WxTgGCd8$(}`lQs%hn*hYV{Q7~l9_#F!O*)YHzdlNsKwy0ALt1lC;G*Tr2^Q^@o* z_WkmHQS|eN*JKi4I9sEchldB`1MaYg#X57^v}vsVo#tO)WkH7qd{f9bQU5O|CMK?` z885J1PATl=x6v|Jm!Xv#1;NTTw;CxXMQ59_u1XUp1>j&;}i2L;-oo~R;vXzE95!NJUu;O z&xbXT0_X5GXwbm11_F6`d9ky2fMJGz#l^*?Y15`nU(L(Qdt&Yd=H~uc84L>#*ucQV zJ3Qy<_|3J^%>yO0qW@!7wnd-aW91NWhJkB!_xIB0*1br{cT}|at6Npr$S;U-{6gV; z5@_+~_i1+b8??OtBmNuK|EqA6fm4@q$6?m!AcMh>I)DCr90bwa-Q6Ads7x^jaxLiF zLGN26$cr&H31>H9ztHr(_ulJJT3YJBOce>ZZb9GSzfWYCyuc?tv+^1E!L`t%b1GIA z)IIv`$^p^re!X`h9r!V`)-}b`2t0ukE7EAq;J7F1l;I{j)z!1Ed2HgUdfn*Dmv7CG zIQFkHEbpNh%c{cQ8s9xS|KuEuv#|pM1N))=U0q#)(GNL_0(`g$6DF`V5D0u9$k?G* zgfosH-vwUxXPcn2=~`J?={VO`B;ZbO2>@1f6?XP! z|75y%tn7vREvtWj{(DxoPwp_Rb+if?H86(ed~}nR zd>+Z#MvNQy?H9KclC8!$F|f0Yh=`aBy)p3NAPYeKLyro&*T9Gdf%kwt0dSPCMygx4 zF022=ix+=nFc_YYvm}OVX!9_!{&~!9U}6JLH0$vbY>Hax@p(1zc;7r0`pW*%G^gh+ zhVy;*SXr&#Jd2*J|Jt#{tK*RKX+QZ)>{ntOd^nU5nAdZA-l8>w;vC-r2l@QEf@=2O zAmEeTzkh!w_EErS^YHM1UgrP9-g`h-Rc!y`x8)`wp@URGu%Pnng1sQpM39n@MyM*n zgNGstDoF1=v?M_2MM0%0C?dU=0O`H=-aGrhKKq=x_uQ5Q^u6c%{lBx;tT|`S%$%9M z_q08G_O^#j7Toz1wp?*01Ky8Lr-Q7oqqDO!&W~HISh3=r^78U{^L7WH%$MOW>L-W8 zB5C`-;wa>g0_F`Wb=gA~fl`MQ;19jyqU~#3BWP~#Yc%7nOSFGZJXOeg-|Oo#W6KG; z-q#E>tCOf(9pPHHNLp({|GP5piXoviv&SW_EAWADt6d}M$kG&xvjOP3xOM9mj6t+> zbaZUy?Ci{EVUV50-2~A7%9Sfs-+uco?j|t&G5>=11Ni&+`1C3-FHbaw0mkRG9#Q1u z9%Z4s9XQxHBAO1&N}>z9v+3s1Ji2lqhfZurr}QWz`A)cVw#GG*4lPQt z;DzFRI4CFxSE39&AjrF8{DCYp_-@csx^CS%w#AA07q&!k?gIF4+_Zq{ zkYj`0NboyhBLuqAp+5}rJ~$u1_~Yc{#PAOY2w-5UH&Eo+7X!0K&OGzg5pHJ&Ol|&0aRz|9PJVuV23ah~yFpGAvb&IN7UPdC`Um_a*onGZ zh9=+LV#t4V93=%A$DiBB1pF=YO12(u(A-|vX|+ov<7MNB81nH7rd6J|*;WngQ(W9_ zFy$q~Kf$Iwi@NMf+|T!wL&6Asb$;(a+A%eXd4gun1pW;hHgsyyqJ_SD_wGFY_vzDz z>0hhWvV4Ez#*KOWha4%!e_QasaWIeJYhf#a?McKCY)q{4Kp1Oim;V?m@b{GMPQdnr z>;VTnuNaKBU85!M2XP#1Pt1DfDlP6Wy)*pxn8IJOb)m3tVJKI|G3vzilHS=ChFnLa z#drR{u!n&-xjrWG{9Q`Bxc_bX_U$VaFgdJN_+$LRT`A1JxG#t?N9f0Kbadpi-}=(O z1b?F)5Mw))HZ+VdMSVAoi8rDDTfgM9eUEi+XrpCW=rgdbfAIA`h8r;i#s z1t9aea^*_H*{^c^*}8SB)%h3jx3rZZ!Cs0teYOVwgj=O_WKkOJ{UVvRev&}GBP1Ir zjE`ZlY!_wq&`7pzGOyPSn)~i`S}`ch@L!}eg?|NYlWm=dx&c1M`ruj6gs=x9-bFg8 z4kDiscX9GFLg^XhisxkmLTTSu@g}-u-BSv_%=BU3dq* z{5U(+D*To6FUEiHA6|U%MeWNkzs&ouF=NKyjym&yz<*HgziJ)-#h4+77(;|doOtHY zEtdR8$D6EwY&oneD@$d(#8gnkg+jK)`|G+CI{m=@*#G$+j}{KfbKSws*&?ihpr09iNaH-W?1 zJML+;Xwd??at-@mTkwaS&_(_3*z~XiwXol9!*|$y!Ff5(euV?=<6K_3hOffh;DHBt{R4TS+SWgpzRzNNI zd;ehOk;exbd2?ZB3fTVRZ|l-{EgyNYn9`%l*>)RvKQ%_R<@Iu~g|cBp1TA?l*znH< zeQweG-nV!jsnxEJXM*K-4>=B@^4UsS!2jT!bgT5Q9Dl(75&XC3pMTzUm*0f;x21_;IFg^By1*a^s-{d$zaPU z$prp)jumpg4Q`3F(H$OiCOxlnjiW_`ozD=T^S0&Na1GyA^ zse}sBjq5$wMT4y;F2f^%?L95(7es4^#hTPrS?_y(w$eUdPtX_OpHUnAw}U(t3p6)r zqQ`wC=nv~V!TdMqYp84d0sR~5BHN2HX^UALaIj^Jsof^5S1#|(qu$f1u*EG_I4#IS|^t!oF2=QIZRdnVEvml%e< zu(u`X!?ccxiZ&>og)K0)%cO`m*lDstV;+fgaISUuM@B}1|G@MQJ=~B1g`6AaUjcud zMM5S8vJqH+3jU9+_2X8@VxSWV=Pwxl!GCjbkk>F%b3p=-P8BrA!@KF-e{a?2}n| zBR)*Nd8Lq_XEJT_PNA(Irqb@o>2z>bCY@N5!(++qU-KwFu!QnbWW1Q!ys6EF{9ep! z9*jHdhQ;%L0Xvibo7EF-kkUx?HUExxV;U>%ME>E)Y~$;<)j3wre}jU8rs9YldU&7@ z3HPzVYliG?$BrF&3IHz-b_)gnO`Jb&*|Nnd|0nQpkyKaNuA0?)%k^bES*g3!F5n}D zZCm-c#7Xn&hy>a*IfD+)%A()b=F*jY1r&CwgzdMWzrdc6Qm;Mu7-PW$Cq$bQ_td$;`R)vF(3iwm&^=<5OR6TBYCJx`qq)TnU&#qh`Z zBL<(k=^yZi{W{66o%Aeh=*e-ieLK@{B8>>8w7wq6JZ50sgJ;+n6L23O`jsiMPsmuFgrU#M)oWoKvmKlIQ;58&?C0}ni)f8vQJ zw4n8$fBu>6YJm3-hw(?Y70mnJy3T(k_}jMssI&!H+w(^EWZqA$bCGNd)|S@VH;lX3 zhrwfCw$3$yHjhZB-&W>PW=y5UeE6SqckkXU4h|0X2JJU%*37YE$BuU0yLV@~bHKf< ztc+p*!V51zpSHST!-l;6sSp3h68^%5rND`JHxI@4bRHkp42h%FgE8*F=BYBRNQXG_ zEJ7&`b}ljYtQr)pj#h$sd4vCINW`=x3`BZD0Ha;{@@YS%KyK0 zq=Yt)kl?h%JCkjX0)~d?5g7)1q#{n(Cq)Q)hizI*o2H^nMg9>|`69%BU^5nZQ3m&& z;Tg1hVixbIZ8^R(mRCbL300IFR6#M9$|(F)8HN5{O2Nk^f9IDHx^=jAA9$#QqRyAG zy&7AtsHo`ssZ*yy*CW8$EIDEwUS6$=(?IR@ycnG-M zOSoZJ60I2=$2L{(C7(zOT1S4^8%5ma;hA)9bD_z;OgOeV8&*l@1B&S2j9l6?F`IUL zlttUdWYN}9nG`TmfnRT_ER<)>Z}Q5dz5mK)+yS4~Tt!92trI6syozzBO`A6M&_NB` z8Y4%JRKxB=QBjcqJ3GjL!a-Le_P?P2y7>Qqzq##M@xDd2TPxnpLx0b7@);5@*>q*v z&ay~@Jfa+g(sL%qgVsU&uyYF-A?=Qjvnk=G(I&lcfKk}VGTJ*yva`!}Ze<&{%6b|9 zVZAO~pW#{b!}MHA46LxZmV#ZDkdP24(szE~fd?F(eDX=yThVmu)=iDO?qbY0ci0aA z{^0*8$DeK6w!LRf{?ioxu!(D0ZwX#j;=QwIy=#&*?s#ReZBf&_mU&FSOEz|Wh9r=` zXBy`RTz>jGkBYJ_2{JpbioW{{Hg}cvtNS13|3v%`_4lXX~VD-8AjP`$MKJ~*2kV3KNK0#i8AgdJhr%ivSMw|>oV*|cIeQdG0soA zcI|2hdT!soJ#;!4_{_E(;1AtoxEl-}slB~D-~S2-2*mAJfzH8Sm=w9Oc@3-%m+MMaX~XR-_Vt&Hm? z>Rq4E-y@x2=kJ+Ouk_|+g7(+DBpK{Mi|^Y;<*==4a|fQnzR%NXTkaJhR2YjMw37jFa4M*4< zycY-lKlD@r{?MBU_%B&m(i0_5nN0V@f!4 z9)Szlmbc+u#E;D7b>9Zp6b{9I1a5bJlt+nymh&RU>)qpXnf|TfUDQ$WAJ~uHH7<{B zC7av5SGc>SrKKm&oH+wMxXw6J0qpzt@6UFEA=8GlmHIl^UG(<$#u+elBf{Q_y_1s@ z;NRlgZ@+!HtgI|t@o#ov^#+ek~>@k$%FvzP`@^BXSM4;sI;q3-HcioAbi%{LYWFDJ{&B z{u6Ii(ALqgO)ss1fJ5{_^aIIfao^bFkwyEbVqayOUkYAket!O1e}DhZu%Xkk@er_4Gl0&ph*t z4(oO7Ta~c9A9wEDIkw*jI}I5Bh0bm0@b22RYlp0?tbOKnE6S{)pQaVj_7Mfd_TOOx z-pp?qp2Kb2>YZ!x%{;BO_Wqto^-2{c722~ z5_Y{Y4gqhO+#i5L*zi8Kpop>)EXOY4YHDiIQ&LiDU6;A(!Dkx{!8^ zHsS#71Kn@&$~NLh@tvQQ?`F6Iyf%AgGpz7z@_n>~^Ua=FJa&9H$vXU#Z&%TtafMP| z{A+X}KBrgA(_^Fb6k1fBw8H{V=UV=zZ4H#?U|5g6aIV0N$GZsE~0%Cis{x*6;x4bU2ijF zHOtG(&zw7V4x1;Om)nDX3|n*H6GztiC^Jaocg4-Gwh_&;?h3T?o@n)ZHzGK=W@NyQX)qLQjDm#M;47Z(@*bnMtM?5mtwwQ6Pm(n~Mt zUU}se9_t4V9Qf}9Jv(s@1KBL_f*ySELC8Y$7?hZp_ze+ROjdDMs%YQ%5(8{NyIZ{h zJ0nh%Fs$tv^o=#i*5P?Z*elAhRwm;1PcF7G{#VnUkBjNYsU;M1p^E1$YYsY+ii(Q% z?AWmbc7z(>%^Z%%9Q272 zI`C-;WyM&|rOCIe>DatdO1f2T^StWr-MgiEd3io7SFVKJJjX{Kd4%tVg6_S&y&;pP zdirUY)B4Xj$b!D{#v5v^wNM|>#PiQT-!eBhcbz%>;Y#!FN^7A}`LxZukYTl30bAv> zEgXQ3v6h7wu;zRwus6jQmo>VCe)zPMvaP_sFr$WY6D;u}Tun_)es*^Df|)aCwgc?j zw{Nfa^77IyTeggKqi)~6oo(L#HyrMxcI?wuG8t$r{Aoqs)`czSGuyo zyA*V^oDO|eO1sCF(#}z(v}?5FcaJHp)1%J-_B%%v*P0eESC)r=*n)p;uDZH9DIWZu09%fZty{O&!*(&wp{7ln23;7C3Hfh;UN}BKfXoVPM?)t;+qP|+q^72R0ofL7 zZNpi2L23J-BDZhKImEMd|LcNsI4rocHm^A2Z!!%>Qq@tgI~Ni2?^Wm%&`nrArsg1&%v+?tHYkxOf-%u_leH z%iSgJBh?kq`#`*YEibxD<%M^7{3^}IeXsg?jCbIHRF>VfxvnvDHCWqUy?XT>m~m~` zu%SbjE?smzd-l{~uEYJ?zJ2>L*lo#({HL85|8O_V!^1-}aNt1b3<3@uHf`GUP;PGS zlA4+tOQo-WGk5py-Lm51;@!Xe@(UI+d}e|%b>P5(8pt9Gdqj%6(0>ajYyii_#j#y5 z+?Rj*?YFfLKm0JpB6~MCx27>MF)roh<-dYI{x<_VyXxxdgsiNr>C2ZdhwV7rebc}B z=9}7X-MVS!%$cL!wr$(r0@n3$n47?#8#;6-_}*+M0B!Q|@p&*IAz^rFY3cXX)z$I1 z^YmZF#od~knvCM&;vKhd-|q43v(LglxBWv8J!JRXbI)ntfB$_A^j*N-Tx~K@e<#eHI>TY^UptTaq;5C7m|~cM;8ArDemSon7Wajj0!i3tDr;}7-Z$&=NPuaoy_I5Pkq;BhYM zG-=YL7Q1)v?sVb8g%^T?g5HUWiuxcaDaku6Ep2>eX6BUa?Ch^{b8~0q<>k%G&(B|y zpP#?<9{i%*+}zpO+1X#FrKNoo6B9G=^5x4f`uO;?d+MpDz%zlZIXmp(`}OOmb$54X z8BFNAi;RpkwBvu#RaaMY{l||VuX^^`XH{p;oKer8KVJ<#;ahLLrR~+Lm##~fE_UEy ztJP}Y1M`IeH|Q&}2Y*%g2ACV`;YE58qAZlj|H1!YzYO{XCr_TNflke-Q>UtNzVZK> z6MX^Uh7B8d{m`L92i4A*+cFzndkoD}EEZ@lq_7H8HN3&2}^;)y44h6+3N zxbtfF*kg}DX3`F_lX%8iU+dPb?QnMi?`_(&fely4N5X!M?&+tWW{+~9PZQ@O6DLlD zoVNzD_C-ZS>MK{S;BJYbuK#~p$Z)7WV(RA2?yc1ad!v=Og(GXEQ}$H z-(P?IRlRlVR<)0hk9y+7i45<`%1XAQ+p=X#hIw9I-v1v!UN5KYFL=iMcktjre*X2> zUk&GZd-m*6-MxF4&Ejw_Jfx%Use?q6!79gUtDwX{`K5GrtRNeElT9LP2&k``%{bkR}I{Z7z|96+EsJ~Y6uX}yASLC(z+*pxD z`P@-ahqn}IX4!Z)lD0~%c(zwOH%R6M_>c%G$rHtjA38rGV2DMNfmMf_We=UIyG zNXBPe@DzOq-BsP>s$u-gD4kwRFSuH=gzR30Xy-yGtBG5S6_V<`pftkX|c~o*{B1; zPMtcb$BrG#`NV%9?}+zUTOHW%TadTLT^i_KMIA_U4;!YQSei z+8HxuFs%u?QH0zF9(qWO+5ej^IyzbvA0H3>)7Yc&Sp#73_SoC6bhzK6 z!yX#(2=#hB_RqRUAAMBk;NZaN5C=QSh=(i+WMXlT8ux>6c7U?sP$tT@v$Ny!TDEKn z`;CUQNRR&@zsQ5{ty;CxHEY(4>#5ahQBQ5lmMy^>KpV8MgWaV|7uW*k_)eWV!DhAz zE#s>EfB^$|yup|wpJRYN{ z9=gb2i>FbeM*Q5YSu=i@`C0aAwVJ=-JIcj(8G;sN`v43Z^a%hAV+cA63X!sjK#3K%Q z@Gkr2h!^kphBUZ~p;D>1Jo&jf%11iHBNX}Yjy&>zIgcn0@yI9gh%(R)ola-)8jYq2 z>@aDyTC};bMx#OdL5EBqpb3mW&=>CP?2Lhj=Ns6tM1R5>33kq~#>Dznz9$De6Fd*& z9O*ydAkU0>9qaQB9Xe>Srh=YN%yaU%F2-sn&^vGqQbzk_oC0<*Oak9RqtUcbtJN(v z8VzLkTB_A*=$C7y(P(fnPVgdL znI3tRWgv}6r~D?;pd3-A_#gBD%jKg!c;*mwmfIoP!0pp&wJp(RwOWmKGyUrIdeAx3 z1z?5o!qL&u8S@xup+SQNm>YRqz?u`bj<8M;dmx-?96EI9Ke0aGy$bXi<9rJ1TZ~Uw zOQFBZdhRj50_W)CjY0o%e?T7u3>ddE{Mu+VnznX!c5T&abvt;lR@7=WY-_dGXf$|! z5MHCvaHv+RJD7Wz`-4ZkMx!x!kxq{5X#QQR)taXIG-p(geXfqoBh{NM^RVQ zUv2~1fwrNIZM9l$Tb)kV2DAh?Xti3<2xy}PY$~CTU`&+f9LyU|^7;qs2R-^)r%s)C zj{zERaB#r>miN*7_U$vQ$^Q!H@9&TEXq-#4Ju|IVi+|xh8`def&md1%y!L@DE{y9K z%ejxseHi#|qtofy0v;-rO5hSWfzfJgcpQ&^6yLO3Eqf6s$8$cdR{IFP%Vmgfh7j>KzKJ>@Ez01!$YtUAVVzFL@u)lA zwOXxcLuZXfgLZY2J=zRK)l8Eztut-NV-3a;jDr{-(SI7-+1c^@Add%_7nlaH=F(_1 z+D45Ufxn^NyLT`4UVj?(Vqb@F&z?P)?={ao_Z;@C*u#o5ZtR<|u!9Xwtc@_gVXns5 zhBn}SG1~Q@R;%r()oLHoYPEm`-~u>3Ci^ZLjplKkPWOaHqk(@?tJSjCXf$2nwOTEQ zI-Rboot+(f5vNwG`B}VcG@5^Cwc39mG|!KC`5Tvo^5pUnZ&JQar{nx~c6R?jI*}IX z#WV5lqIkjpiw>Rtx{MR;ztRr_((PuhnXw z(`Yp8<>%)$8V&AoJ+D@)`5B=#k96?(Cen*Mh}UYhhI}HO_%1@7PRHq#Wr=SH;Ze3I zUztw+$8$QJj=fr~MmwHWtJUymBRtxRb_@D^LZ{O`4)~~4sz+5S6~>Q;=j zioGxPXF8n@V-wcYEitFba~}Ho!*+Id=-+79ljuuwy8(yiWcaO^qyP+;}dP5z= zyGq6NM%~d4(Js+W(QbsG8PNPQ=m+xHg|PyC1@j5!46G%vZonG21=fr9_V$=(F#tF; zZrm8_LObliAip-x~?t2Vq*svk%PzBEcV+z)rSm)s%;ERFwuxF9?ZJ(}r7v17-&eEH>P5(*+-8aU3mQX@g>3^J9dnp5nA&ioieN~o%n|I z;@MUx%2a-r^DRDj@Zj772M)~IyLa!5fPjE6{QUeTuUWHZ{E{V0KAJIO#>h#NCbs zmzNh>R8*8uT3VW3QBjdsRaI3~RaFJ~+tTXl>N44v*VNQh{N25xzNJ-FRV8RgWo2bS zd3kwGNl8g^K|w)Oc6Royl$4Znv9Yl~2L=XiJ9_l!0?^2~apPRNcJ123-rgSL2gU}> z9Z&0YI;mRhUvulHWB=`^VngH^#4v^KvJ^Qw8+w$20?!JLvcrP=6v$HeP zz!OhA!S)MaR}SYPC>wha@USokL6?`jf5FeR}G3m1-g?z!jQ zLH|*yRL{z5rN{Mp{loV5_84cem%^F=`z-K7!Iy?T1UsB(;p`IUjX004Fa9CFhxPuH zDN|T?HqIj;mkORebar6A0nZM68}K@GIvvI%jQ3d2{sZ-F)vDDibLYix=#Mj)$VF)YAQ=hOH<rRXXSSw;a=zwuRr_*7d*i@xb zf&Yv%4SSp~VGO_-HgsGqehKlyW#J? z`!4hLVb2=26To+t&rfmI1%5U7mEc)npW5}MmtK19!i5V<%gV}9>T1LPxj*O7x6;zm zPAy%!)C226wOWn6K_|Ul4_mYkz#b&d4Pb9mK4-_h675GHeZ(>Zwr~#z`e@#~Ios`o zd=%tpz+1q5BJdu;)79(s57^n+@tm(xsh)W0rI%g_3=CY0G3w8?j0m>KVVe+k@Tw|m zsIt6pR-p+qaR|WjMEnO>_syTVNc=J|1Vbwc)=- zixw=)fpcc`ec5gSWLqF-0p4zNy|&yAt)W=VeU%xK#EbrkLS zI?5#6{Z*9Y74aes$KkvDP2}IBj1&2lq5Pj+(<5m8C)a7!=!>**!cE#WJ(9~te)i&@ z-$WVe`Tgt|IxsJWepnDoKP-x--Lvn|fn_0da%%!zJDf$qr*kRhMj@p|mr`MNCDX?} z(gkSX%8VH^2BRN>pRU*I+hYvCIU@Qig>z$^*~l`6wK~Yp0{=K?me2GYWLX99 z9I@tm5bF-`B@P}uIJvyMJhdJu=l-0PSVkeIbLr&f6o${%$q}?+YzVFPxI+sDT&2%n z{*|V5|CQ$Uy-dr7+@@uNZ`0DjQix~i{T407H~xOhkWPFz#2fP6GJZ3}nU*QV-=d{* zTJwL->2sMreeoE5`r>h#+vkd04^aoxvQUQ7qwFPv0_m$aPt)WVf1$75JVncf-lA1* zcgV*(m^MuaqutY^=*Y?hy82Tl#osRCF|IzefIU@WV&b7eg9gFy(PQYB4h{}&^m=`B z@CLCLfZPq%0FY^A1&eh!@Ov=!LoTgRqeh?|l+n=1$q6zJIE&!E|Mk~j_s`GIzgZXV zF^(sN6w?X+WD1xZL90E2X!%eHX3K}%mi*8=jJI#zKErUH_Qok%IyjJ4xZJVomn-6z zyWF;#rnbC>{6j6vH!lzMnD)jW^l7(a^i7YmTu)nNuW-4|`OV8f`i1Xbr_W#hjXvvs zoaXeoL})X6nU)c*biG3x#)R;gcl);-9vA9z)z#G{SFT)H)T~)EtOXu+aByg^*Xyx9 zhpYu;FCar=C-ZQ4A85_VdqcL@0a;w!T?Osf+1X(&*|edTxhtoyGtyU!(4I9tJ$O*|m2 zBE13MO1e>eH;H$-W0HP$?@L^tFJAqf77e^XD_nw1(kt`U_Kg31`|bt$wA*p|^0kw+ zhKk5N` zoYniH;$I?!wdxub z73E)>o(=SS;C_W`5Um(0+E>32bLF(xPtla_zj1$2;=>5f`ju5%`jx0#E#=_9-}XGu z^_c#~X`a)OzOH<=m5Fay+e~@sH)%W>AjT8hbwNFRKMJGZQ?SErTeV3^NlEmwWy?Om z_-t=)-yY*N__Wx!WB(4E;@%AI6qq9)XFbr91leueM?hNSk!6~}Pr$k#7w;dMK7D$x zqN1W8L3`#7^F7A=Rc^sDyrfXTWhLHCV2|&zcLjWdXHltke4eQgd-#W{5HKWHl z+w`sY*YY7jwC&SK-YeL0n2T@Sy0yg7(GjvGI3L1V9lSax=q1BG5cg?tM-KNgzzc+j z!#zgmV}%}W+;NicyYM+5nqEP5)XS%q120dgqZ{B45<7^mb z#F!hLot?4ogUmVZC*WMUiIbBPX9@Q9_Dvf!XaG6B4ihI%e50VC;F_)fXT+9Lz{E>?O6|a@$;jX`Oc{(??oV zDf2s&n!2#x16^FCav!uP)7A1nCTY-bh6dA$A#x}` zqkaOHqJAPD_9z&ir@s6<&F*!{kj~n_tl>DljZblp(Uz<#)wx-dkEopRQ zc_QujESh{gLusX)PFWWCJNVyMZ=Rv0gKiU-t1Md{&#cCYZ?%OeOD=CtpDT=i{0n>{ ztN9e^R}KxK@4t;>-nT86l$5lqO`A54Lw+9k#~moh>B)D@AS23m3Ni3w-3MJ`IP-n# zsi)Xh?Sl_Ki1S{^l;X|-?l0jE3uJgZoj!f~vznTkQfvPP&;EpODhb#t+p@+zl<5}p zLwy~1qL>fn_Pt79yz&Qq^@fZO*HBtL%$hgCb;HPTI_8tiV@Xj?71s^p3v?-0l-5vg zY6S(I%%lCY;<#?8E6Q9t=oaJRi&y`kdHt>#aIgHIXqPh1@NB@lv3{tpc^-jNtSi2G z`y97#PVdWxdWiI*UQ+oX3|s$?A}A%y=JJbjb91ooa~qod;^;o;${Y{_vH=T_18-^MeI zqP_x8pa=B9{B%oOl6ASnJH@ndY#4p>);Y$>oZeT=U}A)`_~({?Mp5v|JSs1)TlAnH zvy#qiPp9?X;XD>#ya%me&560ffLF8s{MmN_8!mk==ac@2q$=dIP=F{UHL95_y*t~ z;C@`Y&Ye3yo|2OCy{-Rd#FSCMKci@siz!alxQEg8L)rD>XPY~mpZ&ZtnHIcvgQf`> z4FrtBEZ_v#Y#AR#5f=*h9I?KxyrhQC@6KeN{PfpP^PIfs{hR!cRj$hah-VR^oyup^ zc3Y_p*!wxedzy{F|OZ)0p2te}eWH|2&VF)??++Q2KdQicMRhH8nNGCr_T7 zfIXmIuWya@JJ#`7&ug_>mJPi2IoBr%jvo zMp02wpe;Ctp3bMWp5aFP4-2DJE@8BNatvj~-@oeQ=qrV^abzSd8F-t%e)AknfBg(C z>M!+A13p~C=*Rhqd~SBXF6L$)Ep@rd|3SHH%y7MCSh(?jX5Z@jZeFJ0KdzxPx6f6Y z_S$Kh`OZao?orUU;(683Fgh?NflBhN&u`F$92gk*t-J?p?d0Ut)XB*S@*s?V*n<7` z+i%qyH*QodUAk0(eiin9xHrPQKj=FE{}AWN!2jaKi{C9REseAWU#zXJ9n2;lcO#4d zFW`UYlvv71zJL73-6$g8(UG*;C5-0vxkl4oJ3}*j07fAaPF%xiomT|i{x#29)#~jq z*Bo9R&vmwjYh|4BS>Oh7rtuOiM7&ApDy22Xh0(IXLG;aA=V|(DXJ|pc8-{P@Mf@omQ27ijt5Agg78-nUJP zq0Bhj?M+Nf+}gBhQ^z-(AJt@nzc&@=Zh8V3CPjgB&uy|DjH`s$5y zG~?}yw0v+d$M5?po=OX>kB9f-;scAxZ;betDINH@nZ6s^3B1c~#WT_=y)vEkGURgI zFkXhzw>>X0ePgTxZA)c^laE|B%G>;LG^IvZ-G;MEOH2E%UAuO;!`udP8%>%tf&2#U z=t3qKx~Q<$XBrUrfArBu8UHx<$Jvist;V@O%llz1`Qwj24zI4Rwu(sX{g16qq17&; zzD5|V85TjuR;N;VasBuHN@wW*!=y4X{>|=rnQ35NpX>bo!<@u=?f2?($)TmR<&$V0 zYm_*&^(^XS>w9hSw(?5robxo}tqZj5gCL7Ckk5Bi6eR}P<^g18W*+X=ty@=>O4SzU zz>OPY8P^c{L>T|jWhce}fq&RYh23rRf1DS{@`J!X^L|g9I5DoKrl!am3?bWiWN9+3 zb}`~zv>o;J8y!s%=PgXF*2{_huY}jqfp-`_fYFixcj)^WiBwkj=lg$Zcqs*pk1_d| z)Q(6)dqlhbT_MKzxxKH^*KeGoIlZp(yRr^|!v^mtio0ohPnDCC^K1Y9{c#5i_c3vH zi+h?-M+IGE(8Z;J-X6^T0{@@~$bG>k7WN9TliRp)WAF|b{}(S_oNSBt2VTp;g#!Nq zRtP1(#x;WWO;4coDBE+EEywsD6D{LJE^k;k_x-QmJWq4py-EQSqbVo(-uX-BE(Q3X z5JNsZ))?zA+VR(f=p!gwgyKKO_vM3vY3AD(=^L58m1)-ui=_3#qbTaK)vm}cH#hf` zmzNjrrnhl+c5dFZX;bJHV*G=@16e>Z{^PDsqehKXef#!>PE+XifZP{k#&B*786x1n zmF7AGKWRd!hV6=w%PAq;XR)+Kky%Z&USnA z^Ybr^8#fL-;I`o3Hf`Fpk*ueI`#*H43R&U5?)by5h+3_7knfP-P8s9> z=FOYm;NGngUc!O*dti1Ft#OswiMlGi55pmfak%;8So+;Jo#{6(wSxKcMcGx9ols5@ z7YZpm(R8n9z`t7*t#vbn;iCSxcn+BT?o~RxG>Ixo?r)7D9M)6;6XHy0joYF4FP;r; zQ^X5;F^?1PQl3Z|7N)e!^r2`U((*dz-7DO_#qZxTl!LVEheuQBX{$>yc7=t7*FOLJ zbLb(!_zyWC$nHBrZ)NM&t+jpn^nva$9{)o^LR5)~iK>koH$sOPpZ%l%%lcfD_@7}5 z|ApCAw11{;{2SmU`oOR#@*NpNJN^|<`=%w*-Y*j=V0;{H{wS8Bub9qjnCJY*#F)Ze zrY{LbS82vu7is0Np!;592#5Z^mGQ5@u^BD@C*QHQ!8|al=M`ExBux6ZTNJJLil(67 zY+tzm{-;iz%J_G1aA5r7E&y~yK*uP?f9Si${6AyH4A$}T>8GFaU2n*K;O-dae`jZB z=ySo{!iVnMxii<6ZAO8A;I-cH0lg2ym|*mvi5RQ+kF|<_{A=mJAe#C1C7Sil725TA zEM>%)jvx2pQp3v_|EQN}$2#{YTIU{3>)fLGS;QN|XvMRf))0qp!sDB%@O?aq+DgGiJPw{_o`E)S^+NMmWdC{Y@+abe%hQ#yKzJU+58q zTo`m?WAlfzVr>4wJA^Jg$oz+dgecxS{!za2KN9|p{}ungpHPC2 zkycE~FoV_dK_T?*JC~WhvF1VE4PMc7=eX_uUs6&MJa_Kgx5W4lIbf{+aVHr3UFb^3 z`VV%kgndlx|NHmvk2`;`MFkymI5Xz;AMTZehKAb2e|}me?VFy&eVO6LxQdaUt-(xz zOVM`Z@w0~i8zr=f`@e12uNWLkvwL1;7%l%Ggtkvgpv&LoP*!{e+j3XByLa!hoKZ?> zDX%${4*1^&{LB9mp{OH=wba8Towar=;;fd9?`Gv=d_>>>_U+3wzt2tbagC()p3!vU zP_E50Rp5WloH>kty4!8v??19Qm9|cZr{LdB*HEbuWwh;|352>B zaV7qvY=`(RzSTFhR)!L0;=2f?a#sxvr#bIlWBOh;Fqrozm-c7alm}P#e~kZ5PEL)Q zG-<;8pGO{fL<{=|SFT)9mzI_q#((txXP@_P^@WKGQU@X7Bf2_LBF5XjYFaG_ThUTGanGo!^^U zYyTJHKh}Sc0R#S_llk@6U&r|m^m+04FC28s!=?uMKgNH^3_pfykyeT}S@9Rw95-Z{Vs_FXQfPZ(XJZ0H<=5pn5>A+x`{mxaI*ZYP%2E@`D zmq>;g=wj)BU`~&^ZSab<#J}AC1+9rTtm8I`_9$@2?Nq$WcwMh-mz-C8Lx}H&vXMto zhQ&7-Rv6Rf_r6K9dR*nP;9NcNFYA+S1U>iQ|33Wi!>~KSc5(#n;TkqM9R&<@`Q@H2a-v++To4 z^c&E`g1&+Loy%~yj{mfXGTQ#n#M=J_+$r!^SDo(byZDFMf0hrzI_DbA>2;0H?@Oz- z|BLzG(b19jf6{WUfgX2zUVQOI*ojsb7Z)4&ziryIVg4`pf3R!OqzT0SjqCr~)_)lP zf&X=GGF=0{9&v`YZ$N+Zh%<>ZZyVnIN5$L5|FBptUm7!H94O%<$E_F?Mss>z=P>~D z0LIHX?_Q^sgTuHU0{`+zzSU3VbX7XBZERec4u=5*D`7hpz_TVEl(@5AX{9FZ98{7Aj=FVOI@$e_#U@`vUp=hxdP&|K)om z^}_#pHzS?}zO995mwEhp_c*Kg&rPYIbKA4&@Pahj_hky>2XwN*GmdEnxMF-U{^PjM zFM96|(*V{3!2RO)?r<9MPo&-G6=yO2i~cX+S)y0uv8=DCzZ9-lq?P_5(S)FTV}21Y z`T|14iFfH4?~)h)G1g(#kO&?JZXC+175}3D%Xa{9htCoB`JodEe16;k0{&q)8}@8> z?b@Y=4F}m4F`xh84l%}mtpCHp!ffvUr2d~G;lTnHN;q0Ri}VQnM<-bHf5;Z$d>L|@ z1sPSmM~k{xOg9hb)9KCGba-JJ?VFa&bm2ErqKj2SBWdotH)(c{>ol*|O$uKMdN;>^T0}YRn5e+NNgd7FB;J*6Q$FLnco#Gw@^C2OdcB-(J>Eqg z@n6e#i~Jt(_k@4g8IkQVIKsXe)_+Z!G-2E3D_5>G=mmzI zzGt3!MvL=*@PBb{80&x7HK}X;5Bbhb>2YRKa54FK1=GAe^cbG*x7WTbupyL&T!WlQf3wsYa z6z^6-1MXxTn&d%#;bpoolwn@BM*^4SJ3N5`4;R!r{tNtL|L5%NjQt-3`(W=7w&ZZn zo5y~f|3CQPgDTj%h5R@4#^UZbCV%vQ$p6&E|7HIFv=mzFiuJef@_x-E#5WNyp5^qz zVtM@*eZ_SCkGo#N@oQbM4~rvT?|2IMIFY{lGL?=j%%DGfv+4Rz`4oAfn9qHnQx5kz zux7khx?uj__(WRkiaJX5T01P3mJSG^`Mqz^;{L(39%~0HI9B4&=67rPM4UOUMLa@5 zA97e9{Lh;=4?4jh|I7Se;2%0&U?&82X`g@od397&lz{~VyMoXU0{#!q|Iz<(cL?Xd z^*R69|5X}oa8Dp#&qRYaX+tgX_=dEb$0SjV+4?X3W+?@Hl*BL)WpDIIJ$}6nUY9l7mZGt^{&Qwb2FEe>*3pnEcDfHHODDj89k> zh&o82vG1G4qdgKF1PpvV0UM+j@G<5!rV-yHc!)B^cY%LDuf#h0|NQy$8UN7Tf_uD> z|HAnXY(}}cxxr?e3I7i^JaPZCZCflE>{$K_=l{4vg#KUK`EPz&72|)SjDOKa5duFO zy_9V;J{#MZ$oP-7hW`(f7}p!!6B+j+j|lM}tUVX@xlIfE1kswIF;Y6OMA|emiMD*0 zz;^?8PmiJFK50xBQ5Q=1+*;|3_?POp!6Si|4G5zJeQwk8fng?eA^y#wqHQMeR@#X; z16<6$BYvY-l8rKqeamln{qYa^FGokmhM3b~n-4l*V8cnA|H}@v06QbThCRftAt+{k$VJ3Y)J&m@H zPhp(;4wqA z-HP{=+hgsUi1SP&fA1s;I&Lc0YFM`y*HB(+6{UuiQ`E&0x^b|8PW$K5(M6fG>$3#D z$G?Jkyz%nyE99yIp-wox+eaHsf?gjto|F9J$ujycyRSVnP*!RQ6e9M+C zS?4EgL_jwP`ad83VZq?&$htr3)&IY)1^F{qejVvTf>Mc+?Pm&S`x$>cXYnN|;priHzOXmP&~+Tb2fA-`Mp|6P+(xgMBL z(Dy+D>)qomac16@I^O;2_-+CVk(Tjq2{V81WV-!xVXf=`1q&9uf%TuGqa$?p@%>-S z?cKU{WBfzr8~6VL^+M3v<>m|Jx?16>qJ<3(C{IYnP8p_>N_c}@8J%mmszK8pOu z#!;xn_+LT0CZ+Lw0=zHo7tCu3L;d7>D%+uKi}WnDU$jk`);!KudCEMZd=dJ3C6oV% zWC}iEI(8{tNlD3_g$ozLPDCrzcl;~oI( zenbCz%(LNj z`~8swJEn`OyL?Y5C#jt8dO_}7=~6?>X~%>VTKPdF<9@}!aGpy{>L=4R#uLM{i2DzO zfa#`@DHM9rbnH^P;^N|KGiT0({6Fvy{asE?n>MwF-C)>snLmF%%l^d2$E%JXKd!<) zaQ5ukZ08I1+HwC2dF1o|dX4|+|G>Zh@D%m}7XHJfIK1PVh_?tCp7M83q)n!s6Vm9w zR~dA4Q8t~~oJWC&3n}_yDX#(YK^Mi@h*i@`_0P`|J^()g(AM!TmZ(RW`b&~NL~ z6?9R-XM{Kty4Udnyu{zuWOLtN@?NO4M&$lndmSy?X{%i_P7UQrurj{a?>|VtWlSo? z-LQHUi(NrM!S7?oj(rODgQ3F#vOltYNX9?-e`4V;>;gXb+;dn1^7ze<6_&SsPJVhH)$)CM=w+#I_E|uG3fPn!vmgxnYk#>W75-oo}f)@7+r8Pri`CkD{ z7wNQrdM5q6D4Wh=yf{)Mj~5kGm{Dz$E=u!j==y;|=BqF5A4baug!8^qv_aHQgrY9u zyYXFFZ@@tLZu)GsEN;J?&nuOz!2|iWeVj%qA*N>+N|%$9^TTVez4kEd^FfCXWdE?P z>)N%e7CgR%3l|#nzvBKUguyWX!@jS*y*=A@h5Vn)|Ah{jTF?K`|4s0Zx{G#jD7_1u zbDW{ABCV}Z2{#EQK0{*}Pb)r%q>Y$!WLN<V4#b9x^<+8 zqArxOtZCrSd30z&I&B)6Oe+RPa=9p9!jG{|e*BEOpbm(apA~6L@og)Ou}ni=uD^&^ zl&g%F;f1#Co037<@wQEDWn^Rov~AlKHhOsN58e;XbW8GiEnCXrV8UBp=l!S4Z{mJf)ab;A;jFjSVS zEXxaHOe)un>*tk9o4ivgV00R78ktBd2SspMs|H2WviHMz{Kq=RP%kTW;PUYgMY$;3 z&m)z5JuqJ(lsw1d8`GttE{1vunl;wZm`9G|{~%o|`MRgl;knsVl4t$CJ8bSHBqS_y za&p4Dy=k*%%~_I~AN1Xc$=j!UJ8a#Ng3OWIC{}25i^1lrmHe~*Gb^Du?wJ|rW$ zlJZlls5qyZ*N@OGT2*cYrg+!Z*LDfl6?QK zKI8w7-(-`&R~nh%4LCH%qvCzDce)YQBCX=P2_C#sxz8^j5J_uY;!M67(%CA*R{ZdE zGyKbAilPpiM_><{MK^ycqKxPY)?>ps_?x zjc4I-4g~$L(Eot@zmWeB=fBYZ0sTL9_5XsjYQ{fcCSY4zC~&*kJEPWgq71BiFyF8I zz=$95t-hi0pT;_F08M=mMXLtIkk8OK#{ZhZvAm8uFg=@ME|)PM;eW+dS68Q9x^xLP zV45^-+SI9K%a*+Ed-25=wf*|_gS;Q(AGRNad+f2tp!=Wie>*rh!1jwl{{!y-)@S_( z{QvY#j*NeSQ-n$nxGUdnJsa@@+yZawToY*J2hp@rVn(caHfDJ8VxZ#~Xia|Hzj`#$;P6WHf#)T&i0$44G{1oypk{rdIOczAfI z&z?PN(DyH#u=%`r@nZF(k3I_9Z+!j-`ET$JA^%mE{BJ>8m5hHS{QsI~?59=_j^%NG zqkD=K+}GznO8iUdf%8=##E_4IrZx;qqD^BGDC$yit!u2mqpPm2P7e$WbV1+8+8zAf zC!Tl$x*oK!fpL#e8pAUN<#Kt(vdqT|%qQsks|UrB zuY#twjLe|R`|@koPi4hHF1f1WE>)D?<+JOuq8chKsA0Y$c#H5Qc{Nm=tMt`W%w8F1 z`iwkv+5VGVOBXDcPMWzAACf{TluoVI~z|)$+af~xt>Fy;Sw6M-Kk#WDy zB~hlMOxip=lMc_$rGgCWd=;ewt}*AuUn{5Jb|0S$wHKumtXqnr=M`%2RW|>?b@}| z+u7OapwDl}kRj@{v@{ETpSf$A;t{wBzF} z3K%KT4%%L;_r}<#@N)Vs2obNy54ilkI-hw9b-C*5>crsSV0XP<-voDd+qG+F4?914 zy&j!d)4h9lb!cd4o%o0Czn5Qrnb-d~{|En9=zpqn{+IEOwg8^CJlbXJn=*dOh%Bk^ z4~e67Lox1SEC)Vql?}R+@QrdrS`jDyyLos9_j%x7&=jX1o=N+^$ffLfQ+WWT1Mb65 zmC^T~=St;zXHfvW%{qyCNPWUQoq1chuHIR+ZA=!O^)IBt%(};+nwpxtq@<*&FTC&q zWW1f)wr%V1*kg~`_2|(q)3F|Qr!ay2zI`574*b4HII4V%nP?c29!`5xTieBp%` zpvN8ZTy^$)2L}fx3+RJ|UO=J$5&9os`%jkt?HCpow$PUTFA4v7GHifba}TtBRxn zzx%@EVMs$#K|~Z-z+{L@A6eY^ z&p3Kgef?}|5d5e1JSP8aLjGT#dj>-L`KTY3z~zn3`-MKg*(ESW=_~0k2h99&A#gDL z>WpVTd9CAK!K+E#lBFGib{$hQ9#2t+w--P6Z&{cjd&acSS+4(ILfRnh)c+Z%r)Yfn zr0;RH`Nte}viR!#&W!u}?z_*7`{-J=YDKhe-P(Ko_1DYY-c6e}wH7X1D0Dv@hYlcn z0q6uU|Dz*_{NLyEasLzcKgwB_RXHgs>1pnk@i)fdGLD^8|9&=2aGC#gdiuuse!6GI zJ8*iv(>~#cPnXtwJ5%|52bZ8H&;XO(ANDw=zI-~h*mWO!JCc%O2YJvP1=lB#ws&kYOq1qB7k2M->6>fU?r71*D5-g(hAYSf6RU%$Th z>Z`AI*R5NZF>j3?J=&F&!RPaF|I-C) z)~p$FI{1GxB2CekMg4DR+`Bzf#5?1s^f;>C?7Uy#Kk1G`LEoI|&h{CY0lVUGYD)KG z>bub=id|=;NBHAg$JJ{c_DcH<-kfbaLeqcwRH{0#zIYuTr_0aJ-??kouKU`wX~SI{ zz&@&K)vBV)53k34UU%Ppci1$)7$@+*T&GA=vq zr1T;1w_!_YKl9(jO%)G*q^Ng$Y43;!=C60$=coGyM?yo>kIr~!oT-oRX6#cgcpaG_=;D2@2Z`Ws!3HrnQ`*&kbsN^mAVab^D z^YgcD-@d(9ojP^6hgH^iwQAK8*rU&b?q1EBHDw$l%l|JN_dg^5<^I12uh+}`Um_wR zLhgTCwrpA7;_iPycXrMTCv5l|*b>r5&iA68d#1>|!PuvdC*OHk@)7U%x-9>@1Aalz zA?bh>{GQ<@{PjE9HRY}p_0z*un@A}!m ze%^$1m3rXJYkRaja&mHhS+i!%?X_#yE{#5R$&w{y+~07+4W8R?zun!vdv|O$hz$R~ zao7N;R;`-VwryMNf_o}esuZEmi*t9l-2b>}(V_>qThNRPr(=xF8=tPG_F!Bc5A?JD zIS@Cs*YO~npkGYizSR}FZ|DPgLcJ+p$2*1{Zj!FB#0op6=l(rrZ;Gk&(p|fp0Q`EG3j|-l$Ru01f`=1vrTGW!g zJl10D<-+H;YSqfqx^-(e`r6-o^No!8)vH(kH{f?Y_uO;b1!#5d+?ll_HXyMRfW44- z>_=cndj9NC|cIgM1#qwb6!F=`f2{rXDdwolp(pG4IDd#ld)Sk!Hv_8kxvgw&& z$#vkdGBY#3{^5roIKRi;-Eww^_5H24-s*Ys$tStH`M;g(gIv?5O@ z(GfN}pcsPX{@)5;fBkirf`Woz<1E@0-JHPV{`pnqJ11D$t&~}h_`N!x}n!gEl=|@khFNdB`@7@Jo9hY{} zUOx@cZ3!+n)qd}JTupy4Rjrtj71sS{z&;}*WBROFvufKWS>w^)imXtfg6ME?KjWpB zUTR%&#TBw=d#>D}Lx)@^PoCr)Aa}qE{E;bfZZIk;iu)tWfAYyEZ3+qsjs^FQ?dZFu z)3VgNJ?;NCeL?8-^v8RSG>ms(By|6-6n{SY)S2&Wo~Z-aP_Dr#@>3@K>DzZ6QEzwG z{>~#Jvs^YkqgeQ-9x9N%p2^)vdsNCaxJCK?vP_xXkLq+s)bxjrsrBDyhb7lJb?Q`l zT3XuV88c?og2yjix^xV%uT`s-w?&HU{AjWrFGy)P<#kJ74;5` zCw4ic-s+kxbl;hVZ`8+k<9S~?J-vANAIw*u^gE*7>ie0bE5Aw#&3l`$7nzI^$}>eZ`zFTM0qb63|nrTI?B9Wda)Bab}d zZqlR)I-p*z|4mFxG&10F6DCY(lAWEs!P#!VJEhddxjAau-DyGaf4^6nqyN+7-Ne0f z=TYhV$#>XvpLp>-O}bR|elhSiWjJ9=oT1NebvY#En=&O`PrGezcRM2A!4EU0fBmfZ zec#lB1!`9R<7)C9M{L^B?TC~ysq0~BKR7IL&=vh{*1#0CV_CM`i+U!9Tqij>dHAD` zK8o%}9QQPqD_1U}VZ(-ud-OLf_VAtXI@hjc%a&pbgnNM-HEJX>z^JGw&J4svL_{QT zj7+Xj+jD4-l%Q|^bOs;JB7CPi zSc^+cOpLBmr;e{%w{D(?AAZl$4aOo$YtJLz@fKCy$&^j5E{k z@AXcT{x$D}^b=!$QrBb&1y4u^oK5J2c~NoB@A?_ugYhizaNtx?c!{v zoNoV@Zi~RWSDJd;@gKC~`xi3LMy|7E*REZ?>({Rje;s%J`R7NYdwInbS9l(O{BbvY zK6-h7l$$nfn(Na~KXoBfU=R7i3orDQEn8N2K<*H|@WKl(NKQ_E*9mi{OFeK>eKj;g z_tOdWzQa#*JhMiGmIAAH?@A3y=gf23=Uyi?zStqKVC(~Xa7$n=b%ImDZQ&gp1@_E6 z@SWko-;@(5lQn^v7v{Z`u2T1(*=`LkCnsmwnl)=~t5>fc=QCr=l`9uTe zP~dduQRy?A_7+Z46uiz92mO7!+fnh*fuQu@imAu(Z151?bM+V5r>Fme!RtUd2A|*S zbwa(@J4G#c<3zYJy^}dPIg1xBUVJ@kdtiU@#TQ3jcG+b|Q!5|=Jr+Wq9olfg#QwPOvqaz>8b*SeqSr)j;@#RCHa zr+4l=Cb+;i;w8=9fpjL1Gc+)C#_>#DQ@W=J?15?M_u}5k2hD(n|N2?h>ES;lL%rWK zxP9dR@So{w+4LOI?>&>_?!?T@%r6%#SkT1d@nDZTwsPglkqsL*^tEl<7Mo0N=HK-6 zbeW6I`K&+E)vH$zFXzCkR;`K-nGf2;{jn~WtMts7Gn?h*} z`^w+{SDpsf7AF;ROrWz)1;v+U<_tv%U^;o%Hndk7mr6flZEh*7m@@MvWR?bgtNUf$qNh?z`yW{E;9Ja?#Pz zav$Ke*IvtAAlM1?R;W-RvUKUvahxZ8?6Jo#O;1msQyl!$kDOHBj>uH+_exhE+@0a~ z^!wDiQUyj+^cdy48S|lV34Q-S(+1)tpP?yw>;rr7z|&#uC$eK6{#Cc%u39c;BYRiJW zaOFB_hYufqUdwgj(b=d}sZwO~=FLS<51p&aFTdRS?6c2YqehJi7fipqi;RpE_;Vi+ zdIpJ!i712!4OqH#X@`P>g2RQOZ##J4Z=T6g@AtCd4_tv0uqO<_Ia9;JgaLdBf>AN? z;1qD5(w%j_jZ^-zHN1H@anp(@N50*Yq1JqvTkQOA;QxNl4E6O>S!&O(`Ni%PIh~Q~ zJoeaQBG=(O9_KPDR;=i~|Ni^k9Xoc!2J4?<+=n`J3ZP5ZUVE+kvdb>RZb(F#GG$`m zS%(c9c43W;~oJW5&Q~YWGy(3qBBdXQ~f+19#&M?9IC~n=VJ<1J57clcAEn$}1NB$bY^blBK?VE=wKUP!N_} zCqF-b*Y4fBAMDblO9g0t`SRtXs#mY><9t2qJLGv?x^xNc_CJ%8^Mc$5+^A6_Pu;q8 zeHANKjB>l(@$i8=cIH^@V zpYHD8zdtgaGoH!#v$(jpI5`uH-8l3Du_eQ~AnXF{*sZ*=}=%`Zul{epf^Ung>aGk}y5a=CnZyaZeOO-0cfuXno0|rz{OG}%g zlqyDZBH&mf@BOtvEt!(5W)II%Gat%U)9-cQgT@IzEE&H=z!i8i=7BwPeo&fwvV-4? z$p7KpA!&MNsp`W7-Qqj8disY(uEV}Pb{8(Z@Io)= zac;Qb28%P8oW(2Dv3~*g{`>E{pjj`z_@d~BbG`^$LgqP_ksnn2HrE%KbXf%5vq($=)q8-DjS8M*8uu=8!=f9}^Q}RjE?N&3x9hX;apO zk)=wN;-5=y+O+BRyu7?MAzgItoUw&xLSqx}w}iTkaQM06TtPv>{^aE3A^rOGy8u29 zddQflSFfJf*kgTLg3m6w;dcGs?5z1p^Ii(a(s-J-9<-AQ-feYcx?6gf-p@py{s&cB@7 zy?eJSB_+l6>#x7U3tH?EwQt|v-Kte9kqvPM)#vlY&73*2W=cxRC~Q)k-b&?P&z(AT zDmyDH>+2OORFIA56cnH@ddBs^pWK})C@9!*^ytxH@4owP zbwlqtBZY0XjvYJ7eQMZF`Qe8jWNn|9mv@F9{p&mU0dy15RjOUPwkJ9|8lCY-?iplk zJn+B+eZ%qN$H(O5FJYKuU_4yvaRLMe*(u|e=&u;GAxvr^Gr@B_ITxos$@yFJE4)&wcmZ=f35ZTde!J9hZo*x=c3GSsyY05yWPO0W(aM!8`>;EX4jJ~um>aMu_s~NRm76tdR*j`gmp0h4 zWy@^`4<39dB_(BeT3Xtx85tSxW@Tl4nw_0JJ0~Y+L0(?olDxdU<@x#fNvtvR^Yb^M zGh0wlu*>1iJE8G>H}TH2JnzWQ&)-Oy(50N5obR);vOZ2rOMB_ip+gU@Teq&|j2Sbk zUvteh=sibsZx;UnUbAQ4sZ%Epyw7d7-DcIVU*B&(f%7x}i*C!7Ey5da+qTX1?6c2W z+$-C)X3Usz$y;x|Rk?59zKInpR%CB07MRmM3?NjlTsfjag9g}F z@Sva9w{KrJc9Ke$F8%+G$2*q}TsV64DEdqyJHGC^>)f1I?$oIhaxpLG*U>9r-BhYn zDQp!+dcEFA?j6PUqys;~eFkR6n>+%?kg}Y(XTC*Rmc@LQr==e+WF-+gCInl#DXvu98A3fvVdR%E>_ zdrRywa`sr;s`qmCi8$sF_MZujyXc~eB#u3G_Es;w^iqlA8T+;8op+uOJ9y|+qjSM| zHekx#W;~O>X3d&-&R2Qar@`(?>(;G3J$m%;Jn+B+@O188y?R-Bd3hH0vitV!>sqsB zjsO4tc4z0fXJqHjov!WMw{!0#^wUDm2)>#7sM@q?BWvdeAAHanIB=i^tzEx5f+rJP}V?6Jq(Lx&E>s&wo{Buyff4jfk|AH%W zNo{kt=2L;`hXjc^WnNvbW<=x!-X+2Cz`KXT(I`ezb@48%(XZh|4 zNnhwasQX;5Kij(%(!Rgn1-RI19~n~q*}f-) zXTnyblX`FH9bT+T#f!r=jFEAtZm5 zko3($-iHOh!-zTQUFdsc$akg-XZk9^vx!VEPI@{lv-XwRO321)gf2H3^>E7ZmN3)IE?3(&>+TV%59 z8vV&}!<+jj^H5jA<#K@|!z~olxH^c-;M}e~FJHdgJz~TN?nZIrxJz^7$dPhBG6Y|E$0o0w>E`|&-XkL;(UEXRL`29tagKcE z-QBKTJNI38-6iiON|cat;^X7pl`2(|ddbH;o7hE8AfZ1nx=9y=>!$-cM zkJhPECwWE>ojn+A2BW({Is6Cppx67>TW?95L)+D=RV$IjQWvt-)vH${Ulmzea&oft z5jN+uv$HKD*QE^7AZNpliKEQW=fB;#aoFy)Qd3i{7A;!%`xsq4Wb4RF(bo#0fdW(D z3SK1Cfy?aGAY-pqt(x31fh?Z$;j9-)6Y70FpSxMJW`dW<>akCc zoW5MSaxxCelqpjv4`nb;xPFdv$LN%R!gC*;9S0cEq7#CQIs zapT75sk^xgg|fHh1_eT1}yGoFmeB}_?vZ_5=rH){u@!W|A@S%j-gMJVo}N8>$}{oc zUitFnJ=~*My?S-@9KlV|Zy~+c>-Fb1<+xlfZ>dtHBpr^t;41pTk&%&7PMtb+c*oAZ zGaokByp&(IY+27`mtDrYQoo5SSw^3Bv=rc4>xL#1rmSfxr8 z5BpxUgT2;Dl`2Vl7-w#`TReIB7uw1HQXgq4gL=?&qg>Ka7IvzL53}H*DBY@TYR+%Gh}Hy4`LsID7v2=XX637m(*M@L6X ze_^YcZ;Tb}r=T0lyovr0W6+FKJtxR`0A~IA^^mo5EH;$I z^IoDviAeMzDT{N0q@_IaIz75b=+K%p=tzlhT%7bve0+R_9@`Pv2B9BlhuaOC79r!6 zF<7ER2^m8kk4MHmWtC-R=uBLHy5#Kg!qlh)($@GY8iA_)lz@=O^Hy*T^GbLh1lfK6>R+tOXM`KRX^N!rv3oPL?S&Oh1!!{he zXU&>5Wh{h@f&Ui{R-AFpzoFAZICA7j>)B_Y6<*!Um5}1fl`CVHkoi++2z&|peivMD z0k#Pupox)@k%IH!Hh2RbIPnx50M@`4yu`#M=k~et4qbolKI55?Gu^~lmKE!EyJNYJ z!|is*@s7SQapswK?oGySKDIsi#@#JiuUJBl$0Kok<4zwuX(_|hfx|u|c}<+NTvHxp z#K*@=I?_^>`NlJO2(d>&I#Un%_z&tZ<&&TEZVI zmQ@0~rliC3jOPw9^GtkPTpTts$dr9p>MullD=+Wm)nb85xPK2Z=N3IK#lOg*MQqyqj^u z7>SK#7{)+HfCKZ5e;$E8#l^)5j|dOSm|#v|j$mGg#zFItNpbfRG7NBobpvNHvC(1Z z=U;39$hbxSmc5y=W5Y6ucc7N?dcC}tjEIOR>G60t z|LgRn+-^5_rAs=#k;bwt;^mol>}X>=9jt`T`gGZa;>d5( z(eBWEe53uu<4J>ESBICnO&U{{4&`6;oxsut`Y9$RCV@L5=sWt0{$!jv#-ofA-S=jE z$ovAF7!Npo*8n^nyrZmTn8&d}%s9~NZIM$UM`JF)BlloT=)IM{68;PBv!{g27@HBu z%aC0odqZXu7Z-G4!FHmt{^*NJtP`4t;{o0vB)_Ry{H@vJ|-N zfH%*aYc*jR%d&VUEaUNbO1s_e(%Aoxii#>585voYXTC*5MDSg_Wm(ulD9c@e#F5|U z^Of;>y=6&@=R2X>?Upnq9eIs6^%A<>Zqk&Eh=`Eyg&$#F*7Qc`5cCtA)_pH90-lkP zk%E^7XRvAScDuRXRGwWfS2?fOTh8P0aOZV7x7*FxUwnCw$0I&6GEzdX*Bj)yBad`= z%d#qvJ|ZH5umX3eyWMVy)89y2$Xk{rWfS7bW76|XIh0RGywmG?{Bfb>lUBFS8OpQQ z>#e~5=$DP0d8icrkIR-fCIalUk z&0n#wh=pHeO%A-76Og4LlR}=sS^*gn}g3Hx+r-@0C0rPbJH;#B=wj+wGQTuh+{n_m7`P=<#^OTb3oBck@gdm&=9i zW4wIxdcB0_Tb6~Fv^pKn=kv{!Vd70aM?nD0=q^}u6jAcE3fQ5_=<{ZWa^9TH(=KX|+Vy(jbfK45}R!2U8 z+yWUCa%F6>iY%4&0b@bSzhz&6{c6rH>U%=}EIg6Hd;d5^zKcBPl1nau*Wo{8zXtg- z%pYqccpOa+;1}r+;b}DQ48LvWYH(g~UDHpYnGq2Y72R%kMey0c+voG0@AY~OZeqjB z_{x@LUEuL}@ReOIc_&Vu-EOypocpjWi@W;7yWMUHopCOgDAS@)%l8ZMmSu_O*?cFD z$K&yTCtk-%c|7a<&NBVwP^Kz7goqY`Uz;!!Co>`?z6&b(qcF4ov z`S?f1J90p1fR@*kh>eY9z7$?v^QX+W%)dfo=vVqz({De1gVP$91y>z71I()$Z&}tw zc$dp1-moJxG?JMorfUF`9A{Ao~~!xQiH#F=skO*&^< z=d&rBGMxF$vnkuWJ3~_rA@Q`&Jd=-S{?q(3|K;=f_{OvJ3H`&f+wB&wyzCj}PCc z<<7$6z#~RSN3(WfjpgUnHIGaGL0jN0F7SH2&>`rOpRNHH2kd|!u*a5Hb<46Yal74@ zxLhvr9*^fzyxZ*-?{c{a5yaN;cs$}Q%QDY3iSv5BCZ6;hsqm*KAL(_Tnr^qdrq}Bg zufNsgo5|yNrfj~|bh%s-I_o#(5aLOz(@T4NKA)6Jd9=^x^Eulg?V^6Z;m!Z?Odj6p z1Lyylez7d;Qm@x5o_?eM=tqyoQys6zkaOJ0n9<_~SeS898JKB)(C~i1ld%B5n#lSe zzE;b_zyolCaS+2kBD^uOQ}%$^|1|n6=rN(61pXs89W-bVI*o=0`Q4qN`)O%ua%LUf zUe+bt>55#(>|ddu!hRq!eC#MDCML4y#2805052$fJ@aE^WMraVrwPt$epP6@p3{W} zX}Sa*0~UB-B(Maw;FR0#uI2G~YWaM=T3)aBA0Cf~`-%SHcDrjE@AZ0XTbAYYb=+=u z9m}%nc)i{_5fKq}d_JFekH;hF`0jGKBpqoe%j5A#9`fN$8H7$xIy`YYuRIgSyYae= zx+X25X@?1kGj*AKCLQmlEyU@z2gOm2Zm0P#=@`W_>av{?lMIm7kx(b8qpy{uL_#aIv1S*_N&-e z#73Cjk3q&PvPafT^n>Od=^Mk(2yKT)f#!IS(bQ@$8&|-?Y_e6^@Bl)M6ZzjQuO+;&5FJ_V*vdk zBYXLsCjgmeIM39*dv{Z>Cq6zN`KH(!f;~eY3%M5joR@69113 zM(`ef-Q)4VdqU&Ecf+GVbN&H+(|r#d1TMe^*c!OHT&{X#F;#NGtcDb*`y~OalD&y_)eUu*Q6yhX-VVt zdL=aZ_-^Vkb(nW&877?xNkbXNo3zfnCXJ3WeIotha=GZItMD$DOT5S9sYgHSzGj?2 zPk;fiFmy-e9X)r{(tIOugeQfTUF7w8nGfJ)S%%N68_4~;A8y|E*m^SP+BCa$Kk=Czw=E0(U+zlO~0D{mcG|xmvJI=2U-NIw1-Z~cz}0;Z-P&Xh=_o`F&|vW zIzh|tkZ-^fK^tXVz&xP!G1&J<{|LNdJt_Of$QN(E`DPDihenScEqAT{o_r8GY@9hl zM+4bD=OYkKf&a*>^gae6PLXZud)3&pLiWY_R%kwQcjS~lpO62C9w4t^&V;9g--Krs zzJ>W;({t!Jbj-lPgbmzocjFQzN;IidsZ#T*RjXcCyLRpCuejoh8|v4ue`C|8O>b_| zqQ%W^+O%ocsZ*zpJ$v@-^7!MA_k7}sCwlhl*RSWGL4)odIdbIvqeqW^V9b~?_y_U4 zj~+c*LgMwic_uzI4f#wNk~ietU&iy#KaYP9PdU0ysauz8LQ|heQ*jO*h@trg`(`H#ch3=%#C~xu#X! zx^=IwQKLpn`ZqB#u~|$^%(Z&Vm~jF;{PT~Vb7j8Jd=)edUX?W&d=hK7i+w&Hb3U>p ztm(E23kUcf(~YlQ4jcp=VPuqH-l0$gOjNuNvoJ;#9DA+FB|qQl0$L2`Ex zccid~!G0F&T=ubiJ|FUYgZu22Fvj8ip!uwig76+XulaQNR%l6m2TiA+8RF|C zwDmW)FC`^q)6t_xlT2SECnx`U@ZiB;4jw$Xbl<*xKkeDGXTgpgJHFq#b?Y~4*RK6! z#fla0E?Ttcjc>mBX7q<2emG>@xN#2+9z3|`gAYE~r9+1fx7Mpy@5Y3Lgr<622z_$9 z-PbS{;GLKoG+kpX0Ph+R5fRmWJ|F94WJinv_UE9HvL?`San=OrzM$8NE;st^qBnt@ z5q$~Ppy&!=&lNic=Xf4~=MlXIcpuJ=GwzV}uqa38m9?(c+Y#JHH$>}@K=+a3!{b+*w>wKmAD%pUGQXgp zASW*`FC!->=XiE@_L0oY%>8L;X*(D{J9qA!`|Gd2PMbY@_UO0Ye*1|bLx%Kf-MV$# ziWMt1@%eo4Q;Y}J7OcToyR)8#FG8*a&kHTA8Wj~)IU*v0y*2hW(Z!H4fIfiMK@~ky zbR!Wiat;c83DyIg$?V;`w>gJ>F2?{iG{iP*LPCP*G&s)9i9Ws_m%{UC-391A`?iAn zdheAz%xYe*7nw4$G-QaZo&0!TvSi6d&6+j45xVll7heotwQALS;CgCm>ek%c+*Dwo zaT|A*ko?>IUJhWGF#;_*di3bp9Xob>w{+>!N$m!<+H!9^(&B>ATMX%7(Hy+FGhz9ThK>e6Yi(pME-I^XAQ;GcVJRCr_UIQ{18PxBG35xi}*uWB;K;hkk*UO`0^RZ^w=u z+s4GiFb)_4^|XAIH6Qzj>?N=!lrf<9{be4|df@ODvOj#}c&>>IgAL_`F;pEr%>Tp&8G?BPX4MX|TV zey*%_k!`~BfcxynvM0r!Oxdz!8$SH-!yOkaSTOd;ks~Yf^Yb(Q=4bvl9IR4KPEN|6 zJ$vTPnl*pxo+bZaaR16~-MZbfeEIS>($mv- zvX1@Vg#6#m>EMfw9zFW&?Af!2HEGi1I+x3Zz6JUf?3sytG%_+0J{p-2Iy3B}qBqG| zK+gMfeoEw_=mudAhjl7?X~t&i*$jY(AAZ>N$Rm$f*wMT4$}7db8aBr;$)RmP3GL^M zr{3dYk3!@P4;uy<6sZGZpo4*uNpOKRQIi0w|<(JmzU=J|G(Ye4jv#YE9=0j zRjc0W)2C0Hh=>UGJ!K4_%dPiH&yR|V;_OEv=gnecV-qlVpwCqX?Fm4ypLpU4(aqF* z0cSM^$n%eX{KH}`&6M|hGV)yX80wTQTlT6q z-gx7lg9i`(#903u+JC#^9JF!k)~%lm7%<>=uh+|-5V}OH9jmb(j*gD5#97s-s3>Hj zoEt%(Smprm68elRh;xVVKR4cZ<8Q|Q+O=zQ50~&h?DcX!6TV;DW#EiII(n={IRD9c zAM_enuR!}zM5>*TkZ{Gsi4*TmPEKC*`^$Cg{jQTIPpOjyr&K{c{-i3%JE`*Vxwg;C zIjM3o3siP`zRJ$9eP&v|N;{JKm-4B}IV$B)mP$Ej`{T(uVYNT~cwW%I(+{~>1u9SX zkLf2u`i?%NKTn;s`}g-=uOSQBwr$(2e*OBj)BDB94AFht+7;Ow8z=VL7_`l6h( zM^6qRJa^WkAH@DF_I@4qQ_h+N(tM>-2Rd4lOp(Z}ITupvrrJDZqrsCW0$ou)29rEvA>;EVG=H=b$+t=}X z)Z9sX)tpJY)Yq?VR&%FrQ~&vRms&CVpxV47Rqa@HLLJ(krBV*$s1rx?RMv@nm7854 zW8+*pJqBj`q;%|51M^A^g5A8u|H9X`H3|zI^gj9_uO;O%{zDQ{OnxtJka!v<9RB1 zN0!?0Ynod1-4XTE2M5&bS9Yse&uv%l_us7E_~&Xhvi(o$>6_=Pp{?etk?oeK@m-VD zOI_Beah=wv7u&B^V{TjJ_b=SGI^b_#txoS(+22hb((`Qch4XCE-o9GBSXBDkRw>)- zatVv_x35+sTQ67r8ho!Fx#An~1Fv16o^QJ{I6d{4cA7FBZ3r#f?wi%>#SW|0=-XDP zflcSB0ZrzpC$F2UMzmX`-n@66di$Y`>fOgTs}BZlk+JsuTl<8@tp87n+PyYi=umb> z!8w^D;DweiU;c8FCQZ<_;~bO7L|F^R#KcsLj*jN+4EoaSJIVP;WTDvFLMIb>J!f-& z1OG4hk3JpiU)Iu`VaLuUGJlM@=yP1G!Lf&dzLL=Xh=>Tz0oA(x`s=SvN=kY&FE8)J zZ>0;&x!^qgy8Nrd>buwWst*TmRTKNHSFdzmE4U3_1Jg0LtrqwXYO+8*dgW~O=oPco zzZ%R{BX3!*UhJ?&{MZg_)Yy*2c)k_)-F$O~&T^db=9%x#=g@catx&i+kgw1;yG+LM zQ!N*&zV*LVk6kre^}A+{8qxZf;C#A$|DAf|%5Nkb zcKu@in4!N(^HS#|HQ|o6>eai}smTv)R5PF5uI9h9U#>UyW|JQjP17Am3#A|oT&7si$tc5>K% zVh-TUAvPqi2QRjFH4k)F{0}xvxc7s56|_wjKksAC@W#c(VdI7KYv`<^$IW@ST7w4< zZkwE(yx?pV)c99;%5^`csL!9@rQW!Ey}iRM zBXE17!S`xJ>t#W>6^w6`lO%b8tCZc*ez)rh84E?c+jyq)2DMAKQO1~lmi*3V-FAPx zV?2i7GkM{U1~>nY&|}8poJ$utq;j^gg z@vFa6Pq$oT(;o8=GM05-U7r~Pj0@(6$q#Onb=QtxPsn_5wyvO{AbZoMO;cL6YIQ9- zLfEZ{iHSiL#5x$c3-TAE8^^gT>?}h6WgozS|7RQnobQES;!JU)Mvb^rinXttZNauO z$6c^qgr$L8nfpB3K&uamqpOU<3MPfhHzQRubM zcz@qI``Ey0jo@tnr|kGq?N+G%jpqxFFnSLSL(od5` zMs>C>D=TZy2OoUUN1q22SrGQX*l)#VB{~t>rVwYvm;>O|%^f;nuYte)_FKV!?wm6B z@u26%nFDm3S!Zk8!q{-(i~@Q-$UH^nGk*N|PHAarNoVta;66P5tPwlZgsyAV*!Fh6 zhT@w2Zo`zm4#3>N0UpVI9^Pt+dg7Yz1#X1T+_=>L&7=tq;fd@ri>lY8wd=I&DOA6~ zcije4j)eX;nzj}lhL&mb8nkPz8guKfvcBnC|2y%6n*Jzp&U);+19Wi2VR#_yBXh3<_mW^YMca=H8~+_^WjA+BF^A~&GN^QX*2qo(Xda_f2pHlQ{NW8&>JCVc>|Yt?f% zvlgBw_yXPxyJ4}PZWUcFW!m*P%Q0p9Vdx)=rVWMLW?*m633fVDwq2gd6X0!3p3so` zMzvX?23-4tJ)VUIFH$eu4xeVqF``YOc1x-yCMMQi zuwcQU+}zxw;r>thu{`1T$9G*Fyfs5%&)8|Ep&@JZbC$9cpU^bxf59;~0 z%mJh?z6^sqh4D2o)(qYhN~3Ydu4An#^iG`oN1&`C(>VT#|9Gn9BAfPKJxBgyWSbR% zIvi<3pUuC7#*gc`E)YkXWlZb#hW>*dzt7(Ei_AM_{(+~o%XR#V9)H1MVfZi)yxwbr zTJhBp*^dv`rKhKFoHAufcjzs0AkLSt7lvJWeb)nLu%gSAD<`%hYSpU6-Egu7=1joY zv13oC6D?HO}uxb8rwcVcMHME2@8$?h0=ufuOr>4wky?u#`9$iJaNrj^>oXh^&C(bCjN4q zb&}3eoGc9GEro6HKc`eXB_AvT4vj3sqHFHG{&zJ}**O6Z8@8rO5 z=Ql^)%(>>9nWuPnK0DJRhZ}NTfS$0|GUis~3TB=-4ICYHNuA6s?UU5B0o&Bx4H;qc zLGaeAR;?OSrAig-@v#PA?TOAM`e&RuM<0lLH&E7*`}{Z`2~9_*ll#z5b0)yL`s%B# zHf`F-Si$xv`(EHbV~V3T*zCln1LwOm?}HteOD0dAd`EhE`nqs%9|!OE>4=?v+GqM4 zyb<5Y4h?+#_$2*i(`49a+Cf=0o&mU5+^PN5b=Y}?4D72sU4{GwG8r^PHP}zm*D5g#RvWxp(OuV!F zk+-Z+{Tt3#k6-=0>epbN@RVjQ^q24ary|Pq|9b};em}f(K=zEob;-%e-#+lb1L#Sk z7b_!zFMGb= zKlkEbuQo0&4jsG;upb-~6H{}^k|j?S6cl8H^AG6m{5t!ndZX7yKmMCOGkqP3|MI=V zTJ`cB>(x6CZ&9C*+NBmuJ)oA%Ojb)jJ*_inQLxPwiHp;4@b zp`FYD&@kkBrY?WojxlGxoh~%_OlZ>b?tDJoyE7fnp=qDHnZ2%gg8zi@Ao5=h`fkcF z`AvJAWy!xe{;#MsCN8ubXZhed`@l~$pfA4{UW~O#P@W=i-dVP@JpN_Mqg&MOr1Wr5 zM_hJx_P(i8r?MZ0%n=(>$Y8J^hutjh7!X|`@Sm+PY=&}nm-_?i*RO9iYSf7HyrH;{ z^Yj14jx+mc%q8g8Vbh0ut72ke%ErdVa>fhWT^F}#(W3s&ojczR_Xk+>eEGs|HLioE z^@ipfFd?G{|GAS=?g6h*p#nB)-6KYf zu<*}3^Nb(=^`4ijY15__cTh4;IOkrbOc}-wV~BeR&@bf>80Xu|viBAl87XVur=EJM zRZ2?AqHx--)G65qeEWgTHvWg=mw|z!e_rpoQO%!xKpotYDQl%Otuc(Q+~&opYSyzm z)ysEmuzAfsJJxDvg<9hM_wh_k|HD#Id%zuT3y3d{CTFUISUgG@mrd(5= z{#WSHGdD6X&6hDSu<;LSMC;{2?I~2JvklI6GUs(zr#^XVhdR0^J1i7Bb?Q{X?%lhm zwQJW7J5lCr7`9|Z7sz2J2>g$@;)*N0*e%3{Nr@6AtkR`RTO~_kme6+KKWhTk2G~*L zPFD7Pxtou>E4ZIV-%r3fN7laVH~81SQ>RYtl9Q9OKb*#+d%twnVKt$%rl|&=27bD4 z3=H1Bf3sTtI!7Bq%P1-xaWCAqMh$NE zlZ;LJ6WTKNcDsE=)o0?!>#tA49{iuwXS3R}BrR+mq14pWU;6g#%U;+;$RPE7K*;3f zE&+6u*$0CMijR-?+exfdtCsLMXhLBym|AitFF4rs#2v2_fLvlTkb~J^B*rLD9Cc+Uvcilkv#RyOMCsWk^V4rM#I65Td&^i zy-6+m;>cO8je^`MW}8#8*8Y0z9yPx6IyL+To2LG&!F)C7+8;$8D)olo4s9^7GS3n+ zZiDFOdi8SG4eFf-x2V|@_Nvw29aDSOW%%{uk@3Rsp)d4u)=oHzX#C@b*I8E0^@sDD12GDfyuA@dS#9ME`y z?6(w^-_+-CgQLyzPaW5(SG#Rci_d}w%F4>x_Ufyz-VXnb{ZP&ZV>=iAoV!A~ml(PY z{$i^KwS8=a)~i>~@c5xl;+r>bF8GgqEA-yDdmX!@;C~7GGFQ=g)3&RtHfYe`@^$Oh zjX!zvWL^;N7308zY5&?Tw9@oP5FB*hOz5&+&42rVN>2%^*wZ@rr30HY)%1beMRsD- z)%h|93~RYqjcdQw-xlU2(=KNy@q7#U{^s4A)Z91stL-aJ*gB5+#^z$M9GEu1mvP4G z=-wQ)>f2-Llc#p730*c6qPe5mtdz0I813Ka2lecYzu0)?sJpm!I@@fXNo%}`3k^fd z3xY{8;|Tta{$qZ^Gas;KvB#!eXHj{jEg@wxcYgii9+h!CJkw9Pxw*;n=g;q3x^!u5 zXv(=r_JQN#;^bUBI?C|v*i66Rf(y7a!_%-~L-)JyzH2pX*wEU)e}9qqU%7H+t7grb z*w2FYV_#JC9<?tIO@efwsipK)3wR7&8_9QW3}o8^B) z``q*uegDzm9qQoLu%?vLJ3R-SEI6swe4irx3HUUi(E`=K;SXxiwf|A0ZdvKC+u(@_ zO`A+;+6;_89JpPr|M9rZo1LX@FZ&;dcV?@3lMbjidTo;ODH|T*sTM!W*zDin2Q{=g zYu+TO&$NNiw8I%^$8RViE`a~`f0;CP7$}Q(<83-%r!Sf&P+x#go392m`awO@${wr1 z^%S4qw0~Ut_3FccJLG&*I1YYe{rdIeYSgH~xfsp^U>^niXMM`uTCs_ViBZ+7R~LKf zoQLn(v#0yhPd}A=+0B~Q;e`HkUZ6pP2H4Z(a62}p#ilcQpUD2vHRara!T*OIdZ_8~ z&={0}J|SgW&#a-8ZUVK0PAmPtTQu{vJP+t7bgCQ}}53 zRq!8}4ZZGXp=AR5pf(v?aE9R02KCXP9cstQ6N1OT)zK#SBY2`WdqWe}s}VOZR|6aW zC~YGg(Rzi(5oa4hpY6UI4_?!L|38>?2LJ5$LbxgE?KI|FFy5PT2<^HI{{`<3&Kr7y zT!B6Iz*sbO2J+ftOt)EwFLl_UraZh&=280POpY{r_U!qfe*OA&!GHL1?o2FEq6E4L zVk40~0M0{#$K26Us}`o^+(r+gC?|UVoE_%=uJYx}%YCKnr|CON1^@MZjL1GOdf|l^ zZpqBd+;pb);Y>^Yx)kv`d=CK%s&gBsUigL?n*?P~A(@b?76ajXM41HW|E z5%tO)8`X%LSIAnJF)*<40>L|Hn`{_4VHbG*p#Kh$VX=>KE?h=RzWV9?Lu!1NwQ5MS zg=&C|%LVG`mW$Qc+t+H^Z`bc^SCP-UPx)s0)tS!VEulZ&zm_m@1~=_BhCf{~ZD1`- zyKQ>1z~(7CtP8FqB(G_kgw)${gU|rxXyg{*xa8#I*&RD}M3B!O!1?R}z@NaU8$BRx zD;)#1V#9=eFfHdV%E{gr^k4J;V$&ZRZ`hmjdcEAq!Fe0edFC9#%$YOq%E`&u6Ha^K z?YE!wCb^A1`&>puTv1kI47Wy*s6LCuOKf_im9ku*QUL;h7JBnYCO@g?+pg06v@xg+(k_kP&bG<3;4ta=5AFFU zKaM+}%^I4}jxVY_C!F|B-IOoS8h6dS#e9lfVWjYsP8hG(Ft*#|tjCm38-D)iu$=!3 z$EBpCEF3y?XiNBT?o+`=B70zLSwXLTKA)V0(f1N@Z(;v9WRS@$n@~mMkf>BGxiko6ywx#IV5M|Nkc=>vDD2^}}6(d|~N!A*Y>{D;?g_Qqwxe@WZ? zIAP-w@OtHrO=|wz2gT;WxpvIwTUVs0ad$3L{TnV2ot=C_Fy;%Hb?~6M%key=NzjW!+F%c0F@!ZQ78ym~r#o&MC>eZ{)T(@rB3k3xQ znP4@n7gla37EFxKX{{eY0Bj+1csDo{dXO&Q)KG+Uv)u7jH`v84L6u zKm7Wo_8g$$Vqo=3*G)p3^0I%QacSJPmB-baeb`HL;Ju?QetdG^jUT^5`jdAV2L@L| z`dr3G5qXM@bB-hPY}(NoU9NTNnHzqQejL!~M>V9`BJ~2}*rWl+oozAg(PfSAut9zL z>~3{zUw9@0b8>PH&6zXj0ey%41)Rmz_rCpe*$24vL|2L@#;y0?1tyjvP0QfI_cbipq`wVV$+NdV>-lCGe zPcdlvyE*J$AP4vJ?~XS3`>b=I&!q1R9y#Lu`AmKtI{Vrmr~83#MS1#3;&r+pm{G17 z3!OHq7uvBFUZe&#`cdZ9XIm{3cuT#eohH4ry+Z#tsE?oAsgk#cC->%z`JzRO{-y2f zvmV7Bgt;TYkN@cL)TvWP?gco_y%^ZLvL1TqA$QxhZLxKW>{DzVBL9hujKsz{XDqQh zC;ZRVS6^Lw-@bhxg@X+2{j8mLT)lPoRyCochM(>S=^NdTJb(Yj0hM*)_rm`?P5;4v z8+OPYhqYWHb2hxt(=C>Wj)K6PvDs;(dhfCABCr3w9kySVe0)T`+{Lcfv_aZoaM<9Z zgKjv#$$x3wa;7!Ue3x=V@ZR)?q~n{ZPmc@p?hmE^g2xPe;&V4G*L`Wv0i)ZlRxg|O z=>C*8IO>-&J8o3(J!;GT!g1IXT(M%sQ<0I8)wB*o1?+3 znlW_u@3tlnb?fFGS8w*(qVX|+Uj{z~cS2~j%w-N7GymmGXYxDaih4HqUDP*;(|9|< zj0^v}J+9FAd%DG9HSpU1s6p5Mq=sMri_n;tJ8v$&Ou>guYRUuK)UMTGsnH8hy=Kjt z5wWqc=s^qpkBNyXQ=&wPgwmx;Bma?uTj;PM&rD2A#GaKEA0IEgPRIbTuDId~Y~ITH z7yd`veL#mobbmNw?BM^a+=}eoh71gh=bg}zpLdQ+&od>q7 zEsN96xlKtN{Lh!8_DLK3^Lpn^Lbsl2wM;#E?N0);r>(r`?_xZe=a} zvy6ekO%|#VH?5F0`72#FtACH(|GUYgLf!V|>1ygj+k^1M^qJjncHcSiH-M8N_;?yz zcjga$4%B1!O)$IyaI@vz_|&=dTymP=*N#k42zKc*}@eC^K7YV*%wi4(Z?KnbY9GgIgRwe*8b7@GHt;OZ&$u$!dHDcpYb-n!XFf zF~3f}cbnR@C@t*0z;K1yi38vp(MVz)6E-^@4&&~qEFY}y*Y>A-h`pLXcXZ}Qlov!38*11HlS-fi3^H0AR= zrX6QD4Lw<;M&1lhX}87TKWz-;->hD}W3$@upH$&(!g0HI@1AnwjW=>%4E8tC$wxOI zdx+>jhz>M%%Q*wd;9j-3)R!tEm1Gtw$`7=&Kqbm zWxU#Tvr78@c-ZFv_Uzd+?Y7%);|^WU0LVFDY!gENu~&i)D0^VFYu6SXXz2UB_ud;s z|812Z+vY9%U+4;5a>*sWvSm?qh(P`mTc%8z5(x_yZ4U(dag-)zD@? z%NQ8ie6f1|mep$NBim*F?vHSO{P*Lgjq8Cn+kNC50|u9ZL-TJYy|a&<--7$%Wf@0y zopzrTKL+fyrYsZc_IBB<#@@D0@E-gh+;ou|annj!;}uu#t9NWstG-DITLOP3Zq5MHlW>_Bh^20Q-o@$txjj2uW@^XAQ6@I;&q zzTt)&#O{g5;}Q8M{13X+=oH1r#|!=&`afyXq+8R|(^rLq6P(}r?v(>-LPre)KTHDS z--c%ZR>1r9?pxG1FYOoHv-Qo;yE&8YP4V}Xq#e#vUp{Z+zl<}1nW@tbkwHCs!*V?b zEKgs+qW+t)cC(e0jFSmcf1$z?UNyU z)Y^HevNw+IyxheTG7i{dW}ktzMqWZ{e|$L^A`0&yR`xclveg@Ya@bN!yw#zo( zi+~$xp(oE=zf9;q{RwShum9C9Tjjg_pOm#ly?V!1wfyt&@&Cw?Bj5Gw*N^)#xO+v; z0Klh1uDK5Y^Jvp?>@iDc}EYXi=xlvR(uCprO{p~BR-Ol*a^=VN!N}TbO<4i+Yrzy{m`?`LE)8^fT z$Pth!ga0xIh+Ls?*{^ops+P_;8n&$M=+UF|1`HU`6#S2fh~NxBBK8TfRRaGH{Z33w zl=Uxi&7nhwat1gE|2YeD@Zdpf(V|6e=>K`=ohR~7blw5P z-BR^x_l;`hmnrA;Y+9%j{D0V{|MGu&Y&!on^x4KY#&BT#2KABtC+R0mckO?*anAg= zjq`yxyIjh&y|YaJvq`UMnaLCMy~|ehB5UCmOVyBOi*30=>(w^Q9Brelv&8@Tg9Z)q z)Bm`*xWtkrOCqPlPOaEM#DfJ|nsI5!W)6B zxAEWfoB3z+?hhUG*u-_+Dsbmr;BVpyO*uMzqsR8s(*Lhm|2p%VGRSYduG<3tYu>wTQ^vp6g*1--4XMk#2f?G5cA2yhcZ#-yQDR$CQ1!>hlrczZ19VYiL8D&rLs={*rG3CzIBsw`mG6vh&;TXR{h{lTEjn1D?5lxy%dT=+aM)s+9e?G8Z6!IF`u|Tp`J}sBw{C*}iHV8aBZdG} z_#f~e{-3j!oPGc1n{UF#|D(He)ThJuIq1KmzjWW&q1|TzINALcfR%nP6zab2vMmh! zKcMRhlm(qDRHx8CyPeF_%-2sfUn;NzhVXK4-nCtQ_RK!D=DSpNY+tUNTPVsF3S$Gg zEpp(!>oZl-oa5@)-lF&Q4F12`*)cX9|L5!rX|sF_{J-fN`&-~2$>;w^XMB;*raTks zxgaPl`0ps+(O>@dI_ZnfJHEqaHKNr@=}Y<(o)W!B^B?oxJQTJaAn9jJi(qBciu66 zuq7n&e|_)v`Ico>{pOo*!sdU1@jtlFOuq$ax&0kjIlbvW6KCE-!!y%=C;kIpn>R4! z653^MSFd&5F3-@d=Y?+lA~0hP7}|rQOzgQ`%^bQ{Et+;zZCH?|cCN}0o5s76 zPN;1wQq{(VDQe|cDeAkI52-2l?^M(K?@wXFGiEU$J6Eq5oC?kG<~z z&!X7+&)d>NAV6rLgen$PRK$K&il`tR0--5bt_>^dU$0;RDJs%?FACTIu>yi#m8#M^ zNl5R#hxtFhGjn$5-Mk5J(ChbkKjwM%+1=S`JM)_}XJ*cv6B%i{?Y7(O-Me=un~#7! z<`1=N*AA*(yEgIvuz3XfZ?oAz{~szYF80dCuYrGqrkU8%>yPVGzsqNYzffIET+AJI z<^ue`j(?g&nQ3u7$Q3u5Ip}&#d zc1bHwix2vr604t$6N$&Fz(4W-v5Nl7daCN**?*?$tLD3jXXz*Z2j%3tmHmj*mv^(S zQI}>v)6>8+r(>q&o-WenvY6pzdiaL9&BPwRiGP8f-1&=mquV~38%*hSNNjxf?8V@p zY+?ro2Zsj*1=0R*-MV!H>(;I7kM++huDHSpot}_?7(IHleZqtZ2LGcR?7iED3>jjF z-E7!Bg1zg&z`y|DAM&5Du>}5K&3{|gWd6(e$Cy4l>NH(U$IQB|T;JRVp4y7*)f)do z_}@hMm+dRt>9m-M_Of6>rYq?!mv!>}2Z?9JJb1$0d&IlFj~HoiTG-=wtNX9wwL5;H z>y57a#Poj0jqfuCoD!>_^8){>FY1`&JQ#ocZY?X9*YxLpZq~7}(Ub9a?G|r$|5d!%ZJ+pj+8NRd=8e;}N@3-AyA2X+P6?hCK~fdBV) z{RjB}WPF0x^=qx~>fWi%H0;hDenuSlrt0P6un@(*MO!)~_|{vo#v{=IJ9y1pGcbbtU|SMrD+2-oc>lMC>pvwD|HS{h<6duLy{`CKzw7m<$K(2j--YnM z>769Ebo4Z>&)oO)G627m?){x;6)+CGqa6`&9ZIZzFo&Y9QBEN@4eR^9v)8eZ?KMO0YX2*p z@vmr_UKdrhNtON<+K+d=E#?hBdjb9@$)x{CON}|e6t@fK!D;L1!|(mR$Hkjn_tDr* zV?fV?;{CqI#9Q6=8-AE8(EnP-w3)#FbLXm_{}TOY{A<|N@T!+fFPpXe=DYeg3yvvG zt$fxz_?^?KE-w!2>CW*zeZxBP#2&v>Jz{+Ub`-qTgAV+^{r1~L|H1$J`T61eOP%20 z;1J0Fzy?NGSePGdKtlefR;^n0fddD+*??@*rVYt|wr$&%_J3jf2;j&5FUF71&`?aM zq5nab|Gy~uk8yTR)Y(e)Fo)YV>WrA9_?_8NXT_YsDlGeH;f!bY!+p>hF>hGa_=oJ; zSJP9({E=tHoFOv&tU*k_a5woXw{-P95N?p9^_T;L-lH#I{zmV2@1c3Y^nNGgzqrLc zeL?)2^k4N&g-@f7+|rZfB|okszU$xkS*7I`hQsqW4m%iaahAgBxc3xJ_tV32eIhNx z-_^@W}ulK;f~73aUg!^7$PV`ylo z{lI|(ZtI_LB_$=cQ8dG{6mf^W<8m49rI3w0P3;OTb z-WB2hDbRm?Y(lwIJ#kt@D=c-V(+>`ZyQLhcw+r?sj)%COu4>Y+6y6$^%cJh#i@`gB zU)(l7O%!B#kN=4iiT`2wPn^#s`3LYnAt52O|JAHnGv{TOU50b*_Pg)C8?w%}9Xoaq z_TgarYsZcqwuc{nnDqZdMn=MJD&#*QKS%t((*2M1r6$<_+OlO!4fcO6_~-hd_Cp)0 z%cyg$t*P!gjHNB~@Ra6RF@JcBIOq}o>RedGc{&>adj!z4l^$0jVt*?ThqvZZzu3Mo zO>CK#EH=EBAeKK7Cl)>!Lw#!QP+11*7X#0TnFCLYslAR6-2&#(2XKLgVU5%HCzRbN zr;I20AJ6#5`v1zO&e2#V{|W!h|F7!Bs0VF~r2p5#^1Je5TFz;ivt+oz8IRX@LA2h(Bx~Vg4Ht z5#fC0l~)}7`t`&5j@$fq)v8rK%a<>=ef;sq_NShD3bx;2`;KfML4F$Nf0_RK`S}_0 zZ#BYy9Ppp0Xqs7PdK+-Nc)Sl$ak!4PG-P|2X;zK@3p>auV2sF3Efp!Ji$%<@1>)Gw zT(S4lEU{y8n%MMCviRtw1hL}DII-lBSlVmAzVP_F_KFF2?iFv`y-&RRz)|xb2rFmZ z{&VKQGh)s2@m16Ry#<7SH~O#skMN|Yr_y()5oTR*JnMbVkQjBjm(3c^^Sc^5^n8qR zn0dRmt%v@ih2>|&*4h{=G))|8XVa(+OC2k@YhN z^{(ok>xu3|V%+Kieso#F{09XE`9NI+bi+YEXuW#%9O2>NF4&I2{sG(m zf?S<`{;NjoKTQ7zn&T5_@?@n-p{l?fV7NvOW)&f?)m9uG1KNS`km9IbO`@7 z!b}D zG$8&**^ngtZ+?D$bs_($&Oeg;gSYa}u=Te3#WP}3kAq_JeFw!mbRAN6yz9^Wz54s) z`wpo~3wQ59F|E%bv2Szwf9!mUHx6@*;(`h~ZwWps_P0WDWJj*pwIN-6Ju8uPslyhB zCwKCf95L^~<5X9&p4|VhlAroM?SUi2GZAk?^xecAV@zTn{_-1R4!DzjK#Rk)p773b z_%88H@s%Gxo9S@Bl5zOETi*Ol^(o|8Q~qDgeW%c^=Evq>RtEe zXL*0K+i&8X`+paEHhRLpAbY|)S0#(h@5YJmmnVt6o6^LIUvfm^i2{+6T0-kVB}Emq zKJ-`g2~Y0C&)H(u@WbNGF8ptLUBK@*@mAMg>3Xy4uL86JV*u8ps>UX;7c1rtiB;o6 z3_a7k!t30q*s9}O`;7J7z&vB$^S##jfjYV?sPi;0=x3SKg<7X=O4Y2 ze@=@j79YJ5E2i~2Cf@5UuXp+WfcXwTz7UR|J>AXk#|6@$tGCok`RkTUkNgkLVM2$> z)ccQ$_xcQjY^fA3*ot?*6-Yzc2RMVhCTrn0ZMY^1F^xVBhVk4*wdS zIjyR~>;9bn9F?~Ijnky`xL-2z|vj@pG;(!0s z?}KB+{Nb_U;5V?%TlqX9^uKL>Cq*C}E|h2Xz!))WK#Z6*P-11lh*+_BbevfJ&vW9# z7ZSv#Ny*~-h3R6?$Jye@_B_(hop3Z?WF{7iqWp3iD~OLe&$i$Hn3Lt+O=zw{!ic^Hej&6-?L{=xAlMJ$gf9_9?%a2ISG>g zcQ_mY*#8O%2_f6>z(4c<>ik!Y@IPm;Mw_hk2k(0O>EGdRedn-4;$%D(f5C{js_~yv zD!zCp#XX;>STS!%oS3KLQ+p4N6>|p3K7x0ojqi||m^t*ASp4{L@!_jyNT=hjkFqGg zv-=8ZyvR)}BRz4@7Y)7dH61gs{<-Su1X)+cKgUrSW)F@L(|R2hZ!7*0>j~%&To?1I zt0%56zN`Ord$;;=SiW<4G<-)mnoF60pANoJB z|E1`Ejn;oM6#cIb|FZvEaBgi6{%)y9-5>P3O8TE7=|9Jx7X{o)>=XVueZFh$jPZi8 z_h#4M#Ki6g#0Py(iFrd~#gZ}S#OkLL#QK+$#HNWUbk1?diX`#tmUMAyPp(KhQ6#bw zB_D;ogzEZ)fq(oX|3BeBPJGbsl$dnS0m47{p7(kmr*fg32KKpbR9yG}lKqJP&-iym z12_)uh(~d$9Ehjla{7GN>Pm)1xn;d_dXyIBVWDxH2H%Io)|mgFJb5zV-)^@zhJ8}l zX@vbp?A-#x5fKr-x88ay4pTn#|6Cs&p6Z2ZiB_M6Kc~xKtalj){e%AB zNJHV*Exd+x^4Idy!|P?#;~Qm?W%zzUMvdg(nEpfmXJBApJ@Eg5fq}u$-Bzbg9Y5^j z!v2NJ<+2YNGzj>v68{K;{a@@CKyMJv|AP7V_xGpuAILu|{m;Dr>lOb)_3{R_@xi%X=(nnh|FPqH%a$!k{s;2^SU(L645amcrT>=JKb8EO*XQ3Q z{ZA2d&@QMiqt5Vrj_QT$0Cj|CIi9P!trW+^nzcO^43880H|BU)Uzc)kdFf@ur^g*A z6vw{LBYM1jL5A2gF;#4MB}uFvn<$n(5-;WtJ11rjmi=OSpHpJuJqHQTgyp^`E9J}O zrt%Dp6SD`M6;pd2A*=$g=s)1!=M0JyA3mQb5|34lfACW1CsYqZAqy8Hrrdu-V!yje zYtUI)XTTHJ6W5)GyAg)#Ue!Cr8EUo*$2G!R(n7pSZK(3+xSqb_n;uSo=5L7iNAQpJ zAE(m^`xn7sVPRzdQR#7WLiXX-TWU|j`{@j1A+eI{G-3WKjM-K6wH5p zeSK;DU+F)L{PN2$yrf341e z2gkY%K9*3Nf9faIV)yG!@lhyngh%xo+(buMgLHj z&L{9cf?t2<-a~}{Nj(nH7%&_7e2F=LGdTV1M$xhs2Ejr{%wD^`qCr zJXMdf9438%zr6H%b@!8T5yxx`u2;gfip&3K-pzE#Por687)7_L>ThVf8uC9n{fGSv zm&;Y#&(ALyvj27K*2Nwc-X-atrbQApTCLlh%Jg|ABv; ze+2)p&i|79r`PA-G5>udh45mwjie*oCa7<>Z$^D`JDBw=+XnBzyBUw#CMr&>e>qvC z#XyHlW&XN4$7`7A7g)Q2EGX7Y6OR{&Q@eA;@84vK-5*1?CsS;hk}B4ZOBO30PY_EV zP7n)6##329-{%ZO-vHf?74P;sM%cx;0UAD||5@?T3(3{szfi1xI+6Ml{txE`!0TfS zK^pJ%##%qd2JOF%@lf+qGz4kLXZcNH(HhS4yIUSceD|<=+Gd{IX2@HA_EZ+oI#srz z=csOLvi~(@$`t7TYzFzapr9ZaEeiGZ#nJU(=x+9F(V~SfbpQ14-=FM1Vy>&>f5CzU zKD~SQCfj%Z{{Hq39XdFgHETxt59`#aL->dMGvt3X{hzG=#7q9aRDAwMs)2vwfBC<0 zr*YM+hf3e{u*S2(KgXlGJ|`BAh!?+nn0>)9pr#J}qpb8iwNG|(nMgZZBF-Hs634#J zCq4>e_vUv}#mBEEi#5+AQNMsp-aFk75$`my`$6$;uan}#=aWQImHZFm{~Yy!cOQ_l zK-_<34Fun#>Z!WBRwqU{u4Db~DGfcY{_H92pXoRBocL)~mMF^ePIeLJAE!;5c9)Nj zPg9r6)yUV^w_Z?CP$=fCN_O9`Wy_YnS6_X#qeY7rcIbb;>86`N=iSJEOiYXovJsGv zt6#r9$-hBwR!~q7t^Z^G8xRmc_;d4l7v3q@%_8IcTlGG#|2Nkc-|?-g@BB^s&UAeKP&tn2-`v7;96b&Fne!hK zFXpST^M}NneNcU;Z`OE2&xxPcWQpQ@ukruW)TvYdfjud^-A?x3LqbAAp|c9hHUR+v zezj}Yc0r~G>z=Ul_4LzEgV%9$@PqzmKR-Xxe+zkO%p0)&kMqB<|BmxtkpBe!G5>}9 zo44{m8ASiF?{vr{m(C9*5|f*7F$6 zgSk%h`Y`aN>Qn#5^&;`ebyM~D!RZ_Ev^wT*oIbxJtX>AA9B4`(zqT|KS zYqPz^e^yr3);HdG1H>G_^-+NFNyzDg?|&*RDVnxw6^1VJ2m&;Tii2qRo1#l#bRXJk~L#Bm0e~xK=;T+q_FU zr>AY?iT*I;9F=d+h8(Z$A7x}@eD&(9uigawLpP_=QHZq~9Dno&@87UtLua#Q%^YLL zj0Py%{>>8(^zZsJcppLwRg~aqW$+Cra7-1JGs6juJ!-?vz{yW zC$tsktKW5hSswH+?qis1^f7Pv9m(KqJ|KR`C z`4_VPqU3+P(*Gmrf0~#V<^KQfbtLO^L9|}47phP9uo2 z$2zj?kH5cvZI{ah-RGqD1afS!{RP`EqehK_eos$(UvTVi4+scw)U8{W)_+1mLdgCv z^q*t@3p|!O|I6z?HN$@;-UaQ6_LG;S_3X#km+x}C;Pz)f)E)bAJ@PlcBb@{>-{@D? z@QObG-xL*3*n6kPVKL>tBc#`D@o3DYlg0X%QpA?Y>0-yiEV2KyTybnizKH!*@=-Y{ zWi)1k@4r}we)aRJEHQsrteATLG2(sD2jrYr|4;re{txRrhn3$6uNXtsIBL|j7EjU~ zPKWE!davp``-^Y_j0+lJB5iC3r8e~<3AL5y=omFA3wi$@7|Z&Y&Kf^@%8mJ z_P((9+pu9nSFKvLoRIyz`s%CgS6_X#O`Z2L+{lq53IEW24*T$!D=YesbCdqiqX_)l z?RJuX(B$8|UjGOE-}q*l!H3en1NTPV0H1pOaGjZV_ck*9^>$<$Ag>hDfb?s{`YRL<0O|+87tQRH~)d_K=yZp z!#DlA8JFsm=z{5I)~6YV>q~w!{q=O++R|fsGLNd;FQI-bPoJLL zs#U9&E|-hee!!<-?FZ+6VZX6cr%t5zyl2mzwD%PsAMd97O>^Lj?3n+Cg@wWX1<`*g z`r~idzX1Q^=jRt;x7%yH{F6Xev;SG*`+vYQaDTcc-Q?<4(q!oKMn)>^5L^7BH^%Sxe%*6fBt;Mv17+x4Gs>D zfLuQ8vqMig>;1%;-|Mcs4ss7<_Y1P0Cr_TN693?VApaH-5kdCvp#L25pDh0j`A@9> z1Ox=|`e%*izo7rX$pVFKv%V6=f@l-(ct#km7g=Z0kL!=(5-pc;Wq30kx3sl(LVP_9 z9uMF*YhbLH*7G=FAN+@bJt~ho24wnLnXPTD`Uma{hbM|94KufRwjyre@-;UCgR_s5+yGOevYrf34$+RkS~O%I9zW2#_xRhkI{C!y%F^OK<`6XSXglV`t@n;r%970uFjo1LzgGZ zJA1@_=gysdZoKhETj$Q5u?Ilyiq=D*lT!~Pf6Rx!@7{;QhfKV8fR{#Dx; zcfyK^T^+O5Z z>`4}@o=6sJ$EJu6pGgrPJ)bJpy_hOCyp$?7yezMeUrD7gded9!=Jj^E*!)(y*o?cn z=)3k#-w}qdx6Swz$C92~nko*`+&nRzctq@_d*ccU3if^S$tT@>e0;)PE*E6eLc_ws zf|@pMN;WS70|T8n^Lxh~caU6**W4hfIBzrQQDU9DsN8H$dCQ2i^E~GG||uA?Rmn;8~MN6>jcTr8EJ7mzH9$u z;D1P>STrn2to&z+_~g|z@%6izV#mU4v1fg*IItyO9N$qO&h9M|@dt}Z?gD!p8S$kw z?}ko0y3)!-UWSz4$j>Mf`5EQv!t)=$AQytG>a@yhw1$?Glk>%^uf7W9J&^gw*{qO& zfPg@pTd!ZgKJ?d;&G#u&rr06dGjile+o(~aD&ha1|NN&9WI~}XG#~)#4($$`%|`lv z>~=e?|Kt2uty;BU|ApqiHPwIX3IBux9WRUv+^h0eVHkJTJHE4@6)*7D)28>KiDE`S z(Ej6uJ(K57r2bhcEmbb|=ev=Y!YHL(-T$a`zyo~H_YCnr;DJz%8U4;sdYra?=kn7y zIy6x%9+^b+eEZxi@%tBf;>@l>+Cu<#<^G+NW5pU1>p8-@iCl*Qo%N2^{%>=b`=+F% z%bj?10Byi^Nc+qApv$DZFzZlmGG|| zcq5DvwEr6x7UqK8Fr5E_{UgW}Vf`QLKTQ9@|9C6^Eb*Tq77Ug2ncEa?$j=LP>{f$w z_c)gH6voXo-M?G?A)^e;GxorfbNZgrLb%V5n5 zWdrt?Kb$PKOvw<3zR4HKC$V3BacAxSuCA=CEbYjVBTuzy)26YHj}Pp!Kt2sJKmP68 zw|8B8?X^zWF@-(5wr$(m`}XZ?tEi}OlY6!}n*ZK>^Ud~#4I4TlA|jywf%Lxt|B(OW z`7fV;tSSG$$%6ky!;;jS@V(y@GmCAuQ%f}>(FQ;aT zBU=lIXMrp>(euA49luytTwHu~%a$$O?RGot-GxElRqfifY0nFLx6t>Gb$qAOXV!henTf35h}F=l;Ur0{KE zJX*(){;c;E`0JPct@87nL6Y`^{{{wU4~VstgY)5gXoTmIy z2JroK?|DMZ=zB(hr#Jq4Xp&g}LYnw(Q@+SbEK_sDzbEhVM;&;d%*@QSuf6tKd(eK^ z=7Nke_G%*|Bcbci^^br2!+FOYcM$#m=%bISTJtv?&IAt`GQ@`S0a*OO`~h}j!2dx1 zk2+Hj2KLJ7bQd}#<3h4AKFk^rOZ|@eU++^?2J}O`V_gXS6S!YAJele)W^WOl z)s(#Z|6Ny6QIUJ>*s&)%bm)L}|FF7s>xP7ehSIzj@(!@|e*5jWldX&P?c3Yu%$Y;@ zFDNK5@b1CcpaTx)-(e>V_zwyS0{n~<`xj&fc%!^78(|eyb`rpj{G31B2IQk>{0s1HA3g3Q^MY7sBk9o-nQCL{G^TQ86 zycx7VARwSN*8CeaYD9XT+qP{B+&Tsf7~sHJ_OWBf+RDqzFG&BdyY4z$ojP@B4(#vm z??6BD^Yim{I-O+y0_PvW|0wyli=h8l{{;R`>@z+YC-Plln*DI+aKO9!JL3~^IGq0M z8Lz+|=so7W=mRtR#i)K+xj%8bp3B4eoB7B-$z?I|&he2R<`XmfLqCed`Sd<##N5FN z6bAEg^hfYQkUiQwIhFQ9Av^SUU90&s9cX`CT-+-+-E>nEA0MAE*kuV14-ahDuASdy zmtE$2{q@&7VdvtVcizEzr@dpxj*NZWJ&=7Ll7ALL*na!%H(Tr0t;zlu&VRx95!U}9 z|DfjASYtK#|C+D=oA_rOgO2Oi06z40em281)+*y%hSAe=$1`IOd6X|TGR0edW*yS7<|%o z*Ij47{`%`3*I$1<^xuO1ll)IWKmg9all;%c*nh|RC-|R*ME@Btd}qAydsTP6Z=@=0 zNlZzcRUMDNgU$l;z}@@3PrG5y9jleg#C=tc2{JG4mz=)*rpMLGoT6xdjClWn({y3| z3G)ub=l+YmTj<|CybU_+ygLW`f5DZPmuJMp#5{M~ZMR{~v$oUe45?MCRv`40w`kGA z)uBTNCv^8f&k6Rto_gvjTc19CEJ&~Hq^V!B|4!@wE|<#@92|`OGe7h#o6QFPhx8wU z|7ZRGHCq2XS0*;URTciL!fn<5i-()|H?ikA{Nmx#pZJb`vBY-+e>1@M48gp>b3Ew} zELZ++%^!JiK6-e@H2cjR6mR4|z4sY<=Wle6P7#Yoq=+AvWqU0Lb0LTMvb5DyL9*D` z7sHuPoH54*T??>-S?S6tr!rt~;m^2`-o?&gu;!DSo4an|!i61te0=ICempQNEDZLo zeL?qeL>K(agb5SC^V`AqCnY6S2E1oSygyvsx^-#)*KW5%{uAeDK>x}9yV8FP{vY`F zUjL_o|H@c$Ykv<|^W;|vf5TD*UFa9UJK-_aEgbj`N&8QW4}iVF@os5z8lLiV^F!L4 zACE&SKO>y#bH@9yRMr0!?eBeB%<7Nz)?`YH!z~<|B0hgTL!_QAy$Fs+&Jloh=<~`v zS0++Um5P{s#p39;Lb3nTeDTZbT(NUuj@UXQTYU3ww)lElw)kRVme@EUQ*0dXetrCM zhFJeXdZlaK3+d+deC6+z;yj-&HjGObKQ7A^Imu-g>aHGLNlD3pfB*a6_d&mp&1MV5 znq-qEO#)iAY6Tm0IIHfw>#n;T(BC|L`gB`qX({vlp3z>}5&lC$LhM*O^7Zv4{SQ7q zK1Ba9&!|;0N)dp zXoRCYhoy+6qf*7*4f$T5e}%)|7ic_adeX5Had1n4*fB3heExc-_~@B*v3yLLSoUBl zrM+ZiidZrtRbEtYdV6U7n*Pa~F&1H}2^XCrkk6J$nIiE>3CUp8)RmW)C&$LdKGUU3 z7w9((ty{M)$vHsI2QtpUKI`^`{gyp@_AvIVob!5cG*^gq(XV2;1*_bU1U;sARe^f^n|pV2qQ z4SSMzkvR602I3p}s`8-Cjl4BKi3M{k;xO}vBoV)bHBaz8a|WK1b)n)S9BBV1uVjd% zqa|w2>zxBnk$R?7{IonzY#5gzmOYpz>x$;(j7`R=ymPs5=Q8tu>UU4|A>+FD6&cRm z59y$`S)6>&uy#4mumx1RG3k$>9_Q2-NoBLzE2m8F>dw_q? zZs_=deRr(qsLKP?JvzcabY$Is|NUhDi1ClHhVB1C|Dgr{UY`F2{RjRP&VUhxxg|PY zl%LT)8a@m^4$o=uJI5WKN;HS#g2xBn5B%erGcex7o))LtXKNn(?(wdTY3S3S{R7V# z{g2jLha}PeEE%3=lyk|*RPpa+xnAo&gM&V!6FZBdqC%zq(Q1m*|!FMNG{8#4ZDH2>A`kMWQ$t^N!@ zd>aP(#h2D!D7?Z8wF{opsQ(}>UkRaY!|LGMQ8N3LY)sw6DuE17e6e{r8&sO>{p*Zf4;1+uyE_Q z-+p^1_FZxQq-oQpB;$Z}$;ik^*G)IwL^@3(A|gn(fA;LzHKqBwBm86ha5|l&{}%e6 zAydTmk68bwTK{ZOBmGwf|D&)E>>BkZ>zeAH@Zk)Mo@|5UtEbgKB|l}w_Q z-a5zuZJC&D{6CjLFI(mRu>J$T%YQ+5`VV#I_*`eQu9?5%u<9MWRCJnH@o>8MWmTTY zODn%v?3V-kKm71R_pq=q=yMJY4-XFl&jXnR@cixDw|8D~#TAfKAna@B8!y&D_m2(x zX3+nD^DofbSQX&YfsriEwEIA@G_zH^?Q?(+si##H8yemFlW zN#&!LSB_QN=j4*yzBdkMq<-I2AXYw{CKg9?xn#Y#{fGXqL>HL;Snr&MDi>j!!!XX> z>zwM2zo8DJ)5NMrCH77GjWyf11J6@fSorPEojbdQhlkg-+wIV08w`C`x88cIANHl1 zG-=|5oZ2g|ykfui-g|X>7U%~Tt26&!=f`Y9(cI@cZqD6~(etv$0edwSH4-Y3@CYN1y8EmsSI(F=6 zZ_=cRjcvI&omc|8c;|FFZC71&m2Lg{_4eMqdpod~+@whpKVM&8*bg@F4|!|oS_J*C z3IBt&Xd0`DR&kr?m^b~>;aW1Q>G{69{I>)w1~e;9*M9wVIPKY;&_#%7AR-(au9JGcL{ z9I=0IA5Xa4CjHR!(*Muw&u0A_<{R`MX3iR>u34#7|m&=8-_JsdUn>GzEDk?hbO^f9M z|L=Lizu9Jb{a5w8ba;kXIwGUW?}#U{caG@(%zklV?jY&;@V)!&4Y83*WiC#=C{)EPF7W#`C0OrHby?%mMoq z6%~c~`T3u3+qUg4tSw@$3t9fi$jE@!ty}xuc;k&&-^W=lNB8dC?L&tSWnRbDuU|jX zS6;(CE{~4oq6ZEfXu}>F^u=9%`QZ=aPb3Uj=Z^~{JZ6`%eqmGF=L zh4Br%x$z%dPoNEW=6CIXI4qZ&?|Qi?4C<9GnGW8UKa?T9dp}pC$CMF{YwCdg^78Vm z%*@RB8#Zj9y)DQ!G;Z8DxLLDi0qxqgqjM}>yLP2B?2u{0S(aYCdKvh?$UUwLIokh% zuG}_l+Bl&DBM9$&+E0Hi1M}ZHkiYZu^CSGP zUAwk_K|#S`Z~A{>c7^zUZk|{YZPH<2nd(=^rH+l^nFe;GzxADQB-2?CCHW4}f5~&F zQassbB=+f<(nVT`#}}uihpQSxR!pJ45bp)rkNo?eqxDw&AN{9MR?bJ|kA62SL;SKb z-)lbN-0vk~-7}eD?!ZJj*7ZL}^8n=UhF8i%*pqkwPmHyG%1eExJ7F97GR7@*p87yFIIxtF-Z{?j4AuYQ zDSzgBjPZuza@zVmgXWR@))#m!8y~Z$M67)xODr6cD&`DGp#1R9=>I5(>~C5gR0sUe z@aJdLrF^ec=k#B)t}!Mczx|&SiXtz0G^;z0iEe4>srdN#mnTo2+!ok}Ob;qUJc`D?=e z{Q2|m&CAQ%WBpf84(BX>T$D$&&cFiUnK7-k*T_t<3>W3xz>C>nG z$D!Y*O&g!c$VeN`4~!Wz20F7a2Y?ANuMN*%2GIH9Lc%|_T6!)y&LUk`pG$GBH<47aA8ymwVj@h72nH7X30LL z`Wb!KpLHA{e=R@$#&^(1;2nA6nm0&}J@{^v*YXdX7cjQ!;Vg0R3-9ILPV6WaE5~F} zTiv(ap=pU8p)*U4tx)=fAZwXF?Zj6H*fdI0&*KZesXnKk&GN`^@;%EvzK-P)nPSn^IQF&t8&=0+51tUSTYRt%vdn%+Gs27oBOkjC)<_dOYEV| z=L}3FItVPfhvBg9c`a4_g7VOxIs8ZtFXNkOGEQW9sQ(R4CVn5~Kwp$RxHaD_v7GX; z{QvMwvG(ySadca;S8~swr$@hI{L4Hre?}Ql2e@bqKt4u(dY-EMp8lEZXjG=y_*#yL z-B+S`vWvogQGR~@#$CI1b#L0VX+7{fSVLsn6xdt6`R1ElUAlB}KJ?H-_V?ac4$nB$hvjaofa(fj12=)jn(O2HH74Dur+d?4jL($I9`|_6P1wEL*W{mP?L7 z>RrpzNLyhG{SEvF@(168wR|HC$9KD%Wr088|A}mI{Cn^4k9q_@vT}5mSfJ*;m+ zcX)7v1`Ps3Lqq+bTc>g3#?Fo%J34S)F+4n6m*M;$al8kNJ!R;?ZPKKP19GO&fd!qq zjT<+n1HjOM0vQO{o9xo1%jK!5sT*{hSLM)dhrcQks~(nh?T!f(cSbv*tvt1t@njad9H6F+_U=P&~=T`2x;|^Nm z>C~wcWDWkucplxIKYyNNAYdE2ckkZz`|i8X(W+G|C-k7emLAT5Vf+dV46K7Qz`*}) zx7~JGQc}{YsxYrR=p;C{y;!V$JX)yS) zPq%K}Y&cKQt5+|aA*MYroV7u}tyQZQOU0U(w7+HoHnOv z;9QH#etP~0kNgOCMEjG8|DZ6I@q)reXGol>af0r6237|oiRBNch@;yJy*eXH`2Qr2 z^rjHjJ*3Hff$tQ?Q#cwcl1L}Sw%J)C<6J4x@tQcM`{m{3smaO7Z%>#op$&L_*kQEU zY_J=OwR68#ty;mJgtKYWrqC^A#r}VKZ|j2Yx#ynqf&Ax_Pd-U&ug^dKJnexgJ?MUQ z>eRs*1Spz@oej)q8g=N8)U=|XjedWt_#Dn5NNS1cPQ>8^o4;7!9E`B&oy zJTc8fAAtOT5yzm>OsgxWEitWq=VvWXiFfxt$l>r!()47?e?gSwd-V7S3qDES5gxo6 z)Ba>Je{c%Ln?ER3tbROQocgKAE8QT#KlJ$Etls5XS@$i5%UM7sJ!IZAoHpm z4q;b4mL_(u%Og8TXrr1s>}!>km7PmTNqKYJxN(udK6C}bW;ApM!iFNSf7e}ikxu3t zZ@dxu73`N?cG+LP##5bR{(te}#Wu`IF2DS8s6i(?@X&*$^lU-z3g+>U(QeqXWy{C| z2M)YcR#ujE;d(nS(oUC&jj!d3WziCoD|Fo9nedlm;z;3f1^noH4q-~i(!wanK4!Z0 zpA{psDQ!yMNF$s4`Q5l{WuSaGFU_C)M&=mt!7Bq(z%wv}{)q61k35mTOb6wbWyg3S zu?g%a(?5WRTN0fm){V^;XMXWcHxBUs`{zYs1=bOUsW~v_!RkU?U`~NNKpRj_&Q`{ZN<76#{*ubvZYK*7PmvCw{C(&P){Ux=Kfa`EL`d15KyA2?UIcEi5HKjV+Ssjwq+2y28R&^cglSf=b76rRIz zUs5=vd{nyB4;Ys+yu>ZXRb|rSYR_1^BHl~ozi3Dr;mQodd2=2-24suHLosfl+{r}y zfx~6PbHs*c^TgR*-sAtk#v-w5v`nAMPJPoXKXAS`Q4z1hzS2{uf5iF_uY3p$Bi53c;ST?NPZcz zWq#x6Vn>TNsy;G-7u$v7VdceOQK^30t_!vB&INdVJ1{(ixc}mX64F?>nH&!?J6P|KB%a{ZsvesvjD2w6cRP zpnRYeZe!G_Y_aKIdE&&5l0Ss~;^N|8jvqfhsAI>D^=&pAt?f5z)Cjt0{b7F&ddMGm z-~q>i1q(({SGA0RzIu%U|mX`DyESxo34!Z{e6gTx$o z(V|6n7Znv9^M?LIKi}?EMPlVcl8>?C!)!~5QN||Y5PSx=De*m^{b>JD8XaeR8@Nz- zr*OFxR;H)LVYyRJ$|5|=}{_-EPi4R(+@DI8HI`H+Rd~xnKd1j@i{64~0RM5WGH-`=#>er=9 z7hoW?QKLqLeds4?+qNz24>_APYX*MDF>v5Ol4HYpmHz$vR|mcS6%O-&O`A569bl{l z;U91gplQ>lWD6Txz#$)f6z zM&?%f2HFpLPuPiqz9{hAz$?Pa@G^d`afd&rso(WH2`hvn8P+BXkk=3;XkIv_(vU@|EK>G=ZpD)94}L8zW&8qIU?ausaNZ) zR;RFEkei$P;a6XMbz6rH9qK`Tvwr>hq?-hLq@epaqYhpldITFaY6M$~&?oi3YOY(I z3k?nR2?`3b`T6A}@hb@7I4I4UP3lIOux}b3mz{kg@!4pqB(IG7@Z5`%9);hB~ z)M4DdQnBu-JmP`eajRq59sj^NXf1eJU?2El>@PR)&;7u}1mi$r!`)xzt>TQzm6)|) zp27gj;9V4!PzGR%<{hfcdOXIV{*CkJJSi>pFAD~xijSVot|9#|7OTcc{Ofhd>Eq60 zBI*t2jlP(*Vz>zj}XZqcHJKWwjHjR!iZUVH5|2YBZe zEn3)ayX`i{{r_s7$Ah!guV3GG=bd+wEqKgS057GlKM}1&Ws#yDEhB#e=^`8Tbe=e)`&nrjD zx?4_V%N461$q_#;&Zo1A*n_XB1NO_y%hR*6vKDRFu;F@OU!7SA!oJpJmtE#}%PqIK zu*L)V%|{=7)V^rZBKwXVJM8DrpSNLO_3!2e4<76j9UX1!*|Vn|HdMpH!knz?-_wPp=R=<`jCy2ij9%#Arug4_Fx@=`Qg- zpu0L|`I-8U)^}8TE8)i&;V@bp#wvvynJZRuITXIY_b(cfPG!K@173{3@txyZ(>MH8 zT670CN8~6zC{3(+B7<}&z&`th9M(S%Y%C%AkF<>RE&l`FXw9Q}V%N%Ik(*jUIIihB z53pZWRu-F*lJeG^IdkZ&BJ?1Kg@w^sMd)9~`Sfnxx{(Yg=q1jMeEH>K7ZyzDd3gH9ea=W5v|sUFz&|d` zUpaj$qlY}$U;hucyw$&A99uXzU95dFvxfLD7OTg|`s2K;g9U|=BeK=bF%zd0{2&#Q9)*k{4|!^h9( zyWxnjqTeyTFOJTX*rT;VNq4!;IV`t5`77-6H^MY9>Y;y8n31xtad-~LX;J@E*k`_n z!>)W#^;7Nx$V21dJf&^K9hFCX(4rxkHOK#^60v%W9Ov}(@QgBiHZETr`l3V>WL`A( zaaQqULPEmxv%whJ8!^NDn*&=slMK|D|*B>!rgzfg*Z|Ax0e~ZJ>d$ON>+ikaD4(#yr z^8+8`+n_-M$TZ0i9OI< zU>|&sjv+HVFpM}do*qU%SLLr^U&Et(mT^G)W&Rl?r+_|LIek5y>hkjZZTawAv2JWm z4d=h0|M~9Y%g8*j@s$E`>W5NMTI3xa(w)M71?-FN-MhC}r%s({ZNGl~`mi|>2>a4F zpAKFh`lukge9bl2Ku+F{GrBmdn46pX--3NO>d&wT0C^zTmw}xn=tXSNq6Nmf`0pNqLJfgMWeSkm6$mrNx^zNsD!CGwrARssCwXkC85?k2|Mry>l4P{wqf2iw)1@)kyva z^WQbVze;=6L;2$CNrfV2SJ_2lzoMd|6xjdy=b!J5jEsbiKiJs{Y1*`D0M3911O)hA zbImo*L4yWCHr@{X!{a!gd{{7?U@@usIe{hq;KjH%WTi-7dDJRNlZRldw zc(8Vdvx-0c^wT}z;o zoG_^4RmYRUE`>Gt9Emr?F)z<}UG<>s2jIof=aviw{fAsJ_#R-tKwd_gsz2!Ol(yNQ z5O{s) z;OyG9t82i30i<&udcko{{r1~$XS)A?!BIcQUNGi5unhqF1F$oH!won1Lf#3w(OoWA zE!gC~^Ugb)oIQK?dDyJ9)^}A7bK&G;<>I?(MZ{xZJp{NzA6Q1~J&YfTTZtw03`{b% z6+TxIj^VE`%Xp+YgTk!+9;7Rb0J#^;Ou@1>N`c^(7lyjT$xbyY<#vT@xlu zaBkeV(edi5uiBq}`e|EgYAV^7`!DAAJvsVce}8}IL?M~zwr$%wA=_Q2P94YuLG~#a z^Ccf2pRjf7)^#c(;HiAuliQQydMgNqeD< zigGJxF1T}cu~`44!U_5T^#z4zg)JRN><7HTUtSvB~k7K`-jW3`M9qb0lvq* zkC^Fd<p|Lf#kiJlNR6nBS>WC!+g*M>)1X*?|UN?n8Y5I)X43v~JzniS;p@kFQs+9^~Re zTefUj@4$fr14>Ftj?@JI(&3E7dE#wj_mqjBmz0Q&FBge*PZWyP4;6})BXF7H0%J+u zb?nPN0L%fG_`Zs9t8mS6@xEe2F5zCszVW?^bZLA6<}J9Ec~ve8`xEZDZvg+#7kSP9 zUyQ4$s3-wXdf>o;K@U9e0I*N`sGy5Y?Q1~?CuAHQk3ar6omWvjKHo1nwI7Cig9Z)k z_uO+2$pu|^-F27;z)prQY-K~&5$gBZXP<46oSgi6MMXu%#k3jLh9D16n0;QPo-7w9 zwwHEdkEs1fjwaTM!8AsAnmE?s&> zPEO8psDA$=tqQ&f`(==6$w{sd2?xu?=^x6(p-rV?&#Dr!b54o)c5<=!;$KBXOV>SK zBtCqkNUVLhP^=kKDAtU|RV1&6&fW`2b|3TeoiQTeogqf5?Xh z2M32hU-}O}{BV0=Vd0jV&ILR>xqd-&xr*~NXD%s}yi#^Tg-AbJE>ez{Q~!wDTQ1K0 zyG$J4RwfQ_DHR7kFB89gTq^diEfIUyl!)CcO2ki#O2oex6pJ6`7l|L{7mA&83dQyh zipBRIl>F(}4|8OG;OF+NED`%YED?t`mx|+C%fz`~%S2&T&DV3PbIQKf+|8Re-_WdC zGuW+#99uB>)QE@(?BDys#t?X(R;^kQUkiIH>(;Ha?cKZg68@`jtS1O{gmaRR`Nmmi zWup>0zI>tcC^R$_HY01bYSpUlo;`cI78MnJ<&B=WKgY>_f_Vhai`i`IjpjE3@kedqf_xG>WrcIl$UAuPOjd1{Da&>KY$z8ltHiizx$H$MIG-*<6 z@Su=kgYiP>Fb)k3^~E@S#~pW&t%>O9X#2CzK1+08!~Z4cy`ymw@}aO@@Z^(EI-Y#; zNvxr{I&|m&drpuc2~@VcYj54U_2%sC?1iv_1|H^;`|}*;SjEM~KSCc>zkdCopC0G) zLU2|iFffqLYlVb_5U)RW>{!Pgcichin|3>yn7hR9T!b4xempMQoH=tyzfs$^ZSA8+ zk9OR1&pnP_y?VipADtJ2K0NFZ!+yZZl`Ah#PELNgw6yf#U&jyqcRR>$=H=zB*}Z#r zm;3L(A9DM(KyyHQAgkW0RV!b}u{CVi(1A0pACIdamU<>TWM+`4t^+H2RYy*w^1ZZzfv ziYL0n6nW_&V^~;N_|36n#|FIo^2-rEK0eUPhdr$z@H@3@*9P4oyK*B(jwD@dS6y|L zedWrP_N1gF+SA>%Y11D>z$IsK22E+#t{uru!(_@ZJ**A+<6H&CXSCU! zcivfd!-fr4o;!E$G1$y0FE5Y#bJj}#J00q#q@?8c^z`(%zWVB`>#n@=O6VYfO*zOh z1VB$EF-+aTeY?kJ`1r zJn4{gI-S%9u_#8U9N$)K7AR)Jp{Yc31;N150*I!RG z2t3afS6pGV&mU~(ORgFRzUbJoW41$w4%y#$;|=>uFTG@+F=GbC1CkqsT}iADVQz+X zInXWD4}zOCX%aeq{P-qcfBp3}$BrHAot~clMqy#$#?sQ#-^RqM{9(JK`Uz(!9I$f?8#s}XkG)IQ~5OmDI)+*>)WMpLR#~ypE;fxtGT73KMx7VFKd2&E-sin(gbUDG#-S7g~8T9An2Tr zj}OjR(%5kAwbzFA?Af#4`0?W-KL7ml4!d{nzT@!W!}py&eL5;OHg-&6V&d4;)YSi% znVC5yCnslaZf@=ptTPoB7JiQYQdCs5wYa!=M@dP^p3>6N{pdfXrKN|lj#5@ucDB5{ zywU}K>o!~XBV$5*eEg#) zPoC_zYuBz@7A;!Ts(=6f^_n(q8mjDQ1!5gJGBVO1`WQenI(F>mtThN zO|mE2s#Po7J@?!LIYp{>Wk;Qx=92r19r{9MW+vH|+_7Va?d6wWwl!_q6#67c*7l)? z9&%t08GG{3rHVB>?9<~MUW*nj&=;U@2=*rf&?nFz)P*@TU*Ls98aHlS`<7d7srTfQ zPd1u3abnY{Q>QkcJ9lpDrAwE#`{<*OE?>WX{S_a7{P9(rH*daX%a$$IZr;54T8u5< zefQms-+lL8=WW}z;eO+{-+tS9>(;F|;vIK>-@bi2egE#e?>Zrz9%kFNZJqVF{0)BF zw{P$C?YG}v|HT(yT>I5mUtROrXP;fUX3d(*=FXkla?+$p5m8Z5^`Tc-`yc!x{uMTJ ziN}Hd;hS!{$@i+Ou0lPL4bc%JMmTUb8~cyge}T@vOR{VK>m9Wb)=X#2m;qTL8W)~_ z{&_oWh71}si1^qWZ@ke38{wThcXr{tag!!ZaOT(#I!IxQ1N{PX2h5{gE*H+A!R8vq z3Y=#Og3OEEZYN&KX0y5V6Afopf9gjVZ`Ayf-qE+Lm)&l63uCw2IgFkrKkIiY6XK(M z4u=C658^SeP92z-4Gi$}^Yae~2=KGnY_P%Xy7%6Dox_I@$GIaqpM|qXUAlC!zx(dH z;Pn~zm*%#A6URIf^kie6uyNx?=v=f%L`0CTjmI8)%>MAh4@1Wu%@tse7_t?RS#Q{| zA<5b`Zrm6$r@k(i%MW&1A=eue6ombLnonRK2xA9mXro4r0+jAL@sB zFle{i193K_UcGvBL6#5xb?Vd!f*o*N_zr)Bflhe5H*DAta<7=b&~x+V&9Uc4d1KrP z2nc`;S)3EX-W1){-XLUVu_lJIq>v+ro^8l0V4VPRwa~fYc=OFS9smCKzwIkltRT!| zuLb%kvHu7jr@XxUZ_@IY9310c@u#-#-Mbq!tX8dBIB#it?X}nF4EE;Do2f5A#t8d4 zAt50S^b@Qp!9EA}7qG{F%{AAMUe?PlyNvqDl~-N~egQVTp|=j_)TqC7>eLDL4195J z$H&LV*WqyZ1qTPib`PyfL5>wRZ?I0)zI}VYmMvTQsqqJT(11-~lypO39SgGG(1{G$ zV)Q|riNia>xm+$6a1H(ONFV89jlOZ?#&p4U1nexs#zMPx?VOksVeb%o=vd2n`Q?{s zKM~_2Xa(x4qN2j~{PWM-1`QfSwBr(w|6k{*FZ}CY|Khf#`7`v;;oJdSU|=Av@!fF4 z4R+WW1XiG<=Hrh)Cc8Dj(RJ5d=fGMx^mKpw?YGo-aMtgkhaPgmMjI~Zb;MP_etl=h zjvbwkJn{(i8ar{e4SEo;PIdFmH$&em>3xMf0n)wWjyot!mo8oC!u$oi8qSnJUmE1= zgM)*iH`s~%Vf!3$@4x?k=YtPENM*VG_S+rc`9R;XUO#KrEXSrzo9sJx?!-Q={gqc< z!9UwNckXP1p3TzIQq;$PU1MKzRXZLhKKbMm(n+n!>e6#~c)0EC*|RpBNya`WXkzc) zy=?~$93Wodz4zX;fB*gWpq=(($Bx<8u3c*f{$|dc>G@)Jgeg@h1MEXbPPon_0G*;lV#O}5t1uV>GmZ7(e?wU?BX*g@Yx(_eYz6`RZD!hc?Jmz>oR z{^1G>3w0gbL@x^p3XHLXc`V#PL;v~Df7;HRIYaM^wO3z#wNl@L9USI4kOyqTu358& z*47_*-~p2Rd;IaojdbVEoojpNop)@TH*co&VfPSy5wshA$p7@|)3$Nr#$jz9JiBew zs8P0Suf3Mi)ysK_&R=qu+$DF(U2>P)f5w>-CmvlfGoGPtpY^i%8DA`x{$A$~-|NAB zj0l$CK0eK5gi8J@he`&el)roGaFlbUWPE(?KnBH?!;^od!rvx5{Vpxxd8meJ{XG>EOM#yKU$4r) zik03ws_<|AXZd@qexLu(&Ra&swQXCYg}b|JuwV%l+%+LV0tEL!;lYEuLvRc35JIpJ z+}*VT1u5L!y^z;xpR><*?mjy|ZfoCd@7>nk`~$7G#av^~nl)DMWAp{!{}zt-oB4l> z|IO)ykKf^Fe|t;-0Hpu7@W14SKga(#{=a;^2*264{~!Oy_}{({zdQf`+t>BG_!QeuMw+`rqNdn+NYV^Z!rdbicXp|1|FRoBRHi3;#L( zhjFCe%#ZdP9Phu0OZ?{g-{F40x&HU~-=1fL-_TXO$G@MS?l-vKZ|3=L;&A%^cX7hC z)Bg<|E>r$)p5NhcHS#yt|DjU;hBy8pr+$b34KMveuKhRg->ENnn2&$M&wqpeA#QzBMZ{*rPwnvX|MrMbRaU^kq=fryabCTYRr~Yb+@ix>G5StlKmdS0 z;FYYDhWoGmd$$$?1rHuvzZ@O&D{xf$G#A-iLdJaOf>ByGV^pM;XFi-VxxQ`&tvNYy90JuK=N>G5 zeOET%m^lE86+K1I@;Yqn8A0|R6WXG_HG16hBLFn*el24>dg%ANLtAj@#&KElxtc5y zzhE~7-A<*p$pW@{9#(m_`EqWs7kps{@xn)5OR(*7dmo$Q`OL>)f?1hx7_Oi zbWm{=Pn))!Dj>I9ubz+(JUdI?*VpH8eQY8ag9NbT&v4%@E#VaxSHAJi{81a?L2f#Nk47sXjLm z;HUKqM9Ctx2%r)ZV^;tYnx(gQrY*EsgF~*)F3}Du=zY!mp>c!M^AWbB0@#+JyH%w^ z@MV>a$?x&La8DQn0y!MDUYXwCUd-0nQB^b_3eS7)mAfo@Y^~ru zEaM#?y0udsd5n9hmTGL<7{MsR(VHp?RM{q88{Mj5V`CeSXNv*F#G>DoX`XP^&j)I? z?!Nos_vmXo-w@A{j5+g7r<=!XZrE}({e}10sK6R*>9I|_#r>I)$42s9+f{4q?d|RH z!jW(4hn5q0?UWFxeLGD_)<;Uf@&oJ$4RDgNbQfCLa;k_bSwHWDW#ByeLc7Ws0S^af zrL-iwE6V83lv>aMP`~hzYTEh*pyS~lR_(l{?BePwrHG*fo@GU>^cb}{zNqx00bJi0 zf)h7-l9m*94|6s<@iv?&{BBG96h+()*F9Tf7Mk7EYvzyvzWpMLQcKr;0^6C+W1XwL zQSpjJ6P7@SZXAKCioNSIiAQISWW_2ws#JrI6j`3TrFXqNz61gbE<}b`O^}tuoIB?G zbyf)}mc&l2wo?+{0~@2)Tba(dhwYEPIj`xvd9O2_$MPyFxZCbdhpRo#KyQ?lO@~vs z8uq^rt8yd{eCJ$j^%58p-s2Q~xL#{GsDrwDduJAxl-!*3)0MpDX(a}O!Q?bFy+g@Q zPe%C0bL;C$=7J2wP08pk?xp{c_;f;!lI(Mo}qkdl`$_MS}SyqCv=Vw1GkmU40}|9EM|0`iBp&);O8@503|hbkI&UYn0$ehY%tyZwu1X= z1gm9tD314}9NEgdN>37!C^@s|6m4rUBFBR&z6rRrqAy3@SY)}9ULGz)x$e!Jq;Tm~ zSx<9Jxzdb$0@b{_@y^b1U&Y|%3lA?$a-c@D$o61SDQTFT<$;wsBYM)*F}>dN`~NeE;p6{@lCL+fLez`<*R>w>tqFj%$w ziD{DJ6Iu!i4D>>L#Mi0Za%Iz95BD$xKuNYI2h#G&iV3x-J3baEr@y%lJSIcfFUTqU z40vyeEOjopEsY8+xTw24Qzi@|)9{cY@NW>^wc{V!aFUh1?v}WdVPTwi0V`df#QJge z_4ixBE)QC4Al%=ys{>OwbppBc>Wsa;Gsi^F-jGU9EVg=0LLgxg5g6%NSs^Cm0ROx^ zhV;zL^_3Nq^v|E^hrcfi;*xUfhnJSJpI=>d^J3!@$SWv3s9EgL1Q=OC$jBg9&kByCu zvgIG1vn3@WBH~pd3JMCcK0KoFiFreqkZH zqJo>3&&8mBf1mR+#r^#~paTpBf6mG>XF#@vhT;dlPzthxI#7p=i=Ms7bRHABn~=e~ z;XM;sJi(p!J{!I3AyP3mHukTxUmD}_yBS7kFdZ;YHTal7e;1QV#*8rA?k9muC;sUG zTEBK+?=MN3RB0KHq030kdr($>eSM2d(Bcb8Z7FuL`gx$ZOvR^L<9z^e_NY4d`%F@ zmL4kaix==^awjW3SaF4imz0#`Sy8&WxfN)0b~#cPs<9ikc?&5iDLIyEX&CThY zpP$F!BLY5$(-#HfBQ{kOl$Ax56c@{uO|OD#A{G}7@d-p?WXd(MYHDgSl}qyWgkMnN zC>N=*r4Azm`~>18D~6<{)y#2{V4~0sHfnQ9qf0F;EZCBx#>U3VfPsfcMUSM^Z;4c!bff;Nv!jxDxLyNvxGj3H8n{ls69ocY z5&(C&sbq)<^nd&j{0Vt?9466YA$~^TxgM`j^>>U|Bqm=B(h%pGk|6LZ6=v&zvDl&C^FH!}IykDU8n7o!ebGWvlv5Sr_4R+QSOn144@V z%COPH^E&$_<)(`Foq;IVv;E@ou&bl~alZ#=YGIe~rsn2S-SR1Q6|s=X$+zU8QtImJ zW>9`5Wo0MftYR18XiyEiEmWR6l?jlkSk#jz6E@&Tt)0?4!in8Y4~SDcO@Cp74F+1> z-(Gn7`L)l0!A$9+EEkuTyVB@G8%~VLidIb(u>Gc#L9_7i3G3RIU|=EGN=jC@Y&zeT ze>i5Xqs|UMpF?!qht==s-D7UFwJeAm9$u8xGh#-L%Eu=Yy{&)TX;ohlyncfvsyZK{f1Az4;54z;CA2+qTfU(c9@tUvs^bCs-kf zkUdv)hA)+sDjFOl6_CC8rKRzOg#}SDvC%MGnkmc>L^oI0!IErGO!xykh1&qr0OHt* zLUz1UlB0}8Dr@t?n%4#@;OS{cm zNc?_BWi8#NLaJc0L&fh_{W-RbV2q56jLi1oVR3c!GvX+@-Mzg6-Evd|$ScKH7oJ(v z{U;=bo!V%qsFwG)7n`9tRBBOVJj$-Ft`wmrmIej}ECdwN=+*8gs|XCiY2#aH;2oFL zVk;Ctx>Y(7z{LPkBdY~U+UW6XI!FJEYQN748# z@<~yq`DQVGU||vo`^L95!FTsnp#hSSZ&?CflwTB*j<)Zys(`1oJ%W5qPv zuNJ*Bn3$Ld0hl3(7BxELp$rI;VBpx+nUjkPad&sOld-WsE{!mANpUg!3w5`X^T1wd z*h+ci32CnKI0JE$Ms>VmJQh*Uc0$h&TFHt-RZKOZ#83q z<~nfH6oie7tJbK`00Jm8q5fWDhmroB)>VK`u6Af2zm|G2Zg*FmDh<$)uQzfh~b` zNy8f@xHQ5)yj)ydJojeGYnJXd1zzjbe{h}J0dAdn@M8xdRN)7{E+TYub9naLaku>V#CM%+JyxQwLdzUSMn9KMO zfP@CmsYqzJ%kXa@m(KTKTCe?QqW9-ucqZo5_Lt6+^t#+kA5sinj1)jAo}P7Aa2y?V|DXAKN(s$4g$aKaE+l zt%U4JEH%3w^_66MT0ipmoFbF3I0XR6s+hz`fgQuDsB zg8@*@_TFBBKF8)WF3E7AdD7PtLfJS^DOUbT|rY{Cy7zyLr*HwU{6u-&w0c z^GC7s1A9MM$Ckw7jbf|eRz(Fjck1x>Y3sp<2jAVp!<59tM9!k5T3sACrxwUi%Je#H zY=Iv{zsIG=FeEhGDiiYKI~HXp#P*F-Zvy|^B}mKWi}Ou^c8Z+apC!l1mEKQ|yCFcF z`}_N!w)}(NIaksqWm^r&9OU$F&m*$q~l(e__z%Gq^)~J?duX=fWtEVi1g_W6RphoW-r!lkVPPVq6#PHx7%x*q`Kw6uLwMX%qMYVeAS$Iys; zROLv{2i3evFP`k^06gPM9yUktr;HAbQ$qx_9@yFMHQj7xsOZ#yM$KDWTOn!1jZ)~7 zFmIJjzgkoBKArCFZo1urgN5&)(R7{#@3T=ViHDQeqx(S>-zh@rD*IkI-9SY{n{%XA zE&*TfYPECl+s^twt!yDodlM@0cry-`BQtRM0UAl%Kqft?pCv2xvu`<(7e4p_-hO}` z_2DPsY;>g-0f&Rg#f?2jH`?T;{Z`__w@jG1pbXNMIP6%fkM z>d5ft<5#i|THNWyI9q2oWvSc-O`8d?x!H@rI}r-B9)(Ye3Y{9U8G`NErQF;EQWc-1 zXJ?06rNf`q#l^)F3kynoe0L1_jbreJv$;kAk%e5 z#}Y`Hq+Buyfhc6~+vTHRkxHUV6%-WYmz70*o&I_pXQ;tJ(sB}QKhl0XU1{ieIcH%s z2?n2qHC5z@rM@~a^LU6%`p(bCcNU8$ad&(raakREaJ!@8_e*#qNqzn2PhzJ=YO*M~ z{_mXPSM$yrmj_Tss9pVF@>8u-xRhQ`R59I~DQ`F%6Pa9E8ZXK5754P>RNh6&?mo>l ziLczyBB(7Zi~7)Z13EuHH(u+DW-gKy&LZXGNaduGg3K?W2i_iJ6K;RnA+=oI2mvnGxJ^hn-mvTaJ8=3I+#X z!LyY6W|~En`-y4GX>ujA0i@ar%yPRX@$g>QVHrRp>aI#0C6{NF?w^q>n~|MeEOA>P zG30aIO((t=O7+2gt)rtu&(*-lgYd>kP!4iIA|@uLmF4$vQ~SbZhT3yE0L{ABG(IAn zx?ieht}q0P?7LMuoXx{27z+zamHm=9{J!f4(E9h?p*Rh9r^73(G9GRH=P>PTKE&gP ztM(igR@Q3JDCMsf4?gaeV`<$VmIMdCYNK#zgk{6<=t#j}a24n4m+UXbomGgVCXhC(ncJ5u)2dW9`Z#C4Ch~FrGX)Jz^2nPx zThcna#3Vx4_G&nzq~mRE-hKt4X1^x_D`B1RAh^h2?T(YiJrkb4hpzlXBdFZsf3 z$T+o+HM@Do1U+`!tFQ>tc3eEW15vP23>-R9U^%|GJJk!#Zq7Axg_>n@jrwCMO7gE$ zxr^rJbZme9h%maHN}YW+o8K+Yn*`w6?bXTv&)M$?>JM$aF;I zGH6P=+MknwO9u^(C!ck+Q;MdoUz8hG+N79Uxh;BZMN)leSQ!-)6Z0#!`n7x}Cd4`% z@wBO;a(i!2?uMNh-S?Ox}I z%U*eXetkVzK|#UsDSb94Yp$pTvhmI7hRZLjl9lRy+5*$+m*e6v&ih@h_I&GF#biaf zPtnm4o~;!(l#g(7|Foj{u&I4$>#QD&VQT!UCzF%4ZM&(hu)hAPcWZGtIQG*DedEE!&l)A!g&K8Fi`DcOxPvFxQV zr!JPs*A~%OJT1Iam!})Ua27%ck7uVTraKHT(OgFw;@YagfdQ@St#7|SAb}Y9-5YH> zHA*n%FO}k(qXHB>AIYm)#}|+G{Xj=z>_}H6Ape1TEQP@Jv)mC~RVTlafzPPWws3C3 zo--Z=s(IB^fz|IAYzMvD;PLaAtc4iDw#Ly;`2gCus`dye$PliMj~!fSRm=n5J8{tF zzxx*>0XZGOsOkLS2p{GS-FQ2Yua_v|_8TOE7Hnyh(9*dGN*W2S2AGlxztM?8nz1{6 z`hUvU`~)Om^if1|wU8{vHN%-mpbb~|lxo~sjM7Dqr=z9yy(%-j_=@sW^V{_hjoW;+ z9k^I2rr64i9Ci0X*wf1^%UfIpT4%SWt|Io%cTaCAa7}Qw$^;2s#Q8`aHnjshnyW#> zBIV3YEbh8VCJJ$IS#X6Q7}lj_XVVJ9_G=usCm6I-~9TA7S_wUSo$9jU9mLmP*}Y` zzx9_UF0QhRY(aX-&LFgo8jg;S7>NGu2f@!3Gc(PljI$bPn$?|P{pW=rw@Z{S(UZFI zEwFul#t`S`Y+govZ$KGf=F zj)I;E3oDjnI$>gWFJJ6Iz$sjMxqHHoD|8PGE(Qv2Zf>@y$|(h0tcW}408%d9;IlCi zysJeop3u-xI0fTKRyI5N-MUmbDswrPAf%f%~f0Q9PO#HElf)fp5(86MRYR?bUkjcSHnJJLkh=B1bJF z{owqXRIt1EdjlK@SQF5_DJw%zV=cIz0T)a$dx< zG0f9WT~kxjZqVTuJ9NT^z%)idX6Vj9%B3sY>~?g?x0_(D^V1SIP-kbNlL8_SH3p4- z=R8`-&%(1FtMct+@ldQfvZ$0~o~o0xmN6l;N`=7-gv zTZM*^C?DiuX~7M+0I94yQCH&=0G0H_1qBpQ7!j zy@U!;(qS?pJ_|xUotSyJ#PCN$@&I*#E8A{&o9v)y{Pv6UBcK|_#6jdEc=?bev9o4e z{I!bM1@T@}%*u+1Zml&5yzo1%wQMFd+W7wQVp_{$>EW{8sM%qqO8|qxQ@MxUtQ6N`7(LTS6br@*WvGvCR1`s3d1tm9 zPE5;{73WbgnRsP%Y#=A<1jTl~2x0XujBQY@#HS@$WS*VVqNnB>4R_gdVh6^BX*UB_ zJ?H_F9T=S3ySq)xEoWmk!ZBz2_P%Esq4?5;YV3@RX3r_ov$N%Eb>Y=bc){3pPqS$& z+iR8&C!4chJ0=Risa*xw%J$+O9T_opaEOEF9Yo0te!J5-UlVAZC4UB@zh))mWKhM| zX>`sa=bj%792YlSo^x8>PnWb=TR;x!m_Ot6dM*xSnY|`-7*;b`qeZ`C= zFx%s}6HPD0Jl}_l%5S z`z#~Txi9veO0%Dtw_`XYdaj>&;1gsy4Y9+k4y|5SY8=V=4-dW!!T8bgNTDX-*0qXc zhVCXGN7G+WP3@eU_hSiaWmM7rit2;V%I&d2mq#?{Up&%(0*zg~SS-9F^HJ`6RnGX{ zpT62Hh(9UOV1t^`6#-oef%;9hwCa#50y$mAR3)Z`|GpxfKO?N)ICc-cB`jVTWB9g( z6mPL&Wo4!Qadhc!G^gdN^@s+D9i&^H!j?L$%aKfY;c2jUa8T48h8M-9SBLEM7(!+k zF3_@q61r6Ji7HCvK$vZwxS>>G;yQbGFhD*CDg#}8zZb4^f-w60au(sz_Q`~g;OK6Y2};}$S}v?kl9 zlfZeSi6DcN7?zFwGG%3ueCAYn@eA8>&GHEmu#}noQ1H4x-eTlgfq3VWH^FfQ#-T~r zAc_;Zm((>9v`#|k?apK$B7N~}!`=!OSma$j3E=yz5?>^||2U6paTdNs?rMrjkhZX} z;0OPHx39tT|MEFl1!7&BuF0KRo0dlLOiYXm6%B2W2n~KTlNFyZ1n0iyZVawySjDs# z`n-Cjn9cb?h}205U^(~Q+cJV!y$N#)*dSsw`QRzYKtGR1J52Km*niSGKB;a< zb0Ug)tp@uDdnnslT^tkr%5bJ9SzlM}24ndh@0ID;- z4cV9qhPGYT!w#+>6c334$&{DwR)yMvfzc#=vlIylsYKuVq!)JBeAFhBtQg{A5M~Gc zoSj|l)cEOQ4{|m;OHPJ~@>PT5x)aaWzq3%bY|slJy-KyXQC5&g>1T^jRElV z^qhb~UpMOG#){vvaq0dDFHDk^{RsxIE%`nT#_w{Z9(AM^e!M&5v-bpNRM1jV-m0ft zc=I4R&@SP@U@8HuW!%j>)mv=|qc0Yl!)2Hy8AWLdGCl4{X{FcwuL#EdM0{;jEE}$} z!w#B`RH;t+LT*t?e_DXT8!~Yt-Bx@zkLgw7fL;7w?7pa)ryA=Q{t)>b#;=0dk<7BM zgl4c56;C+fBq`&e`fJIP0jLyYl`bnQ z`s|q$6B83T1x06tL35SGI4va&%|>Y{I|pgh`En2*tpI1c{f4$IKqZsK_SX*#x|;!o z?e_cn$51 za~HbmqRaYEGGg!A&Z1>=37L^ir4wpL6g(eQnpagEXHO`Nms-M9^<$4*j7=jnO)Y)*F|bP!h7Fp7B8}nythJY#>GWYc!H9W)IP<;SkKvNe){w&{N%*Z zM2A%A%^Owz43hdq&!>L-@9EaywTbF}T9Hz6jiF~ug~e9nv}}f;|sN0uU-fUH9DDjpA)TR=3x|zAO0|c;B>5!J~f5A@4EivUG+$BvVjd30%5iLbWoofsVkTO_e{%{TPzlPIC?In~&Ea;)43T46*s zc@)JY>y6*v)%t)X@Bz3A9- zV3wifMBHGJb)(!?Y@Q77*9i{~#=mdW|8QTFYB)qhL9VU|kdGC+BzH(ar+$Hk|M_9@yjY;L>4_=m=bq~uC8bUX zie);BD7Qa%``zh7MMcHgCKkO4#Mqw3b;H*WiJfZhku&({Jso3K(%~Q`$f1V_-$bOk zBkfCmG-6Sf=U3Wj{o!g~Z3c2?WT4Vn18S;s+(=x^8qU)Xy{eqJ9Hy`}MRL6)A}Qic za)!8(xZUP{1JTd67_iP(OCPU98wSfPa;yLaWKwKg+_sw_y1V6^yYa^xiRSQ^8XP3$ zbGFtsH8trKd+DjkirOhpGKYJS@uHhaD`PA1vtM>!VLZL*5PtlNaow&+c| zkm%?$^Q*oV_T8nWjpubgL8i|-$}i=2nsF?$pjcyiX1313N@*$#%vF5N6gvcQ@hiu| zEXr+6!ew*WewDVP;Hjes$sm42%+ULDlOKnR*js}#tO@A;;kT%0XpgJ#g;BwE^o2-_ z%(V48XKIBq&9eMCTkC~|h5V8d)&>WVsc9gs$2tMLT({jvWzP(d`c+MYIE(d-+Ews) zce?B(gvT2p)yP*!t(188LYN{{>i+&-R+ceY@okY>o(9JgKpcMHOoc&)VS~epVmEK- z77{f?&*7u8KP!hx!?*GDZn-f@R1j*sKR$L?R>yPtBIH*T^m_Ah>Qp-;gZD0D(|-iae=+bjic$gbyPOcEe*ksyD(&CLdI`DSZQ{2 zQDPGjj{9kZ3?l$p^j9hc7p+{p2$p+f%@HqSYTTW{0=&G(#RBa&qheb*eh*qkH$z(D zcI2U-v$9A4vvqbtqN1Gg^76*ysC!LguTwbl)JlWp3p(Ncnw=kDu|98|5DlP#E}AO3#4tJU7**BOXHMr%iR zvbvw+Xg8d|^w#?;HlaoT3x*E0E)xp(E)Tb#D6;h2UVPQ|V%R!9?XkGwG(RtVnch8U z?)JgXklfG#!GLOHk?f&Zg#LK?gb84It8b4>Wk;ScN8gINATbHcqX0Y2xT#ZSQTGi+ zgBDicyhPB zT=&EcL0l_%LpI(jowT+R8^w`m{$Z~}@7kNHXhwWZ8 zy)07Gt<@y~^z83{tFqG@85^4#%MyW;;2(}_@^G_LJRb_HEV-viQUN2Ne#uwF@P1c| zh_67OyFjz76R2d^;J`qw;%J`E!jT+le@g^#U+D_D(cp0X~+lfP}%A$a%U#X%$NA;!T!OS7(qyMsJNRi5Z~Ko zPL4@yYm71AKQl#st%$KR@cG56`=t*|8|kygXd(T;3FfyP`!LsW(D9ptFX4xi#Ej3? zN`IQ~&6LCT;DkyIT31vOHC33zn5AqJA}vK8Dg_3EHQ0z>D!#%ZT3VibfmFF>l&7bsC>GhCud@;FYCw?Qi}Db$I(h-io_dvRyN3**cICe#Rkl>IRbz~-9RWA{m2 zXXk-z6mq~G>Hg9_>SvS17ToQ@Y_>`%resBfI^TOwpS_v#9M7HCMz=Nhuq(X{CkjAe zQbKccb7_6Oma>Y9x|SAM^02wbPEo3CDy01k-uFj!J4B2IZ;|J` zyGjGQjOCakYaIiL_SC{2z2p7$2xsgsb59mnS2DH37?`q@jaob}cHP8|JCOYDhP2wA z^Ar_omOTl7X~Un9uT~oF)Cji$?W9>`69feXx$KsfsMXB%L<=kqz`HKXXax7-&PWQ3 z0A&Beraf-rD7kefp>KNixCI3Tg?qvm7Z-Vzm3TWYl(o9i!T5!H!U(x9Y-Wg0Z|)** z0KJBuJlUnJl0+@jAkrwN;ECXTdgnPCXdRLHDH{N@iy-Lzc0Z;_#rN4kCH8%{9vju^ zO|&6S`!{(y6YG}$Xvv10LinWM97sdkisJ694JvMy&>>d1Ya<@nYovSP?nQ5 z+$+?seG_GB$k0V6`0(*M-F}?&i}g6t5;^V{#U7Z7)NOb-K09SYcu>Ol6E^WX%ujE< zLiK=3K^sdnl7^x|YtD@MXBZ0@_U~CmgAB&HP7>oA^poV*35IXzT-u&~A9wwdk{E~T z{4MbHr$VL`(H62X7nC%rnNCsr9gGN;bLvo19Bk~n9af2(SKCbwr6NZyHPW(_fbAyS z;qRZrd7nQI9L*5GQUW)Wl|{kZ#j8woH|U40SBnKh<=4_&&SR%!ZO150o9Q;kBYfk_ zF(QY#IgfW+KkS#pb*oKL2;$jNhm!`)UV?#nR_QE7KU+i|;k0w%wAKdlaN=EYlb}u_ z7)$5CG#s3-6jQZ@&hL(IDO1r#zbYLykufgE!Gn{knh@NB9*|!5)b}e|#YG#M1HNHd z+fNVMb(6^5AQf=}jLcNV2k$&?dQjuBfp21yx)mrcu1Dm)$R$ud9H3-55!SRxhok+W z3s*lNSqS~%EoVt&Ge}Jlg1OSkQo<2eP&47>jNgqS;IJzgl>_2T9L)5cey2r|UToL? z_yF5WdlM>(#sH+m`Sj_NYRz1s21kgP*QK$U8T{Xz%{Dj|G<|J%GYoPj^oO^UemJa1 z!xdwm5A1R!%_2LGZ%i-kb^(5L})b3(;x-tz< z^;DJMk_pz)+vN5RLA!O#@2L%Z@J|wPKKBtNV}2_ zz03^AKROZ5*XRK=@6kH=rt#(dL`|+t+=H*8>_j3LgQcV;HMoqwVegJ*Ixxm5myEY8 z2V$YZ`LK-9br)HmUU|xlB`|IjG`X;#wBtf)U|_%;EZL|pU!)dbt^+rE4FHvN+kM3j ze$_q|Ug0@@UV`p0%vDdHGrm^&cw2cQ;CCj#9Gn{;Pk<e22e3r zMM7*E3Kcne3Tl{f;yF_#u?!%hM=L3g)szwk6n%LRl}3L#E;Zl>aim6P5PjPLVO0%J zS_$}c9dS@kry%hmD49|JbudNjcCs{S-~b8AJZC+3{!5J6m|$+%ktm)*(G$Tu)eW?9 z%++7_K4s{$t`&sR?kSit-?jU-i3@Cancp#=jqoFQ#}IKa`nZSSngzXI z6^T;|_E~$mYXOFeKPPwafLYC)4V-KHo=++~t^{InyXQ_@TLA`|K_h8dStK!QxvFdf zPb*sz;D*|k`vd!aI02ge`fj$x!}+wQ9WbnaM{>WgbWbx=X%wsCcMCR4XA#AdL}y^j zQcg@t5UpS8fW@X+ji$fYsi>%sJ3|P!Lvt#|oI9=UY%^+EtzKg=vw+p|+=bExayRpI zfyZlVn=jpJv;CYr4;uqMjY?&#RBzr#5rpMp_4sLJ%bkbrzEUQDwO8^T7oZp%i1!+w zpoFR_zpXt{3w8A=dCZM{sabf#sI4Kc<_zp33r{oF5fQ--C*`zUR;V!~lmj_UVTHWZw z)+<8}(kQmnVVC3O&h>zHz;Gtw%)>qGgv{@R3|<{If%5nD^`QeW7*yGa*>c9a9jW6- zL3w+^qB(%wot;-{#d8vL{tYHFntF=@coay6&#kA_A_8{_$VDwM&z$h3z_kN|>lr9( zZ3u@lM}*^oj>eI`_A=?XumIxK{Lg!B{1RzUa=2i z4)yK_XUbO1lIdldF)v?^BRh#b6cXw!w=xz%Q`TmyXrOj(i&{2~)S-4zDX&fmy&H?U zDo#C&L0lhX>PLfqAJ%VM9bfq;uSY*4W>C?rHNl9hUg6l#(?ZT+@p4*L$!)YLRps$p#@4scv>;(P4C zJ9%QB;ht4CkWa5;*@dUlCy8{>`ZU;!z_MzNf;;df7k!wuhS4$-`IqWeYwKB?J7g^% z3{ytwJ41jb!i6{tq$I_tivG0;&`&VoDwc0$k7W|-bt^IzkVNhH))*vc+kN7VJsVHo zpH?rqx2LYp`jp@!@w0+k>hL2n0$kyj^Msa$M8ef}v+Z*zCG(P(p*1F%ZOgJc=q?5j z6?g;r%t8V-H&3;A9ICS5bG1Geze>7Bt-OVM=L2Q$zqg}8gPi? z^GASI=OxlMYWEyHZIDnQc#&|63!q1KT|JWD?cZOuJM!T749o1Wr+ed2h+~>6`NNR{ zb2>3*d_h(zW;gRTGvHf&hmwtNGrIrzIq`>~_q|hq6(J(3d4( z@9Sd|_TY}OED?{#hkJOtLCov2B^Q5*CrcI}dfb8Jem<$N(?@0B6M%$P<#qL8wk|p? zjpDlwxZn|=jfx=rOnCh@%Enn$+H{+Dk89YzDcdQMp^5%q6htmQl$P{iFG zS#zQQ^a2mohOw4!t%6@xUQ*rAes(UH#A(x>FqEK15+R()#?WZLC#S?a5W=l4YPC$~ zpI=&f?06_=rAth`H=t}O?*4wX-1o`vz2;WlpahnpA5^4?8q`iE? zP%ZIM39Mg@f=S?z>5q=D(1-)V&ByYwYv;L*yP6x%`|==e|I}Y$z&qd-vJd(bw%66w z?xrzN0hFUJOCgA?rFIwyQpiGT(gz|cxfjQaM5MCF0O|0RhldA=#7y(j zF@)O(2StmEhScKTpE-x6%QcY*sw?&$n8z{@zdz~ZVvSsH8yvaf)6&@#3J;)*q_S8@ z)2Xf%2^cvPBur-s!r5u3`fzNPPkbI`NkbA-=Ku;(M>lYNpB6?^R#I#gY2=?Ht08?R zL?M@XJOcU*@|V;eAuUMa%T+6UJ@0 zxt=MnWlP}Zf(l|X%LrM{Zt*7x1Y8-}pGIuNhCUm~THN02@e1N?Cf%V%C2H(%%cdu6 z`AfAqr9B^h6rw?^_0KZkA~dS%Pacf;lU&80RVs^=zfgzlc3chZKo0NWvX~g zV`C;2{~m&*p=8p&&C?db|R+9e;*?C8X9{TqW&| zyxtE>0xtaq)%~S*qc=)QZyWXdEF@q$9I3-n-zUkWcbF0Jmx~en4s48~dZWnRPk((E z!0zDWlptngr6q94kXUTBeRxQkUYvFo`o$!utbAana;OJk=nEnz>m8OnVQYm5F&2JR zitlhmYWn$)!^uQ(#*XLg$83D^&pu{rd@jiA*>!%TX_s1(B~Krc4%`rtyXgd`yhkC+ z4@Hju1&#hpofvPn_$6^>!HW{J1WgAMp12$@OI6!J z`JV|1y#j;5KWre})xP(hj`tUkGSSf^vscUjJc&Erw#WOU?$>8Z@ZUW>J>l(CC8hYJ zgj*QwnV=v#1M<`k@P>a^E{ZhGz`#K43?;niQz1ZW&UoqPmPd_ytk|6B>V*7Tx%8!& z_RhO)NdRMlH&{Z)99TpWa**Kk94RkW(gh!5$!9-yj{wnK`g)}i)44F)zH@@nN0EJH z*2Kk#{N~F!74c&h8-pq0tdsu@2ST#TJ_p(677e;VqLAQCa;H4fckfk#3AE(T>}TZ; zI>?9+-6~q>RVIFbi|~Cuy!qLxS7+ zpaGhmrloZvq2}-@7j?Yw$`(M2X~{=&b&2VSd0XJ;$IiFSJ_8HtxmXT0Xt1Hz{TF)> zw!Ed!LJ9c}$mr1T28_JpQy&g31nZDe7~axLDQ+^kvoE~2`yu2kr_@Bunj7GYz8s3P zSM3Afd+z7JJ&_~AaDqh3TD9^6tn76pgD2)Pl(BTT$+P5hwJ@#EU80i`a&qFh?j*F` z#GT`P`U-CT{bohE;JH_BI-33>-~*>sQ}$5;Ic|&Fk)h}1oW;&=S!Exd?=jN;VrxbU zw}E1@m0GC=Bl{Ec=M-Nx%ewF$Utr5{4~uBPlq_BQ)xWf|LgT(0|7JMh}+v)qFS*} zJx!`{B41vMzm%eZFq zcxN{?HC4GCEfyY}wxq}s7{e|P1YNfA+xN|Ry2r9a-f|=l@M0ILv7fGce=d$VJp7!Iw6qm{IC9P@Sg$6vEBm>2YugnSi=98bPYcT)n- z-vu4(n1#NF#*gqzHZKJ{a<4kjD>6$nD-?>gj~-ve-Q&sc#|{=~ue`8a5fzHm!4Hp- z;7|#^SIDiSxmps%q@dI$lJbh#BK=m(MaE2jK7j*8=_jMYk%0IGJH5qwMf$eQsOMs*^N3gkFfvLc>;__S4;c7dOq02|xfwZp>u^{IY;{Gx%5i?-!+&J>nMB+214w@;qA2#h_ zvu|9Y(YoiFKh5Ahr!1=bF*|7xQ9`*3z|h}xfV=zTpmDg5?*slX=#y>a_JJMol56PmTE*sgx@EAt*?gwm7E`Z z0lsgm#q&F3t*W36(Eo>{bKt714a4v`*|u%hWZN~>q{*6W+wSC=Y)!7olReq?WZU(v z^ZkRp*V^Z-_kEu0zHYQ$36Wr^3o&-SXWBwC9!jNYp=4ZTo3Mq{G*B#80YmQl9HS^acZ|98HFjmKI-y=CuD_~b%j7*jycecEXd-&PU_K+N8=$zz7j=NZqH zGIxfyCMEV+JgIx}-1qyMIMl#b_*jH?$EZL~HSzbg_@e7PmAvUOxiGX&eSQ6w&VSUb z&ufOCFH3}(6EsA_GYqNG<(iej8DG|#t@i(>7S32kzFu_1qmT&FfmHzD1K9Df$j9|9 z3j`H9;Qwsty8D`4`O@jUEqA@&tGl9#is?+D)Y19*J!cI}hK+7D4$x?d&vKJqpK@`l zwFv1Ik+|^N&uE6fJ}ntRg$Rd7X^405n^yaLgHQH-V%a^3AX&`{&6{lud}v2P+tF;r z`DfXLz#77@onfS@Wj$C4WO;budd}KJFg%CxM1_pV-=B8=!l7x;xmk*<7xGKU(BQkA zq8x}eDF?0{>a)j7Pr*C*2fv(UXy7~li5+@u$^V!bCL4?Misv+l98oNrTgvshXuE6$ z)3>(VS#?YbrxzECV5U{rSKH@qMrLq+9_#-8-uQH}`snz$_vQZd+k;5W=Ua6uhzQK) z4aE^U%}C*1gNOydISQ^UrP%oRBHil3j*cwiVe(61fM+Sn&gkqd0TPd$BNG~(C(iIT zlm^07aBCrphBWn_g)AEvxe?y4=c$cy&9P`NYKo+2(eo_RtAPSrLM@E(`c*lDgqm^I ze0cQVr?h zH=NF|g6Ay#@~!8iC>jV|{+urB{&3{W;eKYuKaOo6`}91k0BW=N-x__ zH5Id5N;M1CW;$si__w5ixXP>Uqoob=*ArM?oPGE-zr~1i2megIDYl8dZjLf^Vws3d zb;U5!IxH)96FVVJMYXcWgyiu`8bA6Sk}XNF2@j6CC`fKwk4+4-<;|u;s_9CSAPV9h z@@~z4Pd|`8HDy8&Kwn{P!qJVtkT`E6htM+cy&fb5=lE+ZEHo;=b!t`~m#E2&?K_(+ z{QCC4AOIYSu=w%gcsGW1=)7f1Io-gU9@t~}d2a~I<2WdDt(VFT(KCKGErCj&Dqo;m zy}Rji8Men;$As8kGuP7t-~)x2)XN5D$+G7tH`bgYk^S}0?ZN0iSp6hwGncszS16RY zCUoP!2a@r&X*6$GH!1&&&{4}1i+aTy7gLXd6Q9dJE{GuWd4?>ZTzj#0ssHwpk{X^B zXGJ1qhTOvm%>kJ?T|nqr< zn6Jk|8;UlZt0!DAP&*y%uuYa`;3ZNK_mIl(-2D%FtF=p(s93C#nK2PhicnNC>dRq= ztmt^?WzGrE_!p-e3(+wH>jDvMBHXa&cPPguJ_m?=*5ijUBE*=cU5$18uS#9k1b}1>Qx;~TDebFXw!(K(1aySpdROE$ zb7%>d;!wwIS#8Js*ccfWh#J&)LHNMMWN0_@9!WN?jn15Bx@}O;mvqa6S4qk=p^E-> znz-6?ud(n>JFKP^eh!}62(R87uQ(xtKlgYfBqSe(pKnv2Ncv0NzDxQZ-)4`w1QmeN zGBW$Y(1>Q-S%csxa=rJxH8}irbG6eCPIgfcoMTS7{i0Sb{2xfX>b&$r4HF0A>bs(I zDM?f=^gTVb<_W`hc6MefmsJ?l78%sKR4$kR&cfU#vvixx$B{`~JxNF5(nd}gO1VFY zw*+xRtA@DCYfgh$gB`Hzg#2W(-H?h6w^|d}kbwmmHm1MM2hj#adXsob^nK~0I%2Y?d4Q9O$jJ9Lz1ij<_^I7EcFto2EjT=Q2E=Z9kq_x2zK7baWul3*HMSmP+Ptv*x$0w_JHDGWg_fRNG`1rS;?#^*?)u9> z^DW!7;H|U&!zSfTFGu$q5m~F`*ZW3TKpK+1Kws*&6yI4G1y6`#{yaJu$Aold*}rhM z;uC(;#`Y`D`L%d~{wHj*Suzbu6uc_&z{dNdOsNJ)k6&wE!Uy@+x4|>!(EVX>53(ij zHjhxtX>4itz)W6p$-u~lHoc4N7?$>O@2l-zV(@jRy`y8smYbcEGeo*5sH;nOe0)5= zr3E*O+pZ5&MG$Bq&5$b2kQ(e@E-+IP2UvHR5^6mvj7b>VKeBW10h@S|)Luqd0dSud zWNXb*)tZ=@rB-cv%D;7)&%qGv$Ko+rl5U`zo0;itF;}XjNs}x_lPo5jC)1`UEu43_P^ksZ>3zvk z8lRlpJvIE~S5ZYJ0J!bfBf)F7uDcRe=rUowG?8rw<}r-SQuwh3 zs1B`QL;yI6b#LH0BXzq6kazptOcE?8NEn*>3sDjshp_ht>1e;Iv3au7&#z_G<)LC$ z0pJeOsd#|0?pKysbsblJWx~Ux(kOyViM@Q8>CgIaYJmH25l;#7GlOz}t!h7x%4 z&^_MWXvUf&5n&V~tjXo7q%Snhs&?$nBm#$a+saJex#}ZkMMd>724Y*&2qKFMQ2xL+ znW{*ySV_=yEkH7I5LnHYxD`BUU@+h`_36rvRDYvu5NB$D;I9|6{5%f4*&6IA2fuj zdvXwlq0`=VIC309XJ3-iE&G?&CJ_-{xbeUT9DZPF7nI&WA~i<_z2EO>y1Y|+NgybN zTxEy6t#*COKgZIDGHrtQ@^s%6NxjN#||*5|uj%ZB^+-Ug);U*hLl>ZFDyt& zQkA6WF{EnCM@bin0Y=7RIYcZ4hiJND#cU*Vc%r>Ak_cWDfCaLWr9D1u6+-s{UKV1M zReQ$}x@jf0v`y)zc@b#FMRb^Y2}9`o_^yNXA9~swlGPs_YVz96*mPX|UU8H4O+FBl zvQEN`aDeHwd-B(t`)M3^O4*6KY`$hy4OPI)K&$(ch3J>V zr{ri?c|_2~g}nv?w&3%>6&(z5vh*=0{u~q|wQ=lW17FnkBCjdKa=uLv z<7S^_ecLx#mqCJ~(u?pNZ|(kZN2av}RHzv%U&SNRQ3FXQj#+z_JLsO~D!zJtaZ5Yy z=Vn_WF{xT)h_jibQwo78aY?o*!|*f4$%rmzfSGml_?&Rd*A8J);|IJMeD$vD#HUzG z#IHip;`5M`)07y^k~VWdjjji_vmYpVbuqG`Xq^h_EPiW;!jEmZYt78y+_Zj&yOn?D z`{t$rI|qljpPyf$Hq&g5$EMtr@M~2IVqjDh{JPtce($4KZ^E6ap&_a8$94SPMaQN6 z?YRdLEGH*tqOupt%Ii;g$tHA`^6r%dsTjsR0MsL3AV+dtDlQuOB>cMR-;SyL+0=0I zQy3wV`yN#A<;)K`G0`ZX7!in?g3rbGyN7ZT2Dup267|yKOoUCB>z}}yfBaB<20{1p zbY>O8pCwpOx4|VxFJqMnT@Of+n@mQQxz0;2lTqYVmzMkSm43M(QEk&u;VzgHg79}I zXJQBD`B(aCV$*Wpd-C7h=OV`0fid_I}HY?)AcZ9Y+>}I)CyFMddwNKCfJZ~ zosIzzXc}Hjx~mCo?_r_nQhCpa1ikF?(to+Bs6AX>8-A)t>P2L=NjSz^AHkb`P0n$* zW&GJdH^ms7pa)e}yLSq~KOk2J&{ZK0?sPaAVVn9wL@w&bXJ+Umj1b`Vg@aWHbCupN z-@RcXVmpa>ctWRG&%pk*d(Edz_4R`0ckXgPA8(K%&21sr zvT<+twu_tot@0ZLnYXuhs8uE&F)_Bt%S=4jg%L-{6P=itxc#$g=`=xY7rWMT@Xuh7 zG6A$T7XcoDb;nMY?xsYNpk3TgR$!0 zUsPBzH@N=%dF0?7)t%5plNZF5!8+vS?@Werpko*~@mQ`SOE)@gN2$baXZzl~Z$N61 znJE0OaXeW-QT1cEAdedtqZS@X%{Kx|=#BgWFVbfW6BQ;TzH;$Wr7Y|Fafq-Ea=?7dYVyEXxD*Bg*rs$= zE$TB>=&=xLTQAyAo52$l!`C66F7~XkeM$Zti(#jKJv{)h3b7{$Qjn7qlL!_P5^~;_ z`x%^VH(q=AgB40_qo|^?H)iyl@b<6r_;7kGg64U~lUWM>(UQnnJ!W%M0qT!SIBk$FQYrBh2H z0~1{USi^g*`YMOeLr$K5%x}9El!CJUJxnj%Vn^n@LLgVK*jl(Ab?CUCZHXm!%lnth zfgx@M@Z~S?XzVqy>j3FVp7ijFsdY7!Q$HlTqWuvb%VxVBTBk*?AVR+++g6)+LcC!n zc=5=nmrO-Z>WFbiUT7H9)1V=okp7GJzbn#ibEa;#--w=55qbaldM*j@bs_Vz z>OM|USMI&o=5$P2;#roC>f%-$Qyfm4o`HRx4$~?uw;O zueP7*>Ya#w%y8NDW`41;|6FY8a@#J=vR^GdZI>;ZTRl6gsYG7fO@k&SfP#b}2@bGOnOO(n@u(<2kN#7KBNYehjf9u;em} zv>mtP&XoNzOYDG+FjVm27Gx7+8T(57goySP(Ur zP$7x#{f6Gi7#L|aMTJ6$` z6Oo;>GsgS-`){*Rj317UXkb67cPA*l>#k(olB>@5*_nvnb!2MYZ=PBl;M?}G-5b(+ z)rSZ+MVVY~c4{|SV>jEbU_^pD03C@U?A8vXi+U?GqLKRp&2`Yk$Fl@0n=Mgumk|rv z7%#||*s*vGWTbu-MK;9vKVqYh^S`Wk+Kz?CmxA^(q1`0no6%K5gnoG13j9g*b`p;? zBQ~-0bE%bFa-C3Ghc_fY%7otYMjS<2b2nNbEVuO~7@~idt=0E6sS=_c%Qm;PJcK(R&-?G$lWhDw4UKOPCZ@TN{rT&TkF8SVsjdHa-jJ7QxImCZL{Vux(qzq> z5WeHRz`tFZW`ymYlj0Bj6q{7Y$7tDVNt%tQurHSZc6bMS{Hz#wTwouL!6`;{siqGi z4YBSdzo{Z%W&_@r$*{fl(;5?J^PM~k>l_%m>Kpo>RFoOq=RW@V0jO~yE>WZBK$1{Y zR_=2mGO>6&`yq1M>3Cci!%`T-(hi4+%OY6XMFABUxy*)SyV3v+NRTf9n~$Iy9(MZe zPpDKX$1@aR;-o{0Q+f?thz@x{kq>wg&D$hK%4v_XvOCCJFD(+sRKe|P(S6Y6tF9`s zPDHeP5k}$|iA5Pv%}(s3*hNIOhu@*}#E(7>T!38<>z!zW2)xiZp?@U=zIJ#B{8Ky+ zeJ#pkIxnfMy-WiuPPhPlPDFODt{QV(apB?NV9OhMNwHK6&J4Deo|ur3Q0x1{+BNIv z4eK3P@Wl&*&oG19!x|?IsUrMLh13tecmrAq%UfE~Z=MCc_yNsKV_M>MHK&4xj|0j; zj_fO*n@J*P*MDE6_|mwxxi1Y%YL)$7x{v)c+4j#hD;F%1qT_3;%?I3IS&M9VC&9hpM3-8PY1SI}-3x49 zgMfe$Gmb*dN~;Nrhdng1a_@M+coC+w)S!F?+Bno#ZO&~Ls z_Swp$-IUVu?J=D6sWUGO6kHRjozy+73Jm)v?U;+!%j>Iy7kI%+5O_6UH|v&DT3Tvp zZ5@r&A9#oxn43%T_K#Mi7nx@rKtAx57R`zh0G54$Km6XtQ`5C(JFtcKxq$`1m8JA+ z=@dv_$As9V8wXUaT;Q((ghgIz+M0Ga+qBm2Wf7l&ef|>?lxY7I3rgRRX!U?Rh^r3rAx6G3OM@KMQLo#j56= zDzAoH;{cjd5($42j#~$lMcG^_TiL#0vudMNbtcmVyc=tDz2j98n>xzhCx>oBr7P07 z99xy;G;6>WSY=8tSbzIjDkmwqv9a+RoM$E^4B6ua|5U(3k(uRXJf!}f8NHbdJY-%W zp*XNsu3Up*x!x4E_InFU!gXgT**|Cs)oM-3;R&HBz`v*m2n_{Vm!F^Xkyobnx0tGl zJfjvh%@~r1Lm_hfPXy(4Yyiqs=T}NY+#azWjZh=9U%09{{<}a~r@+0L=o4E<*j5de ze%AJyh#;l<%*+}w9%MN5IXO=kG0B8`oRdePnBCzI%v1;A;6)HSZwbM&vvM>HZN6n@ z+V^(X_fwzfK?*()C3umyV9mwNEe#mx4S_8<+YGv|xvElhI zQRcw*GjayfpaB&O&jmrk%EI+-zMIqG#2~dEh)=!I0*DNgg1KjSkcKtt4_CBn**Q-iQdbpGCH2jIGkSgVcZpnY>12oN0!rfonW>cWH;e-^S2ik-q2 z>;_0goVPl6^K%=1#e*N(b-bA0%F#u<_wmTT9w8}a)dSwfN=>KViQfGqYbYgr_Lz;b zG@#C!$aNpW|A7TiJap!>=FZY!NR?f3JGs0}S4d+Lqe%`j#^Zflce!q=^+Tqlq;!4W zPbW=;@Q8)E6VXl!qs{Z>c+!Upk}v5C2??3EZYrO#^rWa{bn-c|Ajig)HyiShFDF;ZzD!7P0*cUTWvWo?!)T#lP>r)JYc6(hU| zAIZ#*g_l3gPXS_FZ>ppfCruqr8FqTRkp{kxBh7I|Wk_*n?MFqy0|9Z;5^3_(27f%+ zPwx4F+fxA_x1R5(hN!J9FHyWJIEape*ji|S8qLbPqoVk}%DDgK>qXt&q{+$2|MHg0i!O`tc|BM5QIjzX7>0A(&euu(^tKXqV)ue|mx(Xnm%Y)u=(Qv2ffacY1AwttaA;jZ9$tTmotJk+U0uD72{GJQ6t1^xDQ_?I zCf7$~c{*5Ls zYawlDNJ*2;m8|`i%=Nu{W zC2ais(Yd)KSTRy}4-bVimUToT`Q#1)Py&{ps ziwK0H!pwLg4zhE4t@pm3TRy}XPrtYL&28yhM3MHYhSG2e9knKZ^8xLr!}C{RbdOeyuP5idt2 z55R~o+Sm5T+s_i8rN$$f#mw8>o8(@Oz-e+h-nMk$sq(BC9^Pk1n9z2nF<$2C=2orU z?eF)I>vNHDPw^o;#}${+R#}Nyp@ASQMwcqD-C|GtaW?f4Te)zOeNJ-%aBd^oyh&@> z?!5G4m6n$FyxtuI8)v|xmQpK6qT!{FWVGP7SW*9kzH53PjhrvR$y85gc^Y!Ue}I*w z0)KaP#iIk@+-o1b?|y5f>R-1$gX9GNaEGD=Wy~*~3xFWUtb5rMsrz`v#tR%jWrk`3 zb0w+PVC1jct`a&HZVK_x#kc9-mpf#7t_2~(RA)WH&QxU)zQIC4lP(}h)wqRIgvLi< zE*9M$j@>`#*gs94da}7S< zo-S60*sU!$+2Fc)ct8R;Ig3-Wrw--jxE8r4mY|`g;$@)V;Mt&g@!3TNe`I5J4X@je zs$3H}MC^7B1w`2Vy@gjU{(1=odZK-*b}Dw@OpT`d_^ID$gyxGme;uU|F@!BA zpz6K9z5n3bT(LPxpa)vDJM#$G6R;H6$ePbi3muY?vQ#dpalp$=qyuk;kK8$)u%6f0 zTZ(>I$!5cTxp&R;IWuzqh66)Vf~p`0BfWWtsi$EWA>P$B8V{xlW< zdEgfMR3@0~*z>z$0QJx*(Av_f_1TXhPiKEr(0#)!)h9POe;Tp7gnDl{eB7^gF!K-Y zMBiT)H>5{MS2h1>(k*qPthd%JJ zbm}%~#vv9FZafe&&3{8{2bEu2i%Q6C+XL=J*x4~Y+vvb;uPJJ2S;`Zg7poo^IXo2*r#N zC{`&_+$6>H;&B&Pegb{vpuPalI z3*WSwG9d3mL5^#7Ei-%ZA|TF5UdY#nbMTgdl=KjMxjZ{#TC)D~ge7}Vb$cLBl1{1W zXe$l6ja%@wwRBsztLehh3!C zg!o-J&-*`fopf))^O1*W9M*XJc5Nge*>h~$#d;@KjcB*4+{_7@x3`?RxjA2O&e_BSg7m2e_Nx2y@pI^A ze++ZY?iTWAc6RYn_s2a_;K!}VC#IjD9~UPlrk=4LL#iqr*2s@{r8&uJO~fP&1ac5P zz47;*NfBuNtC1lTb0Oi(0~12_n~|2FkYFV|zI4iZAbGu!s)-?bx)}v4ilNNd7}Xg+ z>`GlG3oXE6Y18gxQ_f!0OkH&Gz4HDLTkMYA%8(U zoL+$t<)UG^OZ44ZLr--P;+zsgn0SHB{mH`1!};2G100qlHH6ktFZ%ScnUN8=co1l| zHsIYRMV=aLNNTS!-T#{!_3Jkku_&4KNxQOygoL1gfWonBriF!ttbEkUiq28Ha%O37 zvmFfuhIp&jjecI>o;n1XH^u-ueFfFgQQzYN^kJx7B?eRgjo;w6vtRIBbD5E5lU$PSjhkhX`lZ)?!%wjuq_eCh-x$Lh-vf6-dV-C|qXUfe2L6 zQq4+el~a|b%q-#|4}_apCE^H-9))iuiWp8Q1QFgn%P?@dk<6lkvD&FpZE@}q0j9Ko zimau8GG?v7N0GtW&D06?{8%8RjW`e7EE*dO0$X^y(cON6?lP=h9e_FSqnhkRPm1(J z{9re=?m?w@b0e6NSXL$&&%RLglH2l8I0#Qn1C81s3Clm_YCUfW&dCTwre5bSqs#i6 z@q@x7{6_FNFp3c4ja_;{aOP4DYCIzTJNL4)=s~^Vu5qS?7E3y3Ny-(9;jCMZO%nl9 zzi8u%BzM*zIP>V}m{3+$MqV<|v}6#Y;=79}3#iNiuyG3AO-r=i%_>k9bgIeETYvGC z6a8APnK)tbl@4p9yGK;sOV1#1M{&YfMXl%=6ioMo)e*-T$xO1FCCNwZ3nC+FARIXX z7M_w6W?fk2J}TdB5)BxU=xPl~-2@3rlJDt6<{Fw^YN*-Z_UqN-E`C_?&Wep$LI25E zux_KDMxspY)Pk?{b{>MPSk=q*n}~M!dy3XfY&hrXbFLYNH>0)mC3l$`Mk}jCEDZRo ziy{N_4qjneWe#Qy!}Y8NKt?fv*+ieCtX{bQ*8i4TsWGIA8yb=l4U1K4_T&G^4jLv6 z082ve`bj?$hKGhQ)@&&OhOetjs!TWqRs@386u_F>zXJnM$L*k+8kWqQ9Au1;r+L`k zAfI&%(KSzqern;r+kc1_wNlaO{Q#U6W1VD_z=2cv1hK|;shxwVrEE@o7s#- zuaTR15l1f@(s#;IW{Qy?f|BwPEXa=#SjPY|S^634ruCE~Vw1^lgLrOa7{6?D2H<~p zPZ7T$>~X$%_%#LKm1Ff4Jp>Hw$z$VVGi(QFt;Jnd7dyGk0l(jNh^Q~!ix;R)1DE!= zQ{@xUf?f4xO4P_H3gt`s!o-0S7Rf&|!q9y07qs?XPHQ9V)>`*K0)=)%0?(wMdjS9d zM+TC?T_LXVFo>Af^(aBaDqFcs?*ASJJtmy5Ea}FXrFr79AXa8iy5WfCjma1k{M3@w zam8EckhRL>bQA@~Vq+#!(UWnh=?HzPY;NtqBY7E4ly?%WYFfml}Mmr})|-++8%sbOSB?x$f?XFafKs67?KzBV)U z{*#Z8nej z$}q_h(3+2rBe_%Eg>8%&birgGYKeZIMxF&7S>FG_Z6@YTr8!eWZkP>Jx9iEMApf!+ zcZVM%Rx#O7K2jEZXs;NO#WgQ-US+`;iMidT%`CZ8!7NSwaevfnDfg14C|Camsr>uISIV-5yb|o0B@7;IC2aELC+1PplvtY?`l+`OV#JRJ= zQfAX7DBuo{kq_J+US1lnptjuyKpn|Ln_su$L{yMAcaA8iVV2k=3}<8d#umw4t{HgL z&ONH?D9J0CN1TZpW&v8?>lD8HF8yn%oMIF=)t6h*lTiV|iF{|!gmW+pv~)oOeDj}m z-RMNu;Wtv@F*CdN#co)e>oh2~9X=6$k#Ti#v*KvMfIcE#uNvb27rtmwT}n!>uTClqG%+ zv0qtT6#guQUu!V(f(qQ;3@paycin(nWC~{M&5h4VDsEPeI-N!d!qW;anG z<^_dr@gKOsq3PC(Q)}b)g~Xr{^AA7divVvKB~xbI^Z{t?H3Dk#)=j$q+Wv8J_P6}a zD3$B)??1Y{JlyE;@UZ1<$ojlt{p1L2RHaj;N$$l|5ex#Sj=jAJ_MP$DYhcp=pBDg; zEn>Z`ATLza9RBj1KUEBISWDVu4}9`*$q7Q12F7E3<_FCaed8n9Em#%zWpA2^dDI~` zO1#vlGnGmsYQ)MFFnnupXd4;e&pwziEe4H(>sxasvn&wpIBr5L|CPu@=@<5wxjGC5HKzqFFua=zIp;d@_He8G)vPXhb`$CPp7B|Jjz_6*03& zUm8ulMfe&<9+?|H$FbZN6hL4ADJ+moe<>uzFHj*LF%04x12zdhelM6fAle4c8o^qs zv%}Xbg1ZzC8fgI63>?Il&PuRG}wcu~`zw0tqGd&s3Mk9BI}cG#sUtzP4zD zl&^?Z0M`U&_7#yEW)RRfM4Tc;OL6SEoE={3t2BR-1_C#x*9n8>>q(4-#9zB!huM&p z)56khp+^?1oAL=^kR+&b19!5As+bC@nNH%-t@|2bT`M}6e>3xhS^UM1qerh5ee?6tQS|GAB8Gfv@6M8B@MPd2wy2f#cv5`p!7#)BuQBpuDUsf`*nhP-(;uYYZNtR0t`)9MJ#4HY=#KDaJ zmOAUUms-^(yT-%Tf;Nv?SXk*5{WbzOpUaXP37)ggnTKIh?9e@rViZRGsCFJQOUKetn6mihF$Yd^39| z@zZ+KV1SM@C{`u+i7z`k3iQ}1 z$2o^ZCGA~t$F#W}=c@w9yytaUP7QvEX=XV_3m5Y2jB9LTRVM7?{8PkkSiH-&0#q7= zELgV@{+%|G=u1LT?002G4?>OU&Y)}X(x2kv>B?WQZqxC63tM!dm8Wy@R~Tr19uLe|z~E)Z7ZG$&s$VdB|pe3?GF< zsICVwsj@)-vJ3$3jwsvxa{#Fy;vvwNRi>M;^t9-);6@%&Qc{Y^Ug>*mgmph>b? z27I!9oYa2&YQ$jUaMel#tTlU?nS}zDiW5f7RI4>-EV-81kbr?hS2N|-R zF&nhQs<=1o$p0w6!O5t*n9cx;>G#?;+MOVoHsAy|uAFaWFmeX^6E?VsWZKKR8&EP2 z8a__qdWKz*sAbJ9{<>BJnu$l&sx9U^q?y{Q^~`mafAlodR`Jm<6P>3~WTO`J@6)Y6 zDD;%JC_uBra+E+?Q>u1-BMW7s8X&^|4h3N-ufBNh8C`2bNkJ;&S0cdfVuYj7=15Ns zc3i8L-9`EX6eZ^u7Qz-PwM-8ubN)hj;TmVs=vLz>&QZ}*KT`lY%s(jaqO8YHSt)4L z)Tlqrka*-Huh^yAC_T^7?Tc-Gy39}&)!RX(k~#!&3L_4qJ|g{d1yBJbA|k)@{uKWQ z5aiyFY}|w+tFs%MpvrbC`q041z*H8hT)5eN@?-l$C#eLvW`95`J4NNDG4}6=2#oe9 zsNh&PAMI|($(B%aELb+ih_b=s(TJ8Oi1(KZlnWkF`slpMudv=N(hpsiSKU{5lU-Z? ztRm0AH@Q&BjP%H0$%!P%85LpK;VFP*w7Cu$*j@hM;;g8mLYY1$1$zW7Pm@|)6bH!H zt=^rHAssA~irLl|MH_(pck3lzHfQF|chPA}g+vsfHm%xK*!G$DBD5HoC#j;t8{m?V zS2#+^A=Z~5t4?gTgd#P$XiS_NX1hWr(fa9%jMkUDg&k_%GetOv;M-L)s#rqCg>Fkj zPpvyaL-d`7$n7sLejPL6gf8){Gue0UVsi-v!V#u3iI?ADBevQ>Z%sc6Prnf+be?Ib z9*le-kUj3U{9~`o+)QQQ#vb}~ty%k{R}|fe zy}U=b0PdC@h89^SDxRX3_DqJ{HAUW(#2gm^ z0Re*ji*kwDv>P$FZujNog(7*Lo(Qi^4<%jCSuTGAwfxUszHYTML#oLfSID}1hGIBF zDgt2INgoLVl1jUo3~nUdCskdye(+_4XbOLi6s2lD%Dt41^u5$q`5#=7F`6u89rlu@ z^g(+FC<)>@cl$(h+?C+onz?+N7ezM?hNF$B_#au3>Ok$rbl?S-8shE9{lEN2Gvhm4 zqlbOt2kiXE1M>D+w6Nc&J08E~mGTych9KBN4>kkM)=;$LFfF-v4x;aJss+&}rf7)% zJscRJK!|N(Vh5|%Yzuw5bVy>aZy8stlS-i9Q)DmaCV z$knWtmc6UB5<%*r^-T^m7_U#u>y}D0dH4>aim+p!SJBf9JQFtUz85ORq=&*6p(9mo{!;8TM30$;7g3-y9mV;+%WdNMx%47&E>h49y zWq7F-xYSgu@>^zhHkHy`!i2@w$_3v_z$>l+V7sU-(X|G{{8MLUwk5qahIaeZq%dYw z4vMPK3qc^>Gl4ipK!VI7^!&B1QZTU40-@VWFFeJ5Tqr1U(p*d*!#e;d9YEskfpZM) zCyVMHazk@>!6d0Qgv>Wtme<$x z2a&$-(Rw!NhM@)DJPW4FQ~z`3`?X-bJ~t<^x3_0xp}~L+)^Wp<`l8|LIg0%&Opv5D zs@Ci;6M47tppTrkxPU(VN>7*Wg+t>{cN&30PgI=r@-A?R(nIXmr8K{cHbqg8;_c3Vl{c=T-}QCd~xIL zJhY85B?6#r2m<)|+Mq+lQq?v5jH~jVYvmjR6Q(|16N6T4(-=|_Y}bYc2A~+z_+n#Y zw*|JIK>@O`C2^2oAmP`7wUG=r5~PHL1ejky>K84V2`sfrkuRBct!*`#!l`g{ye$H< z7*`Z&R?tuaWK`Z;)tG;Nx954Us4Qf_gxpVV@U(vE79Zo^_11}_u`bc!bwli%FK{zA zLUC&w*~|%HpNDP~K}}TXDD%=9>O`$gaAzHOBc32PT0=Q$;@3_61t ztexoifVNRYL>d72u`Rh0rVhU{q=I|k_MKN$j{3)A!MuCaTB{Q5)edR^9w1@r(COp( zut=L}ce^)4kdsrQO>dG1y1^=Q-IB{_t(g>6xyNY;!jnj}(mbdbGMZV#wKkOdJFA#a zM1{Cy7ZO~f8f%n|gO2DwA(xX(S{BlB(!B4mTSBlE?>MG&+jcA$x992 z$)ScSZYcY#Mk8TtKm0(8N@yYT_L;(5JTz@96RT-RC&u6@e-!_V5n|OW2Z!Kgyxwj+ zcH?~ym{sDCgnZe5ZEe6Wqa*P1ZxzOmrB*K#yy)oGO$3LxUQawQz?%N)1uet4?$?g) z{`)1vc;_}EtJ*k7aAkWCs&C-ugTp6IlPQllI74h4l2Qlo6 z(VfbOhi6a@6vSE54UpA@+^o45eV9Paa9TBoakm_EfTpmHda?9+4-FS**VbaLublv* zMKeYgLw{2lztUlWm(eQzH_3N9r~yBb`+LCWf7tV;GMdH~0Kk`7PEt$*Jy3&T5bRyI z$_(G!G_c+3A_f;JJzQ>){@IHl%Pf_Ph9>iL_gz)KTrD9ZCij^a!TKfcp=h4u$FtPz zsv3=%f~!pP-g(k}s16M;+iE=Nf6+}@jg2lgR(AcSaON^ze%`~dV5BtOmsyj~#}P6g zY>zc*2yF+7VG#P5$wH*oSQXjebtjN_PzqLGM^vzHGU7sxbZw8zcNPfGkGpCf_1^4? zJPlbbj^!H&)ZRk>1fWDktQxA)TIk%cy6sFGWCBow$epsEE>D+z82~*}Pnwn3k${iO z#>Pg=CS5#2!v2TzwQH?_do8f(&EWH83D&B2H6>78UHxvM)<1Rbn2T7@6OEFR^62t% zczRk@)cB`PXEKrGfPChzmjDDjdBUW{wyRqZ7rN!{o=y61o?V9o=T#3)v|osXeg}Eh z;a0h5=jRSZb(G{|$jj>3yn_U1qYYU+M$IrC+9B#zeHGkBN?L)4TI2zXYDOp!`KSEO z&qT2`X{QT#d3m826N_j#N^?o(R^@v5N&WU~zXq`EdVNL{!eHUh(iz_c0qEu~S!ujt z;TodwtSM0o@m!pTfuQ!Mk?Pa-OYD)4^Em?wRY3mEj`5^LBl!PlrFjDI1g_D2IA04P z6EOm12gXT5f~!nV7AosKE)AG;8mPzs;&8=k^fgPTy-#l%MqP$Iz6kKa#y@3C)J3!O zeOUDUi?Lldg+?92#5lgTCE`!JRo}|p2x7ZaojDUPJ~35QaOmz)JJzFoZQL1ef1DcC zMN}SCqUvK_O4|8ByJ!oK-rDQVl3Cg-o00FzPX)jljylpCX1>^o4qeZj8kgD=5bQ?X zw?UdZFo2FvzP%R!eubyPu=f!+9R_#I|i4|Jb%0+eu^FYA~^#G-_;f z8e^KqP8v6E?03!ceuQ(*UTg33+xLB4n2RxQQz1%#u%${S^a^f?FO&Yz9qGRO02p>X zje6ZwrHY)mUmF|K^|~m)ee?UjOMP}=a^8H^Hbb_5E{MoVBv4ONdIJ$!HBp1_8-t?| zro=dTAS+izt1lNK={S`*L)iESMJ$7P&S=HLsP>-8U0qI!ltGcs;H$Z&kbxzxKkrCK zbjUe_MyTgZG@Z0Ai5vZtOHmkzZc)B+K8LlNA)pq|Y>I-EWKziU`Nk9hC_hk|czYddxfz+Nl= z*|sa}d->hBZ=Y;+AXr#VUY;syXkr49SGC-j9pQ>Pg_8Q0I_h6Fdb06ZiP9DZM}~*? zmN;kAPhVLg!UUM)PjZ91-S5d$p2JKYL8-Iy;*OIk?eyn2+yz=_4|~`JlgI^YLIrEY z*7a}=MorV+It=mEnYNPX&>MZZYLZ^Pdjr3iG-hhVRGjz6=bgtmQ|NoXqk4m4gKPF$ z_9mzH6j5Zi(c^p+QkW!q2|a4Lg~~|fR`IEK(!H9=ctYe6p-0gpFq=1A=+MU~ip{(~ zV+LWNEN)rJT~d7!7_@3|v9MlUR%Q}!Zro8ZFg#n=eqLX5W-HM_bVsA&pSR-s3JpVD zdod&_YE{pwt^qi~cjXk(7KD&vD03z^#XO zt=jmCLkn9=enefA7K2E0knJk!8!N2rMZ1i%)BX*935@h7I6p;!w& zb(Uq(msFF(CgTGw#2#?F7vuLfytZ2AgA5e;G#XgYo=jvxgqhXrkMt}qk?Ww)#*=P> zS+bNq@>_P;A#p#0|Eb%DMV8OJaT8>9YQNa$#SjcWjr!f!nk8KYUj=&N0BkD_>tlK^ zdr%;`q*SGfCZ7w#^R-rl4rd6lGnt>BgF2^s{PN=Kron&Usd@7G zUJX)3>gN#Z-Vyl*^Za)AFyy|e&wa*v>@+^YcV8y`EE!lJCU>UzH!OiAmODRvfY}X0 zl)36cTwL{R*&7p`iodfwgTFCZRoQ@Vj?K7Zdhu~=z>PR+CKbjkFNc2YfaEZq9^ix2th`e zUkD8*%vU>btj&O%{PJPe{3OYxXPgVC0CsN?&G8&Jc%Us=*`(-3W^<~FkfH*%KMB{f z2^XE*k@&YI^l|b=k^zO(9~r-NH)>S&)Sz%Jao5wVXlWPEwENhq_lDT}LejD_xROzz ztVY3|R28Uc&73^hN^}Ci$c6@X$iD|M@t>L+rUL(?+^0$PkO&Ayzz)M<8lQOs$^-o!59i@^C zi&sxgP2xuJ8@mDtOYPl{j{mBBEv#h;@7WMIU(@T|Y`^XrPWOM#4gx>~(m`)GGx=1w z;gxl48uTTBT4e^VT{R;*TyRv>icBUeP8^ELeq9|^y)Nn@+cniP{E8S9U#6m+WU=Qp zWmwRQ>GR;QY?c*j#!@p8-uhl8zn35j$&V9}<7-;m!DV&*6?`i!|1bEG+gu5hAfto8 zFz$}zTe}vud!V0X#q_cM8Y{`1`NvCW_CA1YCh_#tK>!#TB$j-7>HukKW>4D){I8=6 z(airDO%Q&8+Km-|vp9I2nLz~*G$SA&?3uyXZt(q=ud1H~$3*uv@5l^Wia`nXT94Ah zC^=T~hr`MaZ7TL!{?w8NX##BGMVp_-_&n-QrX`DHe5!n1xAsnoA`%A%PC=>z9RVox z!{Et8TOO6$QMcOxq8ePpD7kw4?`0jVD6?`fxyHK93QRwLeg-U(q*xV?e7>@B)!@fU zT# z2&51JjSfEb@$PPS=i_20qR%CaC6F*C+_4>b++CwaCkit{s|J^J{Kgad-_0iK_qn=3 z!5o1!JyE|(7d3Qn`_I-SkkFpD8PXPf0_lrC#gataP)LhlB=VpS>pvjPo=+!&}wd_k~TD+0`in*q;JHh0BxHj#b4rTrfT=Ap|ya1pv`oag|+q1!EO@Z{_ekN zj^vY*Kd@l{f;Gscp|lbbh|}$1&Cbftk8po~zk212F;l6@Ise;cRC=Yu%bfUu>WGM7 zST#2T6P5gi9Pl>5@jWUUh?7n`r`!MKC+3pO~q~_~9laaq; zc9M6-d$4a~#myK!fslxJy1@z_<7?xn!$C*j#iF|J}he~8wYK*aai)o*FBxwd6J5mtY)GDCEHcvH@5 z`oV(gxSUQKQlRvUH$E>DX=a<49aWm^aR6b8i&M-z_f{alGR|Mc04U*{P~+kz6t5UB zKQ?-W=#=myUwanwOn94siL^+IAG@gbJW}nR<}BSygBDBpGqv%_)5J59vODf_R{$~W z>iyVcd0I^V^7PN@_3uG-b@lQ!CvKkG4(AQZivLv&onNox_)Q68hCv_@1XYQOj&2zc zK>Qp;{y#Y}Cl81d^s3bKolr-p$JXKkTEdfOvocKK1u)PTT1e_LDnu&dqSNI;m`$G_~oi~t@% z0w{BU^#t}RaUN-Yhv8!>_urTpp@VoBi-I3+OBIVLB!xxdPMIwi^XpCAU zTAr;r@ox2!eIMKwJN7V+9mFU`f#V5smH5nS3h19RF)=wOn}cNd*o?bU^y>7(A>eEs zR{V_s^~A1jh>l_i215dIuhQS8-G07I)}=$wgn@|;IU4%kv&ipL-kdcshWd74_;~%% zxz`K)6h0$5ezUBK;Vuok%e7WparrP_c4~>eXq1p7K~}k$|G2wGtxjZZf6f-}=BWxj zt%}l^VW|VB7L&apW4s_RQnwXvJ>FeM+QBSJtv!hDl+<&|^ZaSozzK~8CtHKxft;!I zqS`w=y_n$%g@KIpM@xsZGUJpuyty#2>Ir4R-64;qDjhS2fM&TAkz=QfBW9yR_0WUJ z#Kfe|fjA%_;AzdSSk~2*W8l8twY0Odvp?y3%vJA|Sl>R`Rf&;HH(ZSl@^m)GqY2^~ zuj<;?mZ}t@3t#@hSF)wS@qA3_ErFXPlFrMS?(Y5bqR*nx|McE`fAS4+CDN$<6EaD} zS}Q=tu6^D%n$60_7BN22VTJ?o8tX4T--w;{0Ckwr_``;`D(ZE4G!W+=pDY?3(^~uU zNIUw`<+qc-{(shGTM{{iJlv7ajz3fichqx^%}KddagHUU?STGmd2Ej%@e)`XSe zPE;76Uwgu}g>iqLp@uk7=ZipZ^7*Ca&ly2BxvgS&nhnu=Lu$ovm-=nQ4rL&a!O~d%gr-M{GXgCk3T@rJ zm5i>9EAYYib1}rKjE$5DZ?xFA2QgfS{_YxOT}zG2ssu%ppkZt%)2{q8uXWyc$?#x> z0|5RTJPj4b`D^3$(E|C}QitiqWe0K)5-dNiu+VuS zZBE*{&P%80Xz{+?xMkN$Wz}Zgn%q>>ulDC(dmWiK$Mo6LrStZf0B%$oOz^k3PUnr7 zTd+9A&g=GLljpJ8Ne@su7G3rsBp7CL!L23+9ez{Z zkSV$1H`e~3m*Mt}bK4EoiUm_CG+2@9i9CfygRmKcD$3x$oHBa!U86buQ|AhgX7zA^ z-{W4iV>NdAD3+gCgV^`trgz)3+Zq&Oh;&!j9#od1n# z!#r_(%a}c_B6A=}`=l4=e-`jo6jq37^ySwmk~Hjd7^1fxKRvK1b!i9aNI6h9^Mf%Z z?9H@X?0o*~1YyU1iKeRzZc$GI%|+pdIeZ1$!7`idUi^YZ17XxePD#dmk+&XPGEtuw zVC`PnVyB6GY5XCIAc}mJ*pGo_G$g^(Ixh6c4K5;ar08Yv;^1E2>N1S{Z|}vu5aWX- z*kG^ASSh`(TIW&aX#ksk&Yp(5fxwpK1PDb~iL`b5>3fDuSc&9Lntkjy<;$&_&Z}z0 zQ~~~%;MmUWZNf0!Vn3Ni$IHhzR-uKo^ZD^OJk#cNs`Y^3I7n*j_e#(D zk4w0#cN|151gga$&4+|_&_vUGqNEOSq!G;qi1sb#i>!$k`lu61Dm0qLZRdj{yFCyM z#k?1N5inDy$#R5b^x$9233dfAa~C)9r1wPpRdYo74+MmM4g}!*92N?gE^Wgz8TB#! zmiZs*oxoqS=!CepB&@1~_9PE!u>#22=#r;)lPW z!v>7IH`z?%Z#P>ZaP@Y6Rx3&x)#GPQ+i>!z+d>m2KbeWUa2KI=-s56HN#6b=mmSP@HrOqRI6d zJZkGM_ikJq1!0ly4c)d%QDv@}#jOtpp%pa$Gr&W$NR-6QCdkYpsTo}+#@PmRN(T%0 zU?EW0`pgr0I^2-l7H3OsKU{UcGRB70%^y8Rof_4*et(?gJHEg0z?C&{16o{#QC{yo zhg^{*hAT*A1YPgmeaX#kWmCEuhSKOZR@=DfIKqS|V6$kEAlZ0i5a#E{&SxORw&xnS zvEfmOMj0j%o!+tGtSu`;1r?!1j7(Ln!PrErh#5n5=3i#h4yDHo%{kVx=_%Lnd(Co! z`oY>-tN&4SAn%?L<XFR86goENM<5+2V|7f|7PaZF{2^_1yaxmVV{a*Z+(v zn_{A(KxDWqbOJPVi`xN2KlsfGnqTc4ECkbSajTjxt_+bJX1f_sB=*gunGBKxj!$1W z%|+ffW45z9I}Hb@vutBdzjVGdF{>ZCX9jl!yw-|4O4udl3gO3iriZzabrta&v*MuI zjhLX%>ZUt1LXYJE>w$!DtUi>#JJ$FgELQTHmr}Kc-uv|7D>iS zkcD*Cx`U_Ev=xv+m=#)zuOInN-*q)3GF8BaPRsIY2A6s#N#{7?NsR($WTvnDqD18x zmW$GyCtIzF1-Z&#P(bl}Bbvw>q0O8NDxd(xZvZ2-F8*c1G7gxRXXjQd}An*=-e2M#1M-s{(LS5-{(pL^7&oIKp5hQ zkW&VP8SHjD%MSzSaNPm&s=ELAi3EQaAIeLN=GAMGPKgW9sSnE>7k(%;##vgD9u#ch zX_AySoTKyN+6kmQ<{9{R!*QEOtk<~}qq7~LVRfx6i`&^C`93Rb`rq!#*WY0GT(B-w zqN!;RabIV#{RX1x)lE~s6^I>eWgWxWNkJT6Qa@-eILa3B&w@LaJ1)()wm%#j!r;f< z=c3cEbS^6iz$C^m-TX*^8Xql#4A#w<{{-^9jpd3 z7KLj`@1p?zz%A)6yY#+`X8VVi>GMWF{9xjXFROvT^c{{){55}Kx!0(FW7o{!NylA+ zPMbcjxhwFY=#Yoq5Nr^bp`2vIfNP-)cbmi~Ya=Zc|3Y}rj` zq#k$IO+4zllFEpX+}5VRJ)i-5bYH%d!#V#DGL^f?Qr2AWhEuDD?IN8q=!>hW#@}@< zlDLmSX^mt5Y|~r_-mq@&f)U4}3~ySHUA=hN4YND9a$iFH;o9|7rxMIzb~Xuqmk?zY zT9Ets5De1lK})|!*j59jDtx|IhGup0R;w+lW>GgZy1w=%oyWkFbH&icV|n*J?C$wD zSiHy1$zy_Y< zDFAZK)c?4hzrZL2jGUZQtRx8MiqWe1E(_D&T({*iO{*xxl1Dbp@fL>KjtW=EXL70= zv_)U{5xO1qlFt#@+k398JH)Bhuj)Ym0PIaxDJrgv#d_NP0& zMqS2UJ&EjT{H<ZzC2E27JrI_R8rZKHgtfP^+Bweao@k+Kd4Mbi7%aYUdntQ~;fhx#vfv#d z;}mDdqDeWExBd7?^>~@5mOQ$;8aa?RW`%uW^#-_;cH-mXV~Y~(JQ~#KP|pc2_yhjl z3z3dPWNO90<52TlIO~PVNU3eyFzbC&vv%Vd#j!SB=h7kngvKu_oP@J_Gp=)Jb{{*GHhAmhEWmg;& z{BD}g-sj6+)-g|Sc(sl4Sbnj^|0OD~${3!MF zfU6R8ZK$s97ciE&X^1=gh;R1HY8n;rMcrBc@TGpYG36GO2_Nj^a@rYkCg_#tJVD!T zZ3&dBL6k>y+mURtSt1&EmltZ?R6&Kl%-0T~vY$u?tynh~8l=TzoR0Tx1f3TW1s1$# zgmzyofyXuNE9_#qLPS%BdEipC*K zq6{Uv88En9hk32^6%f+}ojq+6bS%6B`F8GS^__npdnc~!Zh@7hCA54pc{C>UsAX!q zAscQB>wm-_?sq5ieijxM@GSOq$MNlVPzI=0%2LmHC>q@^U)4dM$n6#A^|n0e0&@JQ zp1vrBYuQ}aX&9OUuYb8XT+ec!V|t7@`I__+pnH(Wr#|^qgiIYpV?)SeP6!gT&Pntd zID|=!lZXRsa z(nd&b*So`{|89aQAfo7I$0cT`)h3HB3IJdR42A?ye>n_7gsvj@i`pq#e@^Dh6XW!= zjubR7Ve1x7b5yp3*DEaNRnIHx1=;W-_Ct zhE|mND#`NEf*^3rh?}sc8Ud=wHudR#KDUEAfqI0Ae)ng>%l3Dv_HcbV#{2KD0W>}d z3)??`{dgYL!v$X1&W-wr+f6v{uBcXdJ&n2)(nKfm`*sT>@wXm}>@TbgPhN9Kf{W{DS+*8Cqq_zPjeh|;T zgSDee^ARe1I6rs#k$%HyZd;*+L_(#UO zEqUX1HiBn-8vV*e^1rfF#z}DVysjNlvbX#d3vl7iG`RUiQ7&hJs`R^p=dHztQT$cg z+hD3Un=WwC$4++!(drBTH!H7<-VXBK{hsRtrJEJ;kAal^P4%Kyk?|H;!`dZB zRdw~MyuU0ccErE)ztG-?FfuqhPw&Ds3lgR8l+-m&zFB7fL_LE$)Hw{GHct~|67qeJJKI|F)6mP zlsJV6N&-8gzILxM_|PEqKgR=oYf&|71)G3WhjpuTBI#278M_#&i1Rl;DgY6EiNI;J zajkm;wAwm#Am7JX3Pa;>A!&QM*xlu2QZXMT25&J$4$+nQlY z1j7uf_0S{le5{0VwpW0|jxDRHcH^6*=SmEtX5zFvdtE+SU-=-&f_)f7gUmy#3VN|H z=yL8{`0{9z9eq5)f!~6=LwH9s;cbzeiC9J&Mxe`n^dE7O8m_EEsfGk$%%VMSwGQ*7 zT2*8~znTlrY6{a;839%29sLVB$!L>7Ayx8do5(H{o+j?R@AN`Dix zb{mQO@5$^Hk{kGs)M}lvg8=|S=~5jjGr|89m{NI#71aJS+Y&r!uEM^#@&8_bY3}F^ z^Yi`u$}+42l`k~a=w;S-`i6FKg4VT+^kHgLKI;dsnLDABr*GnI(OU9Jn^iNI$zj<% zS_r^ogKIa8kQO&_D}sv*2Qc_CkZl_9&0(mxpde~rz>O3EGG)AeSs5%!N>1LWwOW_Z z(g2*Jm_0#5p4K{+ds5qydgT0^6>7J@6FDMm{laHXghW;|t#&9WzskJ#&?1Ct!+d!zt@$v*K;V`+*5~RzA^SgL(sNU0ft?U6o)k-gDAW zWJ^M%S4vI|X4mo(G=0E0mvP%fZme6Zd`}jwS=rf~=I>XOEL9U{)455k;iIN!f26w5 zMl|k@9EOvcxSvzg_V&K4Ln`PI1=wBx+E@YMnqLjPHZ?VIw(VGu0-$meQ!AMkm`1|t z)OP&!qo7iI{Ws3JaLE_#`mALe7k_okVG`c1K^Xf3ua_Y3$2nN5-H`0)A&d?!Em{gn&(yzS{s{*w zU8_vDRuXY|>X6%g&c9S6?5u}g`*dVPigf%Yz98Ih7>)x$lrR0>Ib^U70nh*tPSBaQ z+u**-7gyO*>pZ?{95Z0VP25_A50Y@_`z90Q2S521?_JGdlpp$mX(+E|P}x2Z%o#y3 zQxMzy^If9Pz9f6;A0TsZU^O(MeKgECxn6wH;0LZVG1Of_h=^4S8R4K@R2$-TEv7Gd zm!NnykAq?wZ|{~%nreCHvM&B5)mZb4m4?#fa)U|2{TYJlfiiWNPj3<=v<%qwC#O@C z5>k_B{loe-K!aWq5v!h&1@nB{zR?M1H7YxxKOIiJH&GosxD7v1oGXqREyXbZz-5F)>}ApT+-h?}U-E}Z*n z5XcEjckm7s5DGINa&lpo_Z_jf-(DQxCPENxNwu(XM|D}SzJnbM-}MDrhEAUg0aJE< zvb(z*bLxOx7R$TW1Q!`jr}j%%gR&H&zW*l@AbylOjkdJ+YJdd7d_;IL!58``f&pP~ zzCK)7Wh%k(@bKu==u~gHl$$nA+qO=&+iBs-=H%ug^=@6p%K$Wyb`!t+qsNzGUr-5^ zqa+$jnV)J+DRfrOBTJ`GO38iyT`(vvU8*%j&7w~8d&)L0I<{E;J_dycJfzo!g=IU? zOM=pi5&j$3fSLm>*vgnh#MfWkCd z3oRvsdfnXLlY5Ib+V-6r=81cdSfA+QW~6J-KFRl6*mY-3h;3nhUNd9*aAvUq8@WKh z9lF3SqV(!@@ziVLF<8q~6E>`}stV-O8|*=ca~O_biuBUddC{VX%wPSrfGXF4+PP23 zi3h6;)Jh+$k3D|Uu`c+difLlk;9^>=99`MZ3%&JG6h_I^|2yB~&x9QRbGYwQ!Msks zEm9TT;f8lh_MN2w=M?B8SCXUF!3p!Micg~%(wwAuG3Jg2+g~K%nDSHBU*C`Y*dwHV zWy95sR{=zkcLMj_q7D)>bxF&)#l=J2b_RmUtPMlc;i{J$=SN1M(`ZX`bBUrX;+mVe zj(XT>s5e7D-*bwK?TTMFkDCG?FNe9<9!BQpHD7XzpNU9?eG-Fj63nb$A12j=P;oE_=us#rC=9y1+LSCsyMmO_?)NQ!qvxDSVSd{ZOggdj7B{Yf zUlK{MBx8`uK&=L7Vr8s@gNM#zg3`^_&rciL5@t2b_bTguEk8cC7+s7a|5T8eat(%A zSAJV_y^^v6H`j^54|LTCJK3qhPeU70C7*h|^s$Y}<+Si#fWC1DdEL#TKo` zhEa=)NLp3sgoxLec3{|BB-qB?W6-rdMc>zwW;W~g{z=&M8d{wB^v=qar*e+9^@_=A z{)```E@LdSZJqqhTYKg_PGZR|dZSEY{P(s_F7uo?Wg^}qe2R)<-?)1^=m$R#X{ zGAF1b&;;9ZC-br<8u$=ZrB-N|iUwP;!Am+==Hi=Jf9EhO=M2*SpHL3yFa4IAMnXC8muFSDznO zH1X7Ly9nZx0HI@n3WxaTXMeyiWa@vx#RA6%J?u~=a^>?$`%h0w^sGOkXX>+GvO=&_ zFy?-3i6>p&%2R^$=8DCHxFa7Lq*F8_TxyeeRcBJcZ|^8dH!Jd&b>~7pC5g{<2^mV? zE^p=C-?Sggdp}_rL;Ss=X^|$KaHjn~xRhY+TBuL;g)W9+d%IiK^o41uv*I6A=?#MWus zD(E#F;{gEfo6qL`_tb9o`X7!?N$cldXi5oNV%AKhT-pgjMJCmmySqED-i1?A{mH08 zL#RucFBhK0h85!P=_4K)N;Bej&_ZA`lX>c5#p&w7pKmZ=>OxnVcA_Nb73IdE^OJ>`7ypk^LUJA#T2!?@}B?m zHbIE4J9rpcjeUMkzX!6m2VZe)NJsE!hF2Gc6O=BMF$#$Z3zOI`%t7C^*~p#3dO3(C z0)b^3jOIH7!StzMw$9Bm<*4Hck*n7?|Gobxy&nlULhsK$7!)k-^oGmQcos(&0`oHo4ye_s1#II6YP9gg8h?QwN`f zDnG$A_Tr(alg3|VEyGc*qGIhhLqA`w!1F4PKv1T^yUbsP9(>T|k(UgTE$X?i86-gpon4>XB(1M0RfPtdk5c zMRW<;3wEh!r#m85spT}g_2l7<4T&)N`ueM#p`RhMP?!MZ2!JGJm?u!Jil#l{YPH$Q zo|hQX2GOlIgah2ws0Sk}rtRL0Q&5})=HP{?6$lJ(zbtoJdU@eOwq{7mK43Q-VRaz* z6(CUsD@_=a*IqKfDZSomr(QL0Piioq2-Su2MPMIKM&4F3h?95Gr+LXQNf7!LLj=XQ zEIyUL-)9tMK!*mfH>@r2vGPdc9Ie}eh-AXI;b{UpN_QY?6s5_KnXAI*!xl!b$w$GG&TI;tr=w2PfFfLxMp?+NeV^BdBU`y zbRyw+T2c;a6X&_6xJnirhwqJHqoF<6hfZ!|Eu0vyQKOz7MvqztUT1~w-?u}-O|A13 zf~%jRK&v)9yZYF@0P+Y+Di{}KYXP$2-{LO&?yN{3>zZBrnF|yRvJe(rQSjT%IElfd z%+#^W4@YC2dfjhv)k}_4sM7WV{U0x9OsLW`VDP?g$g%I;eF0a1z8K(P>neocfA7$J zUoPmcb}?~IXkMidh!KiYC`_$NTBCC4^=dHG;%d8}!)0e6ST@~fZ0dSBky3;gW^64j z-?>1YFftLl;AKi1a|zm!-zo6f@r)@!Mr3Q~+h8!^{Jh`ZRJ?O|jk5*><7>oQ_j2Pom&f*LgnLuSY{|AVy(K?zz=BQ2DJ;JXe`ecH& z=e2U)|B^E;7oW20K7jazJt7JdI)qBYoeBvMLpxb^U<`)QyAs4%2>A?*ISkUTJG?YW zJZGQJyvWI3qn|0BS+3^hlFt5h+AT-H51nvo{`Lbj@R-zWSH(nvvO%Y|Uz>NYw$y3Md&D~(pp;I;572tguia34~=a|faEquz-wVnEW4<7}Oq z5xxVrg?^8{J7{Bs@uSrE`1nJFG~io>akvv)X=Yh2EE~Fp-@KU&Y6Uq-WhXhu{raAnM=lpUB03SB{l~4cz38ph1O8MQ zldK>B`&%21&72G~>KAc-fx$O{d;M>SMOUC(QLlBTwwS?%(;<|ga=MiXcLQaIwcXjG z(pc8)ZTQ#W(n1W^;4@X3xH3eGs`6zvBa(5?R@nMrmyxl;aI2xXF_c6;ysM&49J%?GCGyV-6L@-%H@7OqqxSf}Z2WN?imDY_f6sWd) zAeu~=Zk$({yOMM6pN~YPQH_gGz0l-w2ny*070*(gh3eACylS+Va?n=%i`SX3L>xNq zU;k${g$D&UL07{6n<%_&J0KMF6hAtzTibrT+H8d)QCXlj z<%1036wX(SaOqUmI>M8>4=8NtJ|c!VFy`ecmO@})>>UkUJPXxgFSh&h39SwU(A5eA z!lR?%c=J{Ni6k4ZEQL$JN)Rwwa|}HF8QI+2L}AGj4BPEP!tGRr>OoDvkX|9MF5}l< zX|hU`979t37);(j`Fvww6?vf^k*?zY(M5`*#f$~!oqrjYR0;jiB>hNeENy-CgTI!s zXX8z!S44M1uAqh`IO5!xcYpBhkhwX4Vs@%K#ZS|x=0pw_7QsZbi#FiES6rd^cUF71 zl6VhPUPZ%-#)^sOlinNP|1>U1{FleImbSFxfjM%z4DOpY^kkwdbBSu&nAR(=ef1m2 z-rCU{7P$RS^Z4sTm&a{$Mt+9UQ~uaqJ(=J|7s1!@WQx$g^YRphUrdz72+J6$zmURS z_~b_s)&;aXEYNTI?gZx)6v$L)SwQ%p+fQTs=y#z)Y4$93xySxFd3ht>a2R%b*x6WF zHA*!kdbcVm5ouej;g0qoL^jVWe{f((NiBM@gXehdHGiVj-ehosU1_uY+D7VjFuMHf zGtCKkj-1;zEVG`Aq{Z}KRzd0c=?rt?N#%%hIfZS5(_5kZ=2n^OaNo1IxxQD@Prr$Tvs{V51 zqpF{|2cA(jRSy~r(j7JHuQwa}YsHqoD%igPjO<{a#z{^R>S&vgt z5UD$%icH z3V{T!NJfXN#BI&{$)zY1VuGk#vgq^|FS1J7Ke--F@8?);A%2Kz{jjb-&|SSn)3g1T zrHSd{sz?ycgmNp6%FYcywk+mf`M$Ijhv3;UW+~@tvBq+z!bo|NNu%o*7YLsJLW3PH zjYY-J40X=RM~f%yZg0V=qVoXc8aZjOQhwWKm1eg>EJxf@BN%aS>3#$^bEvG}P1-Z= zqSj4fBes0{Mso6cSUZ^mX_=I7@nmgh1&)dCeYY0QxN<7IIz}T3LbVwh<8BUf5C6U<`4WV1 zaU7%1M8vI|-P{-U9U$mrw^x?-z1hEa0AuIzR;f(vc(62fxy9VRL|=u;)>>;i!v-eQ zR?b>fx97@aO^+{*6<=@yw{8#{GxUGe~_+lWaqLr{|<^B6xH|bNTrB zAAdHO^r}JSR;b3_3IU}H)nfk$(8Jm3FlVMz)-(g^%m&Hpby1j2iir$Ah4B}lnvRPC z(IJ6mCROd1s?ZmPfS1!ME-tR{fPS?^F2-H(omsBS5G}KY;4~5dvQ%^5UmwNWf4^Oo z?Uvr2HEDsneVu!lxGDpiJJyf z5Rgii0@%BtCU_?^N*(gh>-;at0xsCn+4Qfr8e`vkp(N^s&7DpH6u;zC zsV)@haS||Q#bZhxX#dg-;kalgk}wPqz0czU)W}#&(=rS5g(I*`Ln+9t4u*beLwJE| zP`^ecDt`1rQNf}_gtgi2o}aUNnq;?0{|bjeP$83^6NDt99YjqU*nYsi4P%6ah2A%Q z;H7J|r}okmu>5EH&tY6C>7YoD`#Znln5sv z-aq|ZcT9Iqf1S7Ns*9J3y|q8CTY|(NjBBpMg&Qp|KL#pS!s0M-Q4Ewkx2?{EFVIKS z2+|HHDIdF$A1VHQ{P!23trXpySmAZ~t9gUvw4{>=Q-LW8SXN| zkh1fo1D{DJt=cb__7)vY$D%D3k3sMo)8$){%bl2=LMgI1cTm$^J{=@^ZhhoTO~LE5-SGk6sKSRLVE0DgVmFjd0o-f$K-Pk2t;9gPUQ4$%=i*6qKU2*hgGC?F{x%B{e2y zLLSzSXF4VZJ6*+Ndzg7jnryJ|NjPHo4TLX52)-(&@%mn@-#Q}vkfOcMI}WNeixa}jf#tnPVMf#HiSPdZvYW6Xe^0jZE-PU5)RYa)ckvU zvNIkB`FQi3jFJp3krj5>X8YHb((dl=@$B1yQA76)!^FXR57>PA#bN!Ij}Y4VL3hxQ zHie&&9=?yXmoS-?5H)9)+|7TROW9dMU4+nxd1xJDoRnHd$P#OxeL$bHKwJI0Jfyl) zlnE}dk(J&1PG)jwvrfcU*%-$iM6y~JGczre|Ew=wi%srwE4twBjnlr1hKAnvx{=~e zi9&a<@;cXt_p3c@6Y$lK{1o;n^U8vAR4)*?8GdCM%>I7r;lRVFKBUav4J(Pc`>RyCZr;90t*RUki#48F zPKH%g`<;V8ij(?JfhT(fE&>MmxpCk{yX97o_X^jTZU!iy3*L2>anlfKFO}%0C!1%m z`CtGB+MVQk><+Smg>^-fIcgdr%I&Q}9jE>n;g5SE;~gU%dHG;T_NGgkucX}>hKtj5 z=69ho{VI~neek$P4?NFE$J?&UvNWRuC1gp29`Vi_y{i9RBGMVf=9l*qKmw!(l^$_pG#yjueyBGi?<(9#GC>z;spXz&0LVqVFd12tT z&0P1ATdTR||6x#3k*+nCEdKHI`Leb1zIEj*RJiC~u;^aEtC*JI$({*RMW#}a-PTH@ zjkzGEppeIvUF~$Gxa@y4g(U7K9}DrJ7SFtm{q~~IOJm7LQfcJRdB)=690;Vx2p@xs zyBnte2-x;e)l&XcFA~XhT5n@o8wh@_v~2FOs6bhihL-)wqe^>Ara>K;4;+^M2`@b9 z+<+$X(hL7vtv5-R=Zepe5nuC1#t>V)2H4e9TsT>sb-j@b2VJ-z=_tyI`5(Imsl%yTuu$m!-Q8X1=aK8j zQ39m=s{S&dE|a0aL}$e+i;s~q=B}cNi-o1xtpA->u}ui@b)})gwzVb| z1i{oK1n-mVgdO`kSz1ER+NQhD8I)?6JLGYLZwaxQJ-X{~aNKqti2y~kNLGnlR8gzF zzOH`vOL|uCFZX&Lb9$t;(}a=y|3#Q@5n5+Z78wA%t&*}3F3J4F9(x6m{_@*~C@jF< z^4zBU_(cs9=|;LJe^xmZ8~k&2uNf+}W#`rSJQj=sViKtv2)Oz+czAIi0RIj?6bLUB zds!BJ|9mJw^RTjE{^Ca}i!5ndu^v{Jh=08KU>!Ds9l1}$r$-`K<0K)pw7PpVJaJ+` zi$bMJhfj<`9|ARjsav5Ny&zFj&H9^}GGCuQ*Gcp}2ibH48rs?o(QF|M9>F{B>Kw*dn|=38#bjivVE1Z) zvEPREkRU7-`28S*UegE&AW&=zCY2QyPWh@{ylIziE@n|>*pf45BZyps8&SJ5MC(0} zSBq_&43B>t*Dm<2b58_7$7|p5rcLud)wvWWMFSQ?wg|5#8{Vt(q+_Z?wRd(&*~+_9 zwU;8YFJw-ww}RXJjCop)Uq?+$uEOGYrz$DQKLMOK#jX_)K3__B{sRdfs3sG^cYb*; z=e}XRyqtEQX=|gvkX!;g()=B^ZukpFb9_mv>_;-i6?K5$^E$4y1Lybc)gjb901Bx8E+nyX`EUb{r3(dYAU04^( ztHn~e<9te)5eaP;gj#WG83x1y)!-e;qyeh}gy2wMx8*%~zBuXOBPCKGLOGe| z>%UVH_-Z<*Oq!f$DF)|<)r;rfLhW(toz8Sdr>@P`Jajhq5@s^xahgA;nmIX*^3 zTZGD38KP*dm}s9e1rb_tkTZqnBOVJM+mJLy=XAj}I2bvUHlv;*)QPNAr)j`38oJIu zL`SF(Sr7TFT9{|+&rEg{F9grtXLGauSdy%2p1dXmJTXcB3Tc}Oe7C<+n;ZacT#YQyY|gz2`JvQKm3zmWZ4|>NG-Y1?_l+&h3#hR4IBO~#uTQJ%w-q< zTSSu^s-dFGC4rstWM+)wSeWTCtoWjsYH^MMA!u|aJdR@kl)R?KWW!KC8L5!nrIuIBjEosw>&b7Qk8J$=+K`qrDlhu1 z)$ye|eU);pJNc^Zqg6-RWk0~PA$M|h$cVljW9H23g4g|Es6rd71N|p<`8GoW%-47~ ziFSU;NZBq^cY~L`qhE%;gUdrH10R6A#E<`R)W(Bd0K)Q1h?iMg5Ol!g)-tRL$j=#$g(tZpaBO7Tg&ra4EJ}a>R=aQ8He(U+x44oJ!2zh8btVA#NU{JEo_uX{prFQgOwfR*B-XFon$imX~pWQoMz$o>h;p|#fdA{$7VL$ApH)Xa`kPYjf zBY!U8hcQ@F6Xkprl6oc%CvW*OC0_2Q!Z_7FWB(Wevx|7)-fx{E3udDu6M&`%x_jp1 z-Dl^Y!Y9n@3ox!^KUT#@U#w8rGG>AlcaEZBBKw{_A$fMp-x5cDv3~QK8KF>@%1xn$ zA+qAf<+?WiX|4X((vll3Bf7&PVHdNh^7XK^_Wtu?rG`oQY-$#}(~)|U2ZJ0>78{&xtM~jfVH(RYGjN9jHMv1 zwip(KhoRQa>{sIzYu(XPkKhkhhi3{b*fol&WO|J|MZ~1v_;)h6!Y~a?xM8bN(jJ@* zn;G1GK6bjs9Pq%*i#2=u&NsWiR>UQ2N|8o7!kWI}g#$;dZ0mvi2Z3uZ>OvPoyU%*W z(Rw^#7XnJZqxD|)vhXOT5`s>ibs99$k{nbP`AFt);_)wcBd>0zUIjPIIxA=6Qe$ie zjfWZDnxix$sCcl_ZY@h{nI%G`_x`q5yS6N)mhjB(oHQ=Df-mmPhv<#`>oFB0(J9lP z$H#Og%3cDei4_^4Hybgq&}(N|wmYZUUGh>$*r7QoI`SR)nn4^@B>zR6 z9^c16fo~npEF06SBCS7MmzCyTX5p7DuCRQ&h6Y^Zl=)QIGMOv2ghz0&=$~S~drO>q zONG&A!y1c(bc=wpybVjblLO0B?t_lY?TVwL#L#SW|BWDW16EgEGH!zB=8zlO+9UMD(C@IUy#>XrIHBTPrhy^+9_C#GUd)@w0&}_4S|@pEXmxC z3Xcb!_Oaa_(sy5Pyc%zFM0+HdV0beIihwiDeS#r}H|lNU z^Cul&r~OR*$o?G!x1&+!5#mZB<6yv=1zn6-<|aWxj;cHMf8qD*JXNk_~E$Ro%GcjiCm0n zRn*PZ8PwG^-ba#!PMLc9L{g7T{&|FzE{&jpC{S+)SRF1nIO{;sdrsDQ`0L?HUo<7# za;HBmFeoS}0qcKa6xe`lg6(a%j@iTRQnY0+77~NjIV~@@jd}vq(@v*oWNE=)QiV=} zEy*}+8KR~ic$>bTd`B7@Urdx zD5DqqvrQ013Iu$0AILSa(By)8TUb~a{o=)oiUAwXmse^VIAjw{Q}kKE!otD>KM@1#oHRfP-aBFSDOYLqeDfs?$3I1anC9sz!Y__ z$nql1ajG+-Gjrc2<{F^LJvvg zvU7408WZvazp^)+ydB3uo~ciXhzj-Wv_nWa7@?4X`>Hw2wjm)F&cbVO7OAaBdJFlA z+4?F#uMkyly*I${IraUp1UTGgsoCwrR(>?VHj?bkn>U{8L)5{PJk@{|ccT#zjkZ`J zq=X)|H~+|_%5zv8S&AJV`F7LikQeNG3YXgdaFqO3z+bP;Y;|hT{Z zJ8oohvYhR5B6@yihNcS-;WD`ac%50|o9$0**X-^+H@@=zH?z*7_^RFvWfut!_vtXV z;7vQ<-F4l3+t)?~uH{mT*lGXe$;^tVQ%zPPFt#)@bXQy+oz*E%;| zdSskOTAoLa2=rY4V=}5TJ!yy8{A@eL%oPAL<$QPN3$>+>`U&%{*I(qdpRERJ^Nw}v z6UclnI4;A0AnF;wj?nUbH4NzM@YXAt@f0{_$i0Q{6Z`kqsZN7V^l0MrsUAHGS z2x59O`8SEI#V$2nbfN!Vgn|)?Y$V8DhM>V!s=M>VyP=ZO(j%!JoRkX5TD!pThLF93 zgO4}6_su^MO%rB>2ID)7%tcTeEieu-pH zvY`EF5U*zL(E-Z6dlBhDaBHQ_MKY41B=sT*tj4-gY;+Yz%#<$X_1W_t!V#T#OIh&r{QUljd;O* z5S;xPk^vSI^Fr;HIf@q0$k5vWRQ*|;YNq~R9G898eb%*8({p>7KyU6nIglmhwrEnO zFD3=Y{p+{Sb-^w=nP!%(w6(QiDOjb?Z?L(6?q?3NxD2+Cqc}@x3VAOiBi5|MiT5*b zR#o2rycja9M9L9KC8VkMfL$|Lsa%QeGINqdzrw(TpYAtK+KV|F&LX8!zDClu@zUdG zini?M!Z(TcMo91WJor=qE_0REcl+oYsmm(#^g4f>z3=u6kMH@R^-7Hj(XuBq zT~z+wOQ6*NIS>uVmnLbuH2*Pf#zC4?!h=#%Nu`v<-+DHGTo$_XZ~chCQoVIw)EEYX zp`USX!kMFPCRIGMUG7j#Aox2R4)=;x%6E5xkc6W1{CHaX;O-hLcQ-YyEw>_n|6GSa z8ZvS!DOkOEFM_H}UlVZ-L;C!pd{&?z2hhpxje5jn%Z#8w2>R!~sW7biwb~W<7mpz1 zR{MybapKp^RtNe<;C)p0cRu*QD;hFPzB!g< zqD zK?W5Dc+3EpG`4^=-OTI3?f$_nMK)})!C^dh(_gHW9wf&=41aJ-el3Se28KJQ488BtSw@6UM(xro9nOE zK@nTU=@ydh?knw={r&wz{r&yrlarHW&P|fEL_Zz#b-vMnD?#Fa+D=o@F{;nirqq&c z^A}uM-Ndq{aX8!y3O3oG!}Z@OuxLuQ+^av7p&;CPiZ)XEYPUhtxa_!zSzF1r?yATx zQ1ef|&85uv+Z3)zuaHcx&)()nSp;f4_fHNo2>}2JAt5lmWsson!K&s`vs)p(Wl+*w zjb-AJM-L0Wf%e>*$QQQ13vW;a^yNALjN8oYQ&LhuA=eEN*QLpH*~fg& zy`>EU8uc+}Ri@USo}u8jhhs>Q657eOKU&#~*VQwAP~ZFH#Wy!(tr|`i@|!z7)o*uR zcctF$txr%;r--bq#ZfTYvdMY&Lz#X#P27NKnSMD8$$~!P>pnmk$m&^=QiI@n(xvaO zkKAKFX-tbG!~hct9A!19wUW73K=7qK-$2>e0^f#(?03e--#nU3s}>d(R2mk@6PYE1 z?xx+$rB)lq6%B8^ zX!@-^Fsy3u-`nSE&oH27>M+lJMkc00`v$cgR1E^(lexRY-8=Tgq>uWa=rH#(Mt{r^ zz1O<-@!|V1QB>{SNzZM%8l4`HMu#`em>J=^((d!&bipCx;Hq+IuO3p?Uz}Pw{r5HT zdb4{kKvGBy{r-_tCRwqFtta!2^e1UJ^wIUQuc%?D*#=icYVt{)VTs#fV;M+7g0a%P zL(I6rUNanqe50arZCYjM%^dBYi~W#`)#pu<=ir!eZsLV%yhrMm={q|w7abDfq+h)^ z7oUiBC?l+(NJIWuez$Dxb5yx=gQh2lDi)ux71=(?;LEL770tGW>f~kIe;lZFbHY&B zhLt@-7ly`RTA7q}QBMhdI#%3il+BxJ{_gI&eiBq!aTouL0VP3(RSGBfd^^+_qC6{R zW3{qDljg?8F{cM;^h$$+wu8I7fXZe9$?;D@EORDrsZ+TkTJ%aQ(-emE(Hzx}>}BH4 zwLcJ@=mXy`FUn0iGe@Oucx9MYeT^>wl3as=i_o$E$&X92#QauPdXD%g7WXVgG@Sgqs#AuWiLTuEjO2Qz zsc`)P;g^cY(y+v1Y5kz6N5>`qDrM-qEXjWVYPY{cK~Wq3eg6_s{-KYC@6l0>f>`_Elo#4j!FmVddC6DR)HOoNF2Y&*Ce_~y4#{Ux zG?rtE<#m}ND^G`JZ_3J7ISCvM^BSY%H1+;o$#TS9r4I>R5BYXz78VwYh>O3$U@#)$ z;?edE8PEeXKEL=1eJ4x7B57M))!4%-tfSatrFYXE>5F$t&)Z*3u^Bi0X{GR?B4zP@ph+s)iZ?EBR{vaB56 z*gf+aR0K8P>gSEi4sr(uozH$o9ZPsigGtq=Q%y({^i`bYIUs2T6i%7o976NZM4HFR zWVLEAN8Y>KlGFQZkyJ%pCyhY|Ah_CA{;4)6`QP^H{A(~NFAW(@R6hAf`9V*O6?ghb zu)?6HMw)u$;-1*A*F{+#-HdvAdiXYGJ@58!Uj2B;X~Qvn=n;Kf)l&V^ND|$EE4Xy} zXTD`8yWvwKWkP-(;oNr91vM#d^A@Ks?sgakNTn>I5rD;G%YYa_?W4;Wd6sL`O(Qz+j#Bw5vJ08sSFlr=UAX zFsV#RU;wrVjRNay#8JXA>}rp_m=VY6T%T1kRi{(`jt_5Y@nR!Z-{WN{+XxE_1Jl&cyUXwvfH=Ci?FiIYKbQe z9#0){vwi zTvqf9+j$$+5mv6aH*l%pi=P3TBsCtBjurbV!#D{z)HZ~Ny8!rKF4l>OpYs`%Ku15G zXLwNm8RU7fh&4ier=Rw}*C>(M>6!bC(Kg5T#1QNeVX;5OW(@QkH1lbwoVe55#yy!a zyG6?MRmW2Wi3z)x$yGux|2g_MTkcesHCL6PW;E5EHVeu!S>K{p|8|QSi0SLedGK+H zw%C4_*vu8t%%BsGpwfB3|80d{s979gK+5s$E5_%%A_P^dI4+T7SU8IJ=zw=o@AZu$~^$g zI;Z`59Z_DKf0kR_ed+vPb_2dL482GXRl}z!913xSH8kHl79V7gIh}{PdVAB{{gJyS zY2Pldm}l`@RtP;Y3MB?c6b?VNwz2t0!6F%9-|zt8xdx(WT5@|{ysnn4YT&*M3^e}A znGk6`H2WVGV0l;e$o?rn0?QAF0$~M@KpKR3#5!gy&V9Tc)eoOd?Mgdw7Z$u$QK}U^ z7klsYT|GG>Rh7V}m%+-pYscTQD=LRm!R73v;eP(s#ka@yTTUBIzx3*E?9HB18_U~< zK^hJ(P48fHL8^ZpjSZPz&-IcN$Awj!W^p~xlP^RKvp+007OkvGO)jG`e#;^}HEb?p z5BpAA%SWVj`fYZ((PYx`a+djbCLa%%?Jj`3An0^oErHu^2YWf}Iu1uVpOm}kw_wx-Z3>O$9NQY>mE~ys1Trx|5ba4=Zm)LB@ z44N_>ApR^G%znf8S!2;eS+iC7_cw^ZqSyBlP4y?#u%~pGxRGE~{(^VxQI#$0VNnoddH~&|5k@tOxi5cDy zQsdzvS7eOJ_kTm9%vD^JZV~W+Nz9df9QXde{s+QypGYW^i=`3iJ=b>Sh85?1a@T$7 zk^h+3KS)TuD>%r$aG5UAbUoRgT{Ih-DG@?S6aa0&q=vj{41hUTu?(!g4o!Mn^~k#_ zx&olh5VJdb@l1)rBdf&;*VwdjLbpIt=W>r*A9H%O743Z3gad`Jyo;w&oX4Ti{$yoy z@%ksou#=V05M)I=*yW+QA4CdayyHk!rt;pCxzERZigboGuW7DmP86$J*h+uH|A1qt z9pO97XoYbjF8H_2S-}6f)D&8d+9}FT0fdQptZHB|m~VM`rGz9Dy<{7E;y#?UvaFziygpZG@8`J?STs*#fDd4h-f20!}*rji|?r-8rX7G<(r@pRDA{0R4xa_I!wO1! z&U-R7vbK(488PHkeUAf=7u%~xTzmD=Znlmf;)fXwF7nA-hrw)W$?R}w*)i#aS4T*q z3Imw*UvOSBS2!3P(0LTG}I!_fe} z+KJTY_#Zoi%aax(G6HflCPv2aopvA%*=~77^)KOZ={}2~Bjq1cn6>PNzki*oW(X^@ zKMpM?WTZ|B$d(i2EQD2@5awc?aLi^XJ6#$MICS%>8(B6O#vi6e8m?7ZSd)bHue|#n znCoEdyF`e@Nh|3RjpG=g8tG2N{C-RU|Iug zT_q%I@evU)Y!nqMzE*t$Q(7#y5xks);ovE5p%e>@EC-r}Nk&+VPzx4aVq0zMkD z%aHT#8p?S8L^0{s>WZY)5ToWoC7LK}xwptlLb5@lo0gk4qL_GOP@ z4g@=O)%(Wr$dXu{>;wx!ZbL?QMJXo}8*-Yd#+b5;->kTT+ypUEwtv|s+V95K+67MR z`UTA*Bfs&G_gN49R1xZzc(L(ArTgFscA2Jo_L)+fnM%u2T8`G6IAeW*bU`5@aDvLG zO)R!)iYg@~MM=r~cuO}`om-WsX`_H`Vj=Y6)g3V&aby1BPo$hsFKX{i0g<$M;Cj-V zTDK*!({oJXbefUH=e9Np-$`Y9yf@#m$+T!b$hpZh2E$NcSXE$CHw0&sgL}1URPQ)X zv&}qPGz8GZDcfgk0PGk6|GE$H!f%?o*W|kVbQzEz8z9mFmig*Vi3|gDVbLk)cH1iu*BzEqlHX$vZye+jRtm~#%pnCn4 z_@LK@IR2SYRiX^{v^Evd{drNBnj|@BJPl+nl~tD}Tu>z!8!l-7qZ(Y!4~O#7#i?XC z>3*0R-zNBw`=1a@M9!HC|98OA}&9-IYfKDAmgOn3^omu`7f}A04cA#kI>? z`pg>4xBKky-HWRsBLM(5yfW|y4TW(6?WA~9h;hD7?<4W$FwHEnKht@)BGbO+z>e$f z*@{W`mV{FD=xDqe->Nu$if^Yr?@yexijuLh@mekxzeX2ag~r->)sbY8_jbG`Z5w`_ zmKf(Trv6gpq}`ab`|+X9SR^}4Ry9Dn%J88AmAtZI@~Ia2$KLt+lUB(bxLCr^fIP-H zU-9PM!MB%lE;5n6jB^0!%6R1`+4o{(ksmjoM-70e07MFa;&+VAA*X(ZylFsk#ge8g z-YgL(q(JO5%)9&$6scUx?VMjj$bb#?nEmwmXDLCMk2QghFMS zl&SkbQKF6-z?(ku-aP%+Yp!wJ_j`Wq5ff?>drkQdUv*NcB~Rqa1nN{7QX@QjS|`vU z>#3G4MiolD(6JOm$JLK)czOkR#;f*dbYn( zdk|c_RAI__@wYT@5Pf%pZZN1Y{4@3Tm7OllpVh#Ff!*CoLHaVTLgTIgQCu(^2?aCW zx8&QQDOT&;Jo0ttxTL%ftV@&hwTCy2%*jxj*p^*WuiUW0mZwyMy!qF%l9GOvVew71 zdNEj+#%cRUQnqWybWTxD+yLTbtxy6zbL~ti=?S&F=rkaB!^?z@jJQFY?w&~4 zkNxRky@j)mHf>IvdhthKYz$05bi%o=7*h6SYD{%6xU7}Ji#b~5&6@1+iA=OMkvOGAd=7SxMBsuzPB8X8PZ@bi&!3pT*o)Z16ZsLoB%M!y3KCEW;M>|s)3 zP=sNP#n|md-dzUX3fC7+3H9|oohABdq)f!XfERHmjCC6G*K?`;SlTOCac@X@p?z;? zUU86NaqqOY4lApzshL0PtYGcJ&(B}``SXM3+S)K+EIz``rf%pz(BSWs7fouv*B}2d ztfp{JNXQ&=;JHj+rm3W}Q{1X%bZW zJ`AU+bBknY7WUGBzZ+KMFYP^3d;B4nWaLwP*=U^x@#L1((*}11s(GQ;LFgCUnd=aB z`Z#5%uf{ZuHG{KNgEB?xh+V&Dc86h^{={GJs7uayi?=BvF!TJN~;=bkNh>n+E z=oLeL9$^r=W%@b->3F*9o;4}5O-%3UaPXuuy>2n&Cp1we%DG7$qQ2~jU}@cN-`Q$j za_?()TY7(e?kOfEbrtJ)o<411Kq239ap5l}Ec~ghO#*LLoGH`8mp}x7GjwRr>Ai+D zH$`gH1vbwkEGhbOm5yRM9|b9u5jKhMCU zoUD7v4!fMxr^H||wj(~Vv;A9JKkj!A^v5z+n3m}e`Iv8eODZYZk6y}(hQwmNC3A5U zvvY|$Ono-=p7?Bk_eMp93mTtFQj%Y?7-UpsSlrH53-qfq=IVMrEF<-z8Tj_$th4A; zwgHW@zCe2M2Djtmu9+Pl&dsg1g!uR`cF&3ex~e4rpUjFAd%)r9SvNh~)jZEZr*Rrz6qEzpH>uU8-K#Z!M^tNwDqx*^^O9nBZ(SGbHgiPVlmT{9a z?X0sXZ~DlLd&}n-M`!0)-ZZi%)0eRYG0$JTD4TU&_}4Ybo|ur}ltcW01OL}f!?_Wj zj;^j6xzhfRm6Qq#3x#5z=~oySlj#B${+tHqIX7ieM>ZmVJ{BGA$t)!M2b5Lq)-}l|+lYpGXy=DcCZK>gn`ukq-2?>)! zL!|z>rFJej_~fK+gL^Gig+m9$Qo;Dau)@F?paO;-}ZIvp$cc>5LKCAV+izV)Ve zA9FXuuv+@OwATxHH;kEM&rPeoc7@8($={_c#`oOK&of9JbmHdmd5*X&_x8_(RO0BM zhStqYmk25_O`P%{5d1weqV3->U(U%6DvqzF6QZG|B{`&#M**M@gZ<`C2XS(83P48< zP|I-w?L#kV$XEe4SSdIrFc;fVy+e3-B8u_}9{kp0H$pdl7R(kl&VUnlonI3k3_`Z` zSohsW`>e>5=F44SbNq5wv}^6hGJ8FlVO7TbmoBL4leht!*Afzp0Md`_hvM$b46du4 z3zl60O_scA9}}3w?_)5S@DK|5@3+_8Jz+3bQjX&T0_?J%ISQMN-DhE9!ijsV?i%sZ z=E3%1$dj9&+p`s)a7d|FKpHXvfF!_hcFa zAvV81ILh#FuF~WQkph+La_giYGXmY}Svbysi=?`2AnElpN1^NO;>H=K#{&V<=(8@| zxE$IpzRhCJY#e6>5Wp@6-CvvA@$uW4tE!gebeXI4^xd7e&SNA9>)8~op%!2LP&nTm z+;NVFH(LN{<_8m)7y*VAv*$xYG4Qv46OAtFT;!UWd%xJz;f-3)LonhYpJWbu36I;x zgW<(au~G+}PmIhwiSfOJ!#L{`V!wauYMp-`dGsD1iwJZbLn5qWe0JvQ_Lf=-Cmq+e zCJP&(2ny$xCHx1(hBU5#G~FVAhbBuEqTaq$5a;OYJEwE+oplLB+>X$ooy_fpN6XU0 z17NfJin;>T8h$z2^R0|8Hn}7~fNGDuC0QKZ@l2`I5}w69G0~@wfNT%?7U7ABOYLC< z>UuS(zt_0&7d+e--P%jdZYfTg$p=@UgHBwn^DM6_Pd-++%8($;xheA~i1XRDx8#hg zSd(2}E191pOQlJJO2h8Vo^Qelv@hz>mbWDSdp{-}b&|P2ch|VOfD`A@@$sy9kmBjG z{(yOlJBx~n%KO7Lg(p9rC{vqvn+?l2!68{>dOS)P&uzUAZ$;1LM;8m1Jl>~a)&>KYQl zDqnIWH~r5HyT~X8$!{Q`V17*@?}Dnfd-CMT_4wWO_yr#Qwpsu1?(FUseG&JndkD`L z!{MV(*0Fh^-M}<;ux(vi0i=u+DbaItx-_soTQRVNzV+3uGK@U9qJWO-gxS<>;U`yG zZDzjstWhZiDiDAf*#^TTh)XI^f$+}`E=s)f=Yx;EZMT;@EXX#(Uh8;?ctO-zFrq~A zQd<|O7_45L9Qd0%y%k`F^3i0wyfv-bvdT}!B<^;9lL%(H^^*Br1gYMu1LaKj# zz39W;75dfc1=4SuU;jCA@Ev;mW_kQ5G32-~s@{l~w%cjC#Ed~QB%bHy4*S&P2v;r$ z*zN!PWYov})FJfL;rjQBJlmnld%JV*JnRN+k_>ohea}|>a4t1nUkTR^Le4rauyS|) zAr~9zxTGZN{dR#@=_A=NcZ!7DKvc&ncL+{ARIl?O_~1#)ifqqpxKp zC3*3q-@aJ)#YQ!=(3=xv)FY;Bm+H2`zifN}Y$p!+M9OcU1~3Zx$WBFX@gX0ViRx4V zP{A>vcDllyb{i}*$7W})N(+5Wa_JDyxb8bZYZQamVf5{Pw-^LNeVNROG>x zR+|~!Q>tLJcwXElQh{UqdahRfOAozBk7RuT&=p7@=($DR znS$V)5mYu_UXbglS2xK&3@h$WpsL=z*zd$?dE&;zcd#IU1wY-z{5*r7dAhcS#-UB! zJmn#wym`g!d>s^lB)HO6h2qbX-H4Kw8n5;{5?2Cts>lIXd+1xYX7?4zQ+#sn|CF%q z;mdhc9}^Q2j?{~PRm}3@l{Dkx?#qrLY>bS5;sy8H1@3lwZe1++>6Gh?X(GYlciT0$ zyy!-!Cu}S%=E=ibXYp{j7cJX(iw7DhcYBlwLrT=>^D+Z^j;G#Uu=p+ij2n37XP!<^ z7MUO1R?SEA_)pJyT+h(|gl{{=kk7c$+aZEO^z|5ez2_fa&#$+|$@|)gdhd~u!LI-z z*c`uq_+yOgskg5-P6BdJ&Y~1yp}SBvOuD%*Bjv}i#DoM-gy*>w`a-MDZiG%)ZY-m$ z?mbfCuK$JXPopZ+0T#p`ywtC)n~kv+cXZ^5)9A9mo-M;DEjxT)-Yxaqyt}RFxvVfF z{rRf<+y?)HUPuW)=4i|JKZ&pLLZHyQE3`*$*X4G_!err_E0`+(*@4;Bog)-|v+uWX zg0k${+C&E)bY0``opX23LGS^7KncYUo_4R!qaHEMbEljA!fSqUkg|S8=yl@lR^mJ~ zQD(4wb~{;L<*p3NDCB2pSTQ?~%#pglUyAoXREOM_nm1PCr2m_u{xa&xobY{jRP7oS zvWGgK)|PYY*4EUlYw5Y!+Z{^c+=WZ+4-s6M`>hQ0;BpSWo!$IFjAtiE0pPbZ^<)w1A=%P6Eg@y(OrQUyIK`2xiGF^t{xR>{g zjg4(|^X+_e%Eg*kbS@l3f(bHcMO-*JIh)f97@>Eam%l2bk%b;fcyPg3mN+4RMZ$|H z63o$35;t~%2U<1XVi((dt8-lb|Kl#b=}zu&qLZA|#s4D!005{dYbjNOtv>!AzD_4K literal 59403 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 From eb102479f4ee1548803fa6e03f46fb08e2529053 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 14 Sep 2024 02:47:19 +0900 Subject: [PATCH 36/62] Use beatmap icon for `.osr` and `.osk` for now --- osu.Desktop/Windows/WindowsAssociationManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Desktop/Windows/WindowsAssociationManager.cs b/osu.Desktop/Windows/WindowsAssociationManager.cs index 92cffd0987..c8066cabda 100644 --- a/osu.Desktop/Windows/WindowsAssociationManager.cs +++ b/osu.Desktop/Windows/WindowsAssociationManager.cs @@ -42,8 +42,8 @@ namespace osu.Desktop.Windows { new FileAssociation(@".osz", WindowsAssociationManagerStrings.OsuBeatmap, Icons.Beatmap), new FileAssociation(@".olz", WindowsAssociationManagerStrings.OsuBeatmap, Icons.Beatmap), - new FileAssociation(@".osr", WindowsAssociationManagerStrings.OsuReplay, Icons.Lazer), - new FileAssociation(@".osk", WindowsAssociationManagerStrings.OsuSkin, Icons.Lazer), + new FileAssociation(@".osr", WindowsAssociationManagerStrings.OsuReplay, Icons.Beatmap), + new FileAssociation(@".osk", WindowsAssociationManagerStrings.OsuSkin, Icons.Beatmap), }; private static readonly UriAssociation[] uri_associations = From 385eb5eed5a4842e0eeb4e4b4dc450d77ec7c407 Mon Sep 17 00:00:00 2001 From: kongehund <63306696+kongehund@users.noreply.github.com> Date: Sat, 14 Sep 2024 16:32:51 +0200 Subject: [PATCH 37/62] Rewrite GetConvexHull --- osu.Game/Utils/GeometryUtils.cs | 76 +++++++++++++++++++++++---------- 1 file changed, 53 insertions(+), 23 deletions(-) diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs index d4c1dc2db7..4c90421aca 100644 --- a/osu.Game/Utils/GeometryUtils.cs +++ b/osu.Game/Utils/GeometryUtils.cs @@ -152,42 +152,72 @@ namespace osu.Game.Utils /// The points to calculate a convex hull. public static List GetConvexHull(IEnumerable points) { - List p = points.ToList(); + // Naming convention implies positive y upwards. - if (p.Count < 3) - return p; + bool isCCW(Vector2 a, Vector2 b, Vector2 c) => crossProduct(b - a, c - a) > 0; - p.Sort((a, b) => a.X == b.X ? a.Y.CompareTo(b.Y) : a.X.CompareTo(b.X)); + float crossProduct(Vector2 v1, Vector2 v2) => v1.X * v2.Y - v1.Y * v2.X; - List upper = new List(); - List lower = new List(); + var pointsList = points.ToList(); - // Build the lower hull - for (int i = 0; i < p.Count; i++) + pointsList.Sort(delegate (Vector2 point1, Vector2 point2) { - while (lower.Count >= 2 && cross(lower[^2], lower[^1], p[i]) <= 0) - lower.RemoveAt(lower.Count - 1); + if (point1.X == point2.X) + return point1.Y.CompareTo(point2.Y); + return point1.X.CompareTo(point2.X); + }); - lower.Add(p[i]); + if (pointsList.Count < 3) + return pointsList; + + var convexHullUpper = new List + { + pointsList[0], + pointsList[1] + }; + var convexHullLower = new List + { + pointsList[pointsList.Count - 1], + pointsList[pointsList.Count - 2] + }; + + for (int i_points = 2; i_points < pointsList.Count; i_points++) + { + Vector2 c = pointsList[i_points]; + for (int i_hull = convexHullUpper.Count - 1; i_hull > 0; i_hull--) + { + Vector2 a = convexHullUpper[^2]; + Vector2 b = convexHullUpper[^1]; + if (isCCW(a, b, c)) + convexHullUpper.Remove(b); + else + break; + } + convexHullUpper.Add(c); } - // Build the upper hull - for (int i = p.Count - 1; i >= 0; i--) + for (int i_points = pointsList.Count - 3; i_points >= 0; i_points--) { - while (upper.Count >= 2 && cross(upper[^2], upper[^1], p[i]) <= 0) - upper.RemoveAt(upper.Count - 1); - - upper.Add(p[i]); + Vector2 c = pointsList[i_points]; + for (int i_hull = convexHullLower.Count - 1; i_hull > 0; i_hull--) + { + Vector2 a = convexHullLower[^2]; + Vector2 b = convexHullLower[^1]; + if (isCCW(a, b, c)) + convexHullLower.Remove(b); + else + break; + } + convexHullLower.Add(c); } - // Remove the last point of each half because it's a duplicate of the first point of the other half - lower.RemoveAt(lower.Count - 1); - upper.RemoveAt(upper.Count - 1); + convexHullUpper.RemoveAt(convexHullUpper.Count - 1); + convexHullLower.RemoveAt(convexHullLower.Count - 1); - lower.AddRange(upper); - return lower; + convexHullUpper.AddRange(convexHullLower); + var convexHull = convexHullUpper; - float cross(Vector2 o, Vector2 a, Vector2 b) => (a.X - o.X) * (b.Y - o.Y) - (a.Y - o.Y) * (b.X - o.X); + return convexHull; } public static List GetConvexHull(IEnumerable hitObjects) => From 30096c1c7198168c667f85873a60461410df296a Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 14 Sep 2024 17:26:04 +0200 Subject: [PATCH 38/62] clean up code --- osu.Game/Utils/GeometryUtils.cs | 74 ++++++++++++--------------------- 1 file changed, 27 insertions(+), 47 deletions(-) diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs index 4c90421aca..8572ac6609 100644 --- a/osu.Game/Utils/GeometryUtils.cs +++ b/osu.Game/Utils/GeometryUtils.cs @@ -152,72 +152,52 @@ namespace osu.Game.Utils /// The points to calculate a convex hull. public static List GetConvexHull(IEnumerable points) { - // Naming convention implies positive y upwards. - - bool isCCW(Vector2 a, Vector2 b, Vector2 c) => crossProduct(b - a, c - a) > 0; - - float crossProduct(Vector2 v1, Vector2 v2) => v1.X * v2.Y - v1.Y * v2.X; - - var pointsList = points.ToList(); - - pointsList.Sort(delegate (Vector2 point1, Vector2 point2) - { - if (point1.X == point2.X) - return point1.Y.CompareTo(point2.Y); - return point1.X.CompareTo(point2.X); - }); + var pointsList = points.OrderBy(p => p.X).ThenBy(p => p.Y).ToList(); if (pointsList.Count < 3) return pointsList; - var convexHullUpper = new List + var convexHullLower = new List { pointsList[0], pointsList[1] }; - var convexHullLower = new List + var convexHullUpper = new List { - pointsList[pointsList.Count - 1], - pointsList[pointsList.Count - 2] + pointsList[^1], + pointsList[^2] }; - for (int i_points = 2; i_points < pointsList.Count; i_points++) + // Build the lower hull. + for (int i = 2; i < pointsList.Count; i++) { - Vector2 c = pointsList[i_points]; - for (int i_hull = convexHullUpper.Count - 1; i_hull > 0; i_hull--) - { - Vector2 a = convexHullUpper[^2]; - Vector2 b = convexHullUpper[^1]; - if (isCCW(a, b, c)) - convexHullUpper.Remove(b); - else - break; - } - convexHullUpper.Add(c); - } + Vector2 c = pointsList[i]; + while (convexHullLower.Count > 1 && isClockwise(convexHullLower[^2], convexHullLower[^1], c)) + convexHullLower.RemoveAt(convexHullLower.Count - 1); - for (int i_points = pointsList.Count - 3; i_points >= 0; i_points--) - { - Vector2 c = pointsList[i_points]; - for (int i_hull = convexHullLower.Count - 1; i_hull > 0; i_hull--) - { - Vector2 a = convexHullLower[^2]; - Vector2 b = convexHullLower[^1]; - if (isCCW(a, b, c)) - convexHullLower.Remove(b); - else - break; - } convexHullLower.Add(c); } - convexHullUpper.RemoveAt(convexHullUpper.Count - 1); + // Build the upper hull. + for (int i = pointsList.Count - 3; i >= 0; i--) + { + Vector2 c = pointsList[i]; + while (convexHullUpper.Count > 1 && isClockwise(convexHullUpper[^2], convexHullUpper[^1], c)) + convexHullUpper.RemoveAt(convexHullUpper.Count - 1); + + convexHullUpper.Add(c); + } + convexHullLower.RemoveAt(convexHullLower.Count - 1); + convexHullUpper.RemoveAt(convexHullUpper.Count - 1); - convexHullUpper.AddRange(convexHullLower); - var convexHull = convexHullUpper; + convexHullLower.AddRange(convexHullUpper); - return convexHull; + return convexHullLower; + + float crossProduct(Vector2 v1, Vector2 v2) => v1.X * v2.Y - v1.Y * v2.X; + + bool isClockwise(Vector2 a, Vector2 b, Vector2 c) => crossProduct(b - a, c - a) >= 0; } public static List GetConvexHull(IEnumerable hitObjects) => From d34e8ea69e78f80940b2cade87b9c0dba6f066b0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 Sep 2024 16:15:09 +0900 Subject: [PATCH 39/62] Update framework --- 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 d5bdfd91b5..c7ce707562 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + diff --git a/osu.iOS.props b/osu.iOS.props index da1cec395f..bb20125282 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -17,6 +17,6 @@ -all - + From 785a7255074bb374ccf92b3d6ec68d5c7a3b6117 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Sep 2024 15:12:02 +0900 Subject: [PATCH 40/62] Fix osu!catch fruit rotation being applied too late --- .../Objects/Drawables/DrawableFruit.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs index 52c53523e6..7bac6b588e 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Graphics; using osu.Game.Rulesets.Catch.Skinning.Default; using osu.Game.Skinning; @@ -28,11 +27,10 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables _ => new FruitPiece()); } - protected override void UpdateInitialTransforms() + protected override void OnApply() { - base.UpdateInitialTransforms(); - - ScalingContainer.RotateTo((RandomSingle(1) - 0.5f) * 40); + base.OnApply(); + ScalingContainer.Rotation = (RandomSingle(1) - 0.5f) * 40; } } } From a99dbfa768e56bcad9e6e8ebff295d42f482a4f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 17 Sep 2024 08:21:58 +0200 Subject: [PATCH 41/62] Add failing test step demonstrating incorrect end drag marker position --- .../Editor/TestSceneSliderSelectionBlueprint.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs index c2589f11ef..fa8db51e09 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs @@ -218,6 +218,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddAssert("tail positioned correctly", () => Precision.AlmostEquals(blueprint.TailOverlay.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.TailCircle.ScreenSpaceDrawQuad.Centre)); + + AddAssert("end drag marker positioned correctly", + () => Precision.AlmostEquals(blueprint.TailOverlay.EndDragMarker!.ToScreenSpace(blueprint.TailOverlay.EndDragMarker.OriginPosition), drawableObject.TailCircle.ScreenSpaceDrawQuad.Centre, 2)); } private void moveMouseToControlPoint(int index) From 3e63fe399f75f31d35803564e40559f59d4a213a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 17 Sep 2024 08:22:37 +0200 Subject: [PATCH 42/62] Enable NRT in test scene --- .../Editor/TestSceneSliderSelectionBlueprint.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs index fa8db51e09..f0f969b15b 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using NUnit.Framework; using osu.Framework.Utils; using osu.Game.Beatmaps; @@ -22,9 +20,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { public partial class TestSceneSliderSelectionBlueprint : SelectionBlueprintTestScene { - private Slider slider; - private DrawableSlider drawableObject; - private TestSliderBlueprint blueprint; + private Slider slider = null!; + private DrawableSlider drawableObject = null!; + private TestSliderBlueprint blueprint = null!; [SetUp] public void Setup() => Schedule(() => @@ -233,14 +231,14 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor } private void checkControlPointSelected(int index, bool selected) - => AddAssert($"control point {index} {(selected ? "selected" : "not selected")}", () => blueprint.ControlPointVisualiser.Pieces[index].IsSelected.Value == selected); + => AddAssert($"control point {index} {(selected ? "selected" : "not selected")}", () => blueprint.ControlPointVisualiser!.Pieces[index].IsSelected.Value == selected); private partial class TestSliderBlueprint : SliderSelectionBlueprint { public new SliderBodyPiece BodyPiece => base.BodyPiece; public new TestSliderCircleOverlay HeadOverlay => (TestSliderCircleOverlay)base.HeadOverlay; public new TestSliderCircleOverlay TailOverlay => (TestSliderCircleOverlay)base.TailOverlay; - public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser; + public new PathControlPointVisualiser? ControlPointVisualiser => base.ControlPointVisualiser; public TestSliderBlueprint(Slider slider) : base(slider) From 67a7f608f155aa7c1e5abcd4f17c31ef23028f9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 17 Sep 2024 08:23:46 +0200 Subject: [PATCH 43/62] Fix slider end drag marker being in incorrect position for stacked sliders Closes https://github.com/ppy/osu/issues/29884. --- .../Edit/Blueprints/Sliders/SliderCircleOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs index 247ceb4078..9c2998466a 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs @@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders if (endDragMarkerContainer != null) { - endDragMarkerContainer.Position = circle.Position; + endDragMarkerContainer.Position = circle.Position + slider.StackOffset; endDragMarkerContainer.Scale = CirclePiece.Scale * 1.2f; var diff = slider.Path.PositionAt(1) - slider.Path.PositionAt(0.99f); endDragMarkerContainer.Rotation = float.RadiansToDegrees(MathF.Atan2(diff.Y, diff.X)); From 2ccdad41e793d9bb982eabeedf6f5be4a08367b3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Sep 2024 15:27:16 +0900 Subject: [PATCH 44/62] Also fix banana showers --- .../Objects/Drawables/DrawableBanana.cs | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs index 26e304cf3f..9a4bc45bda 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs @@ -1,10 +1,12 @@ // 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 osu.Framework.Allocation; -using osu.Framework.Graphics; +using osu.Framework.Utils; using osu.Game.Rulesets.Catch.Skinning.Default; using osu.Game.Skinning; +using osuTK; namespace osu.Game.Rulesets.Catch.Objects.Drawables { @@ -36,23 +38,37 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables StartTimeBindable.BindValueChanged(_ => UpdateComboColour()); } - protected override void UpdateInitialTransforms() + private float startScale; + private float endScale; + + private float startAngle; + private float endAngle; + + protected override void OnApply() { - base.UpdateInitialTransforms(); + base.OnApply(); const float end_scale = 0.6f; const float random_scale_range = 1.6f; - ScalingContainer.ScaleTo(HitObject.Scale * (end_scale + random_scale_range * RandomSingle(3))) - .Then().ScaleTo(HitObject.Scale * end_scale, HitObject.TimePreempt); + startScale = end_scale + random_scale_range * RandomSingle(3); + endScale = end_scale; - ScalingContainer.RotateTo(getRandomAngle(1)) - .Then() - .RotateTo(getRandomAngle(2), HitObject.TimePreempt); + startAngle = getRandomAngle(1); + endAngle = getRandomAngle(2); float getRandomAngle(int series) => 180 * (RandomSingle(series) * 2 - 1); } + protected override void Update() + { + base.Update(); + + double preemptProgress = Math.Min(1, (Time.Current - (HitObject.StartTime - InitialLifetimeOffset)) / HitObject.TimePreempt); + ScalingContainer.Scale = new Vector2(HitObject.Scale * (float)Interpolation.Lerp(startScale, endScale, preemptProgress)); + ScalingContainer.Rotation = (float)Interpolation.Lerp(startAngle, endAngle, preemptProgress); + } + public override void PlaySamples() { base.PlaySamples(); From 3f4422429da16292cfc8a8b48797be1197507393 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Sep 2024 15:37:54 +0900 Subject: [PATCH 45/62] *Also* fix droplets --- .../Objects/Drawables/DrawableDroplet.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs index 8f32cdcc31..c92fd7cbba 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Graphics; +using osu.Framework.Utils; using osu.Game.Rulesets.Catch.Skinning.Default; using osu.Game.Skinning; @@ -28,15 +28,22 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables _ => new DropletPiece()); } - protected override void UpdateInitialTransforms() + private float startRotation; + + protected override void OnApply() { - base.UpdateInitialTransforms(); + base.OnApply(); // roughly matches osu-stable - float startRotation = RandomSingle(1) * 20; - double duration = HitObject.TimePreempt + 2000; + startRotation = RandomSingle(1) * 20; + } - ScalingContainer.RotateTo(startRotation).RotateTo(startRotation + 720, duration); + protected override void Update() + { + base.Update(); + + double preemptProgress = (Time.Current - (HitObject.StartTime - InitialLifetimeOffset)) / (HitObject.TimePreempt + 2000); + ScalingContainer.Rotation = (float)Interpolation.Lerp(startRotation, startRotation + 720, preemptProgress); } } } From c1c0d49bfeecba12db92f48d8f3c586ab820c3e8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Sep 2024 16:15:45 +0900 Subject: [PATCH 46/62] Add comments and fix bananas stopping still if not caught --- .../Objects/Drawables/DrawableBanana.cs | 7 ++++++- .../Objects/Drawables/DrawableDroplet.cs | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs index 9a4bc45bda..f6ecdce616 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs @@ -64,7 +64,12 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables { base.Update(); - double preemptProgress = Math.Min(1, (Time.Current - (HitObject.StartTime - InitialLifetimeOffset)) / HitObject.TimePreempt); + double preemptProgress = (Time.Current - (HitObject.StartTime - InitialLifetimeOffset)) / HitObject.TimePreempt; + + // Clamp scale and rotation at the point of bananas being caught, else let them freely extrapolate. + if (Result.IsHit) + preemptProgress = Math.Min(1, preemptProgress); + ScalingContainer.Scale = new Vector2(HitObject.Scale * (float)Interpolation.Lerp(startScale, endScale, preemptProgress)); ScalingContainer.Rotation = (float)Interpolation.Lerp(startAngle, endAngle, preemptProgress); } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs index c92fd7cbba..73442a502b 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs @@ -42,6 +42,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables { base.Update(); + // No clamping for droplets. They should be considered indefinitely spinning regardless of time. + // They also never end up on the plate, so they shouldn't stop spinning when caught. double preemptProgress = (Time.Current - (HitObject.StartTime - InitialLifetimeOffset)) / (HitObject.TimePreempt + 2000); ScalingContainer.Rotation = (float)Interpolation.Lerp(startRotation, startRotation + 720, preemptProgress); } From f8fff4074ddecafbd79076662a11df71b7cc7610 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Sep 2024 16:18:29 +0900 Subject: [PATCH 47/62] Fix rotation not being updated correctly on start time change --- osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs | 5 +++-- .../Objects/Drawables/DrawableDroplet.cs | 6 +++--- osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs | 6 ++++-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs index f6ecdce616..10e483b577 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs @@ -44,10 +44,11 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables private float startAngle; private float endAngle; - protected override void OnApply() + protected override void UpdateInitialTransforms() { - base.OnApply(); + base.UpdateInitialTransforms(); + // Important to have this in UpdateInitialTransforms() to it is re-triggered by RefreshStateTransforms(). const float end_scale = 0.6f; const float random_scale_range = 1.6f; diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs index 73442a502b..fadd630116 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs @@ -30,11 +30,11 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables private float startRotation; - protected override void OnApply() + protected override void UpdateInitialTransforms() { - base.OnApply(); + base.UpdateInitialTransforms(); - // roughly matches osu-stable + // Important to have this in UpdateInitialTransforms() to it is re-triggered by RefreshStateTransforms(). startRotation = RandomSingle(1) * 20; } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs index 7bac6b588e..877fae9d67 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs @@ -27,9 +27,11 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables _ => new FruitPiece()); } - protected override void OnApply() + protected override void UpdateInitialTransforms() { - base.OnApply(); + base.UpdateInitialTransforms(); + + // Important to have this in UpdateInitialTransforms() to it is re-triggered by RefreshStateTransforms(). ScalingContainer.Rotation = (RandomSingle(1) - 0.5f) * 40; } } From 1b17231da47daba7743d868e7ba8f6bb281a3b72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 17 Sep 2024 11:35:16 +0200 Subject: [PATCH 48/62] Implement "form" slider bar control --- .../UserInterface/TestSceneFormControls.cs | 14 + .../Graphics/UserInterface/OsuSliderBar.cs | 6 +- .../Graphics/UserInterfaceV2/FormSliderBar.cs | 330 ++++++++++++++++++ .../Graphics/UserInterfaceV2/FormTextBox.cs | 2 +- 4 files changed, 348 insertions(+), 4 deletions(-) create mode 100644 osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs index eb8a8b3fe9..6dd7275abf 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs @@ -1,12 +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 osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Localisation; +using osu.Game.Resources.Localisation.Web; using osuTK; namespace osu.Game.Tests.Visual.UserInterface @@ -68,6 +70,18 @@ namespace osu.Game.Tests.Visual.UserInterface HintText = EditorSetupStrings.LetterboxDuringBreaksDescription, Current = { Disabled = true }, }, + new FormSliderBar + { + Caption = BeatmapsetsStrings.ShowStatsDrain, + HintText = EditorSetupStrings.DrainRateDescription, + Current = new BindableFloat + { + MinValue = 0, + MaxValue = 10, + Value = 5, + Precision = 0.1f, + } + }, }, }, } diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index 9cb6356cab..334fe343ae 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -46,7 +46,7 @@ namespace osu.Game.Graphics.UserInterface protected override void LoadComplete() { base.LoadComplete(); - CurrentNumber.BindValueChanged(current => TooltipText = getTooltipText(current.NewValue), true); + CurrentNumber.BindValueChanged(current => TooltipText = GetDisplayableValue(current.NewValue), true); } protected override void OnUserChange(T value) @@ -55,7 +55,7 @@ namespace osu.Game.Graphics.UserInterface playSample(value); - TooltipText = getTooltipText(value); + TooltipText = GetDisplayableValue(value); } private void playSample(T value) @@ -83,7 +83,7 @@ namespace osu.Game.Graphics.UserInterface channel.Play(); } - private LocalisableString getTooltipText(T value) + public LocalisableString GetDisplayableValue(T value) { if (CurrentNumber.IsInteger) return int.CreateTruncating(value).ToString("N0"); diff --git a/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs b/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs new file mode 100644 index 0000000000..91ce9da2d2 --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs @@ -0,0 +1,330 @@ +// 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.Globalization; +using System.Numerics; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input; +using osu.Framework.Input.Events; +using osu.Framework.Localisation; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + public partial class FormSliderBar : CompositeDrawable, IHasCurrentValue + where T : struct, INumber, IMinMaxValue + { + public Bindable Current + { + get => current.Current; + set => current.Current = value; + } + + private bool instantaneous; + + /// + /// Whether changes to the slider should instantaneously transfer to the text box (and vice versa). + /// If , the transfer will happen on text box commit (explicit, or implicit via focus loss), or on slider drag end. + /// + public bool Instantaneous + { + get => instantaneous; + set + { + instantaneous = value; + slider.TransferValueOnCommit = !instantaneous; + } + } + + private readonly BindableNumberWithCurrent current = new BindableNumberWithCurrent(); + + public LocalisableString Caption { get; init; } + public LocalisableString HintText { get; init; } + + private Box background = null!; + private Box flashLayer = null!; + private FormTextBox.InnerTextBox textBox = null!; + private Slider slider = null!; + private FormFieldCaption caption = null!; + private IFocusManager focusManager = null!; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + RelativeSizeAxes = Axes.X; + Height = 50; + + Masking = true; + CornerRadius = 5; + + InternalChildren = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background5, + }, + flashLayer = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Colour4.Transparent, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(9), + Children = new Drawable[] + { + caption = new FormFieldCaption + { + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + Caption = Caption, + TooltipText = HintText, + }, + textBox = new FormNumberBox.InnerNumberBox + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + Width = 0.5f, + CommitOnFocusLost = true, + SelectAllOnFocus = true, + AllowDecimals = true, + OnInputError = () => + { + flashLayer.Colour = ColourInfo.GradientVertical(colours.Red3.Opacity(0), colours.Red3); + flashLayer.FadeOutFromOne(200, Easing.OutQuint); + } + }, + slider = new Slider + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.X, + Width = 0.5f, + Current = Current, + } + }, + }, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + focusManager = GetContainingFocusManager()!; + + textBox.Focused.BindValueChanged(_ => updateState()); + textBox.OnCommit += textCommitted; + textBox.Current.BindValueChanged(textChanged); + + current.BindValueChanged(_ => + { + updateState(); + updateTextBoxFromSlider(); + }, true); + } + + private bool updatingFromTextBox; + + private void textChanged(ValueChangedEvent change) + { + if (!instantaneous) return; + + tryUpdateSliderFromTextBox(); + } + + private void textCommitted(TextBox t, bool isNew) + { + tryUpdateSliderFromTextBox(); + + // If the attempted update above failed, restore text box to match the slider. + Current.TriggerChange(); + + flashLayer.Colour = ColourInfo.GradientVertical(colourProvider.Dark2.Opacity(0), colourProvider.Dark2); + flashLayer.FadeOutFromOne(800, Easing.OutQuint); + } + + private void tryUpdateSliderFromTextBox() + { + updatingFromTextBox = true; + + try + { + switch (Current) + { + case Bindable bindableInt: + bindableInt.Value = int.Parse(textBox.Current.Value); + break; + + case Bindable bindableDouble: + bindableDouble.Value = double.Parse(textBox.Current.Value); + break; + + default: + Current.Parse(textBox.Current.Value, CultureInfo.CurrentCulture); + break; + } + } + catch + { + // ignore parsing failures. + // sane state will eventually be restored by a commit (either explicit, or implicit via focus loss). + } + + updatingFromTextBox = false; + } + + protected override bool OnHover(HoverEvent e) + { + updateState(); + return true; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + updateState(); + } + + protected override bool OnClick(ClickEvent e) + { + focusManager.ChangeFocus(textBox); + return true; + } + + private void updateState() + { + textBox.Alpha = 1; + + background.Colour = Current.Disabled ? colourProvider.Background4 : colourProvider.Background5; + caption.Colour = Current.Disabled ? colourProvider.Foreground1 : colourProvider.Content2; + textBox.Colour = Current.Disabled ? colourProvider.Foreground1 : colourProvider.Content1; + + BorderThickness = IsHovered || textBox.Focused.Value ? 2 : 0; + BorderColour = textBox.Focused.Value ? colourProvider.Highlight1 : colourProvider.Light4; + + if (textBox.Focused.Value) + background.Colour = ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark3); + else if (IsHovered) + background.Colour = ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark4); + else + background.Colour = colourProvider.Background5; + } + + private void updateTextBoxFromSlider() + { + if (updatingFromTextBox) return; + + textBox.Text = slider.GetDisplayableValue(Current.Value).ToString(); + } + + private partial class Slider : OsuSliderBar + { + private Box leftBox = null!; + private Box rightBox = null!; + private Circle nub = null!; + private const float nub_width = 10; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + [BackgroundDependencyLoader] + private void load() + { + Height = 40; + RelativeSizeAxes = Axes.X; + RangePadding = nub_width / 2; + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 5, + Children = new Drawable[] + { + leftBox = new Box + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + rightBox = new Box + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + }, + }, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Horizontal = RangePadding, }, + Child = nub = new Circle + { + Width = nub_width, + RelativeSizeAxes = Axes.Y, + RelativePositionAxes = Axes.X, + Origin = Anchor.TopCentre, + } + }, + new HoverClickSounds() + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + updateState(); + } + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + leftBox.Width = Math.Clamp(RangePadding + nub.DrawPosition.X, 0, Math.Max(0, DrawWidth)) / DrawWidth; + rightBox.Width = Math.Clamp(DrawWidth - nub.DrawPosition.X - RangePadding, 0, Math.Max(0, DrawWidth)) / DrawWidth; + } + + protected override bool OnHover(HoverEvent e) + { + updateState(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + updateState(); + base.OnHoverLost(e); + } + + private void updateState() + { + rightBox.Colour = colourProvider.Background6; + leftBox.Colour = IsHovered ? colourProvider.Highlight1.Opacity(0.5f) : colourProvider.Dark2; + nub.Colour = IsHovered ? colourProvider.Highlight1 : colourProvider.Light4; + } + + protected override void UpdateValue(float value) + { + nub.MoveToX(value, 250, Easing.OutQuint); + } + } + } +} diff --git a/osu.Game/Graphics/UserInterfaceV2/FormTextBox.cs b/osu.Game/Graphics/UserInterfaceV2/FormTextBox.cs index 044576c635..741bff6db6 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FormTextBox.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FormTextBox.cs @@ -122,7 +122,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 if (!current.Disabled && !ReadOnly) { - flashLayer.Colour = ColourInfo.GradientVertical(colourProvider.Dark1.Opacity(0), colourProvider.Dark2); + flashLayer.Colour = ColourInfo.GradientVertical(colourProvider.Dark2.Opacity(0), colourProvider.Dark2); flashLayer.FadeOutFromOne(800, Easing.OutQuint); } }; From 76c5e743d7ce834701c07fc15df3a19339a3db32 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Sep 2024 13:49:27 +0900 Subject: [PATCH 49/62] Remove opacity from old style dropdown menus These aren't used in many places, but we've since moved away from opacity in UI elements like this, so let's just nuke it here for legibility. Addresses https://github.com/ppy/osu/discussions/29906. --- osu.Game/Graphics/UserInterface/OsuDropdown.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuDropdown.cs b/osu.Game/Graphics/UserInterface/OsuDropdown.cs index 71ae149cf6..dc42216c55 100644 --- a/osu.Game/Graphics/UserInterface/OsuDropdown.cs +++ b/osu.Game/Graphics/UserInterface/OsuDropdown.cs @@ -75,7 +75,7 @@ namespace osu.Game.Graphics.UserInterface [BackgroundDependencyLoader(true)] private void load(OverlayColourProvider? colourProvider, OsuColour colours, AudioManager audio) { - BackgroundColour = colourProvider?.Background5 ?? Color4.Black.Opacity(0.5f); + BackgroundColour = colourProvider?.Background5 ?? Color4.Black; HoverColour = colourProvider?.Light4 ?? colours.PinkDarker; SelectionColour = colourProvider?.Background3 ?? colours.PinkDarker.Opacity(0.5f); @@ -397,7 +397,7 @@ namespace osu.Game.Graphics.UserInterface { bool hovered = Enabled.Value && IsHovered; var hoveredColour = colourProvider?.Light4 ?? colours.PinkDarker; - var unhoveredColour = colourProvider?.Background5 ?? Color4.Black.Opacity(0.5f); + var unhoveredColour = colourProvider?.Background5 ?? Color4.Black; Colour = Color4.White; Alpha = Enabled.Value ? 1 : 0.3f; From aae98e6906450bbd4518e4ec33af8049a0508df4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Sep 2024 16:11:47 +0900 Subject: [PATCH 50/62] Add failing test showing crash at song select on selection edge case --- .../Navigation/TestSceneScreenNavigation.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index f02c2fd4f0..6cd89dcd0c 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -144,6 +144,22 @@ namespace osu.Game.Tests.Visual.Navigation exitViaEscapeAndConfirm(); } + [Test] + public void TestEnterGameplayWhileFilteringToNoSelection() + { + TestPlaySongSelect songSelect = null; + + PushAndConfirm(() => songSelect = new TestPlaySongSelect()); + AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); + AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); + + AddStep("force selection", () => + { + songSelect.FinaliseSelection(); + songSelect.FilterControl.CurrentTextSearch.Value = "test"; + }); + } + [Test] public void TestSongSelectBackActionHandling() { From c192a6a1d54b69936c6e741e49c7b0524c0a2c8e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Sep 2024 15:50:55 +0900 Subject: [PATCH 51/62] Fix song select crashes due to attempting to clear selection after load has already begun --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 525884c413..57978b7bbd 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -1036,7 +1036,7 @@ namespace osu.Game.Screens.Select itemsCache.Validate(); // update and let external consumers know about selection loss. - if (BeatmapSetsLoaded) + if (BeatmapSetsLoaded && AllowSelection) { bool selectionLost = selectedBeatmapSet != null && selectedBeatmapSet.State.Value != CarouselItemState.Selected; From 743d50924105a557a0d08424e7c274ee1996d580 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Sep 2024 16:40:27 +0900 Subject: [PATCH 52/62] Also ensure filter is applied when returning to song select --- .../Navigation/TestSceneScreenNavigation.cs | 6 +++++ osu.Game/Screens/Select/SongSelect.cs | 23 ++++++++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 6cd89dcd0c..eda7ce925a 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -150,6 +150,7 @@ namespace osu.Game.Tests.Visual.Navigation TestPlaySongSelect songSelect = null; PushAndConfirm(() => songSelect = new TestPlaySongSelect()); + AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); @@ -158,6 +159,11 @@ namespace osu.Game.Tests.Visual.Navigation songSelect.FinaliseSelection(); songSelect.FilterControl.CurrentTextSearch.Value = "test"; }); + + AddUntilStep("wait for player", () => !songSelect.IsCurrentScreen()); + AddStep("return to song select", () => songSelect.MakeCurrent()); + + AddUntilStep("wait for selection lost", () => songSelect.Beatmap.IsDefault); } [Test] diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 6da72ee660..18608d61e9 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -127,6 +127,8 @@ namespace osu.Game.Screens.Select private Sample sampleChangeDifficulty = null!; private Sample sampleChangeBeatmap = null!; + private bool pendingFilterApplication; + private Container carouselContainer = null!; protected BeatmapDetailArea BeatmapDetails { get; private set; } = null!; @@ -328,7 +330,20 @@ namespace osu.Game.Screens.Select GetRecommendedBeatmap = s => recommender?.GetRecommendedBeatmap(s), }, c => carouselContainer.Child = c); - FilterControl.FilterChanged = Carousel.Filter; + FilterControl.FilterChanged = criteria => + { + // If a filter operation is applied when we're in a state that doesn't allow selection, + // we might end up in an unexpected state. This is because currently carousel panels are in charge + // of updating the global selection (which is very hard to deal with). + // + // For now let's just avoid filtering when selection isn't allowed locally. + // This should be nuked from existence when we get around to fixing the complexity of song select <-> beatmap carousel. + // The debounce part of BeatmapCarousel's filtering should probably also be removed and handled locally. + if (Carousel.AllowSelection) + Carousel.Filter(criteria); + else + pendingFilterApplication = true; + }; if (ShowSongSelectFooter) { @@ -701,6 +716,12 @@ namespace osu.Game.Screens.Select Carousel.AllowSelection = true; + if (pendingFilterApplication) + { + Carousel.Filter(FilterControl.CreateCriteria()); + pendingFilterApplication = false; + } + BeatmapDetails.Refresh(); beginLooping(); From ac507a3ba568e40396a642d13032dbc1e8d6c314 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Sep 2024 17:21:43 +0900 Subject: [PATCH 53/62] Remove unused parameter in `applyActiveCriteria` --- osu.Game/Screens/Select/BeatmapCarousel.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 57978b7bbd..d9359cfec3 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -706,7 +706,7 @@ namespace osu.Game.Screens.Select private bool beatmapsSplitOut; - private void applyActiveCriteria(bool debounce, bool alwaysResetScrollPosition = true) + private void applyActiveCriteria(bool debounce) { PendingFilter?.Cancel(); PendingFilter = null; @@ -735,8 +735,7 @@ namespace osu.Game.Screens.Select root.Filter(activeCriteria); itemsCache.Invalidate(); - if (alwaysResetScrollPosition || !Scroll.UserScrolling) - ScrollToSelected(true); + ScrollToSelected(true); FilterApplied?.Invoke(); } From 95e26e6fd8a548d1d0443e52e838ca68d9bc7319 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 18 Sep 2024 11:23:00 +0200 Subject: [PATCH 54/62] Make slider bar instantaneous by default (and fix broken implementation) --- .../UserInterface/TestSceneFormControls.cs | 16 +++++++++++++--- .../Graphics/UserInterfaceV2/FormSliderBar.cs | 8 ++++++-- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs index 6dd7275abf..369fe1a40c 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics.Cursor; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Localisation; -using osu.Game.Resources.Localisation.Web; using osuTK; namespace osu.Game.Tests.Visual.UserInterface @@ -72,8 +71,7 @@ namespace osu.Game.Tests.Visual.UserInterface }, new FormSliderBar { - Caption = BeatmapsetsStrings.ShowStatsDrain, - HintText = EditorSetupStrings.DrainRateDescription, + Caption = "Instantaneous slider", Current = new BindableFloat { MinValue = 0, @@ -82,6 +80,18 @@ namespace osu.Game.Tests.Visual.UserInterface Precision = 0.1f, } }, + new FormSliderBar + { + Caption = "Non-instantaneous slider", + Current = new BindableFloat + { + MinValue = 0, + MaxValue = 10, + Value = 5, + Precision = 0.1f, + }, + Instantaneous = false, + }, }, }, } diff --git a/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs b/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs index 91ce9da2d2..e4c814e71d 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs @@ -7,6 +7,7 @@ using System.Numerics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; @@ -29,7 +30,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 set => current.Current = value; } - private bool instantaneous; + private bool instantaneous = true; /// /// Whether changes to the slider should instantaneously transfer to the text box (and vice versa). @@ -41,7 +42,9 @@ namespace osu.Game.Graphics.UserInterfaceV2 set { instantaneous = value; - slider.TransferValueOnCommit = !instantaneous; + + if (slider.IsNotNull()) + slider.TransferValueOnCommit = !instantaneous; } } @@ -116,6 +119,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 RelativeSizeAxes = Axes.X, Width = 0.5f, Current = Current, + TransferValueOnCommit = !instantaneous, } }, }, From 0bab755be316d46ef82700f7cd9e0f916202db46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 18 Sep 2024 11:25:23 +0200 Subject: [PATCH 55/62] Add missing xmldoc --- osu.Game/Graphics/UserInterfaceV2/FormCheckBox.cs | 7 +++++++ osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs | 7 +++++++ osu.Game/Graphics/UserInterfaceV2/FormTextBox.cs | 11 +++++++++++ 3 files changed, 25 insertions(+) diff --git a/osu.Game/Graphics/UserInterfaceV2/FormCheckBox.cs b/osu.Game/Graphics/UserInterfaceV2/FormCheckBox.cs index 797ff09800..d4cd86010f 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FormCheckBox.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FormCheckBox.cs @@ -29,7 +29,14 @@ namespace osu.Game.Graphics.UserInterfaceV2 private readonly BindableWithCurrent current = new BindableWithCurrent(); + /// + /// Caption describing this slider bar, displayed on top of the controls. + /// public LocalisableString Caption { get; init; } + + /// + /// Hint text containing an extended description of this slider bar, displayed in a tooltip when hovering the caption. + /// public LocalisableString HintText { get; init; } private Box background = null!; diff --git a/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs b/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs index e4c814e71d..1d44c5d810 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs @@ -50,7 +50,14 @@ namespace osu.Game.Graphics.UserInterfaceV2 private readonly BindableNumberWithCurrent current = new BindableNumberWithCurrent(); + /// + /// Caption describing this slider bar, displayed on top of the controls. + /// public LocalisableString Caption { get; init; } + + /// + /// Hint text containing an extended description of this slider bar, displayed in a tooltip when hovering the caption. + /// public LocalisableString HintText { get; init; } private Box background = null!; diff --git a/osu.Game/Graphics/UserInterfaceV2/FormTextBox.cs b/osu.Game/Graphics/UserInterfaceV2/FormTextBox.cs index 741bff6db6..9bbb5cba99 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FormTextBox.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FormTextBox.cs @@ -59,8 +59,19 @@ namespace osu.Game.Graphics.UserInterfaceV2 private readonly BindableWithCurrent current = new BindableWithCurrent(); + /// + /// Caption describing this slider bar, displayed on top of the controls. + /// public LocalisableString Caption { get; init; } + + /// + /// Hint text containing an extended description of this slider bar, displayed in a tooltip when hovering the caption. + /// public LocalisableString HintText { get; init; } + + /// + /// Text displayed in the text box when its contents are empty. + /// public LocalisableString PlaceholderText { get; init; } private Box background = null!; From 093d9ab076129cf732e21849f5ee49a0185a451d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 18 Sep 2024 11:30:52 +0200 Subject: [PATCH 56/62] Keep slider bar looking active when dragging outside of its bounds --- .../Graphics/UserInterfaceV2/FormSliderBar.cs | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs b/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs index 1d44c5d810..fa6d44d4c5 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs @@ -143,6 +143,8 @@ namespace osu.Game.Graphics.UserInterfaceV2 textBox.OnCommit += textCommitted; textBox.Current.BindValueChanged(textChanged); + slider.IsDragging.BindValueChanged(_ => updateState()); + current.BindValueChanged(_ => { updateState(); @@ -226,12 +228,12 @@ namespace osu.Game.Graphics.UserInterfaceV2 caption.Colour = Current.Disabled ? colourProvider.Foreground1 : colourProvider.Content2; textBox.Colour = Current.Disabled ? colourProvider.Foreground1 : colourProvider.Content1; - BorderThickness = IsHovered || textBox.Focused.Value ? 2 : 0; + BorderThickness = IsHovered || textBox.Focused.Value || slider.IsDragging.Value ? 2 : 0; BorderColour = textBox.Focused.Value ? colourProvider.Highlight1 : colourProvider.Light4; if (textBox.Focused.Value) background.Colour = ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark3); - else if (IsHovered) + else if (IsHovered || slider.IsDragging.Value) background.Colour = ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark4); else background.Colour = colourProvider.Background5; @@ -246,6 +248,8 @@ namespace osu.Game.Graphics.UserInterfaceV2 private partial class Slider : OsuSliderBar { + public BindableBool IsDragging { get; set; } = new BindableBool(); + private Box leftBox = null!; private Box rightBox = null!; private Circle nub = null!; @@ -313,6 +317,21 @@ namespace osu.Game.Graphics.UserInterfaceV2 rightBox.Width = Math.Clamp(DrawWidth - nub.DrawPosition.X - RangePadding, 0, Math.Max(0, DrawWidth)) / DrawWidth; } + protected override bool OnDragStart(DragStartEvent e) + { + bool dragging = base.OnDragStart(e); + IsDragging.Value = dragging; + updateState(); + return dragging; + } + + protected override void OnDragEnd(DragEndEvent e) + { + base.OnDragEnd(e); + IsDragging.Value = false; + updateState(); + } + protected override bool OnHover(HoverEvent e) { updateState(); @@ -328,8 +347,8 @@ namespace osu.Game.Graphics.UserInterfaceV2 private void updateState() { rightBox.Colour = colourProvider.Background6; - leftBox.Colour = IsHovered ? colourProvider.Highlight1.Opacity(0.5f) : colourProvider.Dark2; - nub.Colour = IsHovered ? colourProvider.Highlight1 : colourProvider.Light4; + leftBox.Colour = IsHovered || IsDragged ? colourProvider.Highlight1.Opacity(0.5f) : colourProvider.Dark2; + nub.Colour = IsHovered || IsDragged ? colourProvider.Highlight1 : colourProvider.Light4; } protected override void UpdateValue(float value) From d506d8a1500f73a3b93473f155541073957b7a59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 18 Sep 2024 11:32:55 +0200 Subject: [PATCH 57/62] Implement `TabbableContentContainer` for slider control --- .../UserInterface/TestSceneFormControls.cs | 4 +++- .../Graphics/UserInterfaceV2/FormSliderBar.cs | 16 +++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs index 369fe1a40c..b456da0f26 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs @@ -78,7 +78,8 @@ namespace osu.Game.Tests.Visual.UserInterface MaxValue = 10, Value = 5, Precision = 0.1f, - } + }, + TabbableContentContainer = this, }, new FormSliderBar { @@ -91,6 +92,7 @@ namespace osu.Game.Tests.Visual.UserInterface Precision = 0.1f, }, Instantaneous = false, + TabbableContentContainer = this, }, }, }, diff --git a/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs b/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs index fa6d44d4c5..84becb72c9 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs @@ -48,6 +48,19 @@ namespace osu.Game.Graphics.UserInterfaceV2 } } + private CompositeDrawable? tabbableContentContainer; + + public CompositeDrawable? TabbableContentContainer + { + set + { + tabbableContentContainer = value; + + if (textBox.IsNotNull()) + textBox.TabbableContentContainer = tabbableContentContainer; + } + } + private readonly BindableNumberWithCurrent current = new BindableNumberWithCurrent(); /// @@ -117,7 +130,8 @@ namespace osu.Game.Graphics.UserInterfaceV2 { flashLayer.Colour = ColourInfo.GradientVertical(colours.Red3.Opacity(0), colours.Red3); flashLayer.FadeOutFromOne(200, Easing.OutQuint); - } + }, + TabbableContentContainer = tabbableContentContainer, }, slider = new Slider { From d0519238a3a74f507da725035ce9cba5ad757cb0 Mon Sep 17 00:00:00 2001 From: Neku Date: Wed, 18 Sep 2024 22:57:37 +0200 Subject: [PATCH 58/62] Implement beat-synchronized animation in skip overlay --- osu.Game/Screens/Play/SkipOverlay.cs | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Play/SkipOverlay.cs b/osu.Game/Screens/Play/SkipOverlay.cs index 29b2e5229b..c88724c8db 100644 --- a/osu.Game/Screens/Play/SkipOverlay.cs +++ b/osu.Game/Screens/Play/SkipOverlay.cs @@ -9,6 +9,7 @@ using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; +using osu.Framework.Audio.Track; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -16,6 +17,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Utils; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -26,7 +28,7 @@ using osuTK.Graphics; namespace osu.Game.Screens.Play { - public partial class SkipOverlay : CompositeDrawable, IKeyBindingHandler + public partial class SkipOverlay : BeatSyncedContainer, IKeyBindingHandler { /// /// The total number of successful skips performed by this overlay. @@ -36,10 +38,9 @@ namespace osu.Game.Screens.Play private readonly double startTime; public Action RequestSkip; - private Button button; private ButtonContainer buttonContainer; - private Box remainingTimeBox; + private Circle remainingTimeBox; private FadeContainer fadeContainer; private double displayTime; @@ -51,7 +52,6 @@ namespace osu.Game.Screens.Play private IGameplayClock gameplayClock { get; set; } internal bool IsButtonVisible => fadeContainer.State == Visibility.Visible && buttonContainer.State.Value == Visibility.Visible; - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; /// @@ -87,13 +87,13 @@ namespace osu.Game.Screens.Play Anchor = Anchor.Centre, Origin = Anchor.Centre, }, - remainingTimeBox = new Box + remainingTimeBox = new Circle { Height = 5, - RelativeSizeAxes = Axes.X, - Colour = colours.Yellow, Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, + Colour = colours.Yellow, + RelativeSizeAxes = Axes.X } } } @@ -210,6 +210,18 @@ namespace osu.Game.Screens.Play { } + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) + { + base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); + + if (fadeOutBeginTime <= gameplayClock.CurrentTime) + return; + + float progress = (float)(gameplayClock.CurrentTime - displayTime) / (float)(fadeOutBeginTime - displayTime); + float newWidth = Math.Max(0, 1 - Math.Clamp(progress, 0, 1)); + remainingTimeBox.ResizeWidthTo(newWidth, timingPoint.BeatLength * 2, Easing.OutQuint); + } + public partial class FadeContainer : Container, IStateful { [CanBeNull] From fdd94aa8452ac5d70bd139d22f700d6e456947f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 19 Sep 2024 09:41:54 +0200 Subject: [PATCH 59/62] Remove pointless max The clamp should already ensure this. --- osu.Game/Screens/Play/SkipOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/SkipOverlay.cs b/osu.Game/Screens/Play/SkipOverlay.cs index c88724c8db..362677ca5c 100644 --- a/osu.Game/Screens/Play/SkipOverlay.cs +++ b/osu.Game/Screens/Play/SkipOverlay.cs @@ -218,7 +218,7 @@ namespace osu.Game.Screens.Play return; float progress = (float)(gameplayClock.CurrentTime - displayTime) / (float)(fadeOutBeginTime - displayTime); - float newWidth = Math.Max(0, 1 - Math.Clamp(progress, 0, 1)); + float newWidth = 1 - Math.Clamp(progress, 0, 1); remainingTimeBox.ResizeWidthTo(newWidth, timingPoint.BeatLength * 2, Easing.OutQuint); } From fd45644d0f58f6eb90916aa820628b26918b60e2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Sep 2024 16:54:00 +0900 Subject: [PATCH 60/62] Fix skin layout editor `PlayerAvatar` applying corner radius weirdly after scale Closes #29919. I've also made this handle resizing better, so now you can have non-square avatar displays. --- osu.Game/Screens/Play/HUD/PlayerAvatar.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/PlayerAvatar.cs b/osu.Game/Screens/Play/HUD/PlayerAvatar.cs index 06d0f7bc9a..8e4406c2c1 100644 --- a/osu.Game/Screens/Play/HUD/PlayerAvatar.cs +++ b/osu.Game/Screens/Play/HUD/PlayerAvatar.cs @@ -39,14 +39,23 @@ namespace osu.Game.Screens.Play.HUD private IBindable? apiUser; + private readonly Container cornerContainer; + public PlayerAvatar() { Size = new Vector2(default_size); - InternalChild = avatar = new UpdateableAvatar(isInteractive: false) + InternalChild = cornerContainer = new Container { + Masking = true, RelativeSizeAxes = Axes.Both, - Masking = true + Child = avatar = new UpdateableAvatar(isInteractive: false) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fill, + } }; } @@ -66,7 +75,7 @@ namespace osu.Game.Screens.Play.HUD { base.LoadComplete(); - CornerRadius.BindValueChanged(e => avatar.CornerRadius = e.NewValue * default_size, true); + CornerRadius.BindValueChanged(e => cornerContainer.CornerRadius = e.NewValue * default_size, true); } public bool UsesFixedAnchor { get; set; } From ca8402d98021b6a83d5df5ec119c661bca38152c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Sep 2024 19:06:46 +0900 Subject: [PATCH 61/62] Make animation slightly more snappy --- osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs b/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs index 84becb72c9..ac3730598f 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs @@ -367,7 +367,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 protected override void UpdateValue(float value) { - nub.MoveToX(value, 250, Easing.OutQuint); + nub.MoveToX(value, 200, Easing.OutPow10); } } } From 89509ea49ed608c2c587816c35833f1822490eb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 19 Sep 2024 14:03:49 +0200 Subject: [PATCH 62/62] Fix `DrawableOsuHitObject` not properly cleaning up dim application callbacks Should fix https://github.com/ppy/osu/issues/28629. First of all, to support the claim that this does fix the issue - reproduction is rather difficult, but I believe I found a way to maximise the chances of it reproducing by performing the following steps: 1. Apply the following diff: diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index eacd2b3e75..4c00da031a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -95,6 +96,8 @@ public DrawableSlider([CanBeNull] Slider s = null) [BackgroundDependencyLoader] private void load() { + Thread.Sleep(100); + tailContainer = new Container { RelativeSizeAxes = Axes.Both }; AddRangeInternal(new Drawable[] 2. Download https://osu.ppy.sh/beatmapsets/1470790#osu/3023028 and open it in the editor. 3. Select all objects using Ctrl-A. Yes, it'll take a while, especially so with the patch above. 4. Rotate the selection by any amount using the right toolbox. 5. Press undo. The game should crash. If it doesn't spam redo and undo until it does. Now to explain what the fix is. In the issue thread I spent a considerable time hemming and hawing about which of the dimmable pieces was null, which was a complete miss and a failure at reading. Let's see the stack trace again: 2024-06-27 02:15:20 [error]: at osu.Game.Rulesets.Osu.Objects.Drawables.DrawableOsuHitObject.g__applyDim|15_0(Drawable piece) in /home/runner/work/osu-auth-client/osu-auth-client/osu/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs:line 101 Line 101, you say? What could be null here? https://github.com/ppy/osu/blob/bd8addfb5f71568479d2c037d1b6e811de6e7fe6/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs#L101 Okay, what's `InitialLifetimeOffset`, then? https://github.com/ppy/osu/blob/bd8addfb5f71568479d2c037d1b6e811de6e7fe6/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs#L108 Yes, that's right. It's `HitObject` that is null here. Now why does *that* happen? First, let's note that all stacks where this died went through `UpdateState()`, which means that the problematic `applyDim()` calls had to be `ApplyCustomUpdateState` event callbacks. Meaning that the pieces where `HitObject` was null were DHOs themselves. Recall that parent DHOs and child DHOs are pooled separately. Therefore, there is no guarantee that any parent and child DHOs will remain associated with each other for the entire duration of a gameplay session; it is quite the contrary, and nobody should rely on that. Unfortunately for us, adding a `applyDimToDrawableHitObject` callback to a child object's `ApplyCustomUpdateState` *implicitly creates* such an association, because it ends up allocating a closure that captures `this` (meaning the parent in this context). Therefore, this now creates a situation where a child DHO can attempt to read state from a former parent DHO which can be in an indeterminate state, and in fact, when this crashes, the former parent DHO is most likely not even in use - hence the null `HitObject`. Thus, the fix is to clear the association by unsubscribing from the event when nested objects are cleared. My hypothesis why the reproduction scenario is like it is, is that both the sleep and the increased pressure on the pool (by way of selecting all objects and therefore forcing the DHOs to be materialised beyond pool capacity) increases the likelihood of getting a crosslink. When pool pressure is low, it is much more likely that a parent DHO *will* get the same child DHO again on re-application, even though that is not guaranteed. Just as an additional detail, note that the sentry issue for this lists the "first seen" version as 2024.312.0, which is the release that included https://github.com/ppy/osu/pull/27401 which would be directly responsible for this mess. --- .../Objects/Drawables/DrawableOsuHitObject.cs | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index 5f5deca1ba..b3a68ec92d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -91,20 +91,35 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables drawableObjectPiece.ApplyCustomUpdateState -= applyDimToDrawableHitObject; drawableObjectPiece.ApplyCustomUpdateState += applyDimToDrawableHitObject; } - else - applyDim(piece); - } - void applyDim(Drawable piece) - { - piece.FadeColour(new Color4(195, 195, 195, 255)); - using (piece.BeginDelayedSequence(InitialLifetimeOffset - OsuHitWindows.MISS_WINDOW)) - piece.FadeColour(Color4.White, 100); + // but at the end apply the transforms now regardless of whether this is a DHO or not. + // the above is just to ensure they don't get overwritten later. + applyDim(piece); } - - void applyDimToDrawableHitObject(DrawableHitObject dho, ArmedState _) => applyDim(dho); } + protected override void ClearNestedHitObjects() + { + base.ClearNestedHitObjects(); + + // any dimmable pieces that are DHOs will be pooled separately. + // `applyDimToDrawableHitObject` is a closure that implicitly captures `this`, + // and because of separate pooling of parent and child objects, there is no guarantee that the pieces will be associated with `this` again on re-use. + // therefore, clean up the subscription here to avoid crosstalk. + // not doing so can result in the callback attempting to read things from `this` when it is in a completely bogus state (not in use or similar). + foreach (var piece in DimmablePieces.OfType()) + piece.ApplyCustomUpdateState -= applyDimToDrawableHitObject; + } + + private void applyDim(Drawable piece) + { + piece.FadeColour(new Color4(195, 195, 195, 255)); + using (piece.BeginDelayedSequence(InitialLifetimeOffset - OsuHitWindows.MISS_WINDOW)) + piece.FadeColour(Color4.White, 100); + } + + private void applyDimToDrawableHitObject(DrawableHitObject dho, ArmedState _) => applyDim(dho); + protected sealed override double InitialLifetimeOffset => HitObject.TimePreempt; private OsuInputManager osuActionInputManager;