Merge branch 'master' into catch-hide-combo-workaround

This commit is contained in:
Salman Ahmed
2021-05-18 17:47:20 +03:00
62 changed files with 874 additions and 487 deletions

View File

@ -28,10 +28,12 @@ namespace osu.Game.Rulesets.Catch.Mods
catchPlayfield.CatcherArea.MovableCatcher.CatchFruitOnPlate = false; catchPlayfield.CatcherArea.MovableCatcher.CatchFruitOnPlate = false;
} }
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state)
{
}
protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state)
{ {
base.ApplyNormalVisibilityState(hitObject, state);
if (!(hitObject is DrawableCatchHitObject catchDrawable)) if (!(hitObject is DrawableCatchHitObject catchDrawable))
return; return;
@ -54,7 +56,7 @@ namespace osu.Game.Rulesets.Catch.Mods
var offset = hitObject.TimePreempt * fade_out_offset_multiplier; var offset = hitObject.TimePreempt * fade_out_offset_multiplier;
var duration = offset - hitObject.TimePreempt * fade_out_duration_multiplier; var duration = offset - hitObject.TimePreempt * fade_out_duration_multiplier;
using (drawable.BeginAbsoluteSequence(hitObject.StartTime - offset, true)) using (drawable.BeginAbsoluteSequence(hitObject.StartTime - offset))
drawable.FadeOut(duration); drawable.FadeOut(duration);
} }
} }

View File

@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
} }
}; };
AddBlueprint(new HoldNoteSelectionBlueprint(drawableObject)); AddBlueprint(new HoldNoteSelectionBlueprint(holdNote), drawableObject);
} }
protected override void Update() protected override void Update()

View File

@ -184,8 +184,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
AddAssert("head note positioned correctly", () => Precision.AlmostEquals(holdNote.ScreenSpaceDrawQuad.BottomLeft, holdNote.Head.ScreenSpaceDrawQuad.BottomLeft)); AddAssert("head note positioned correctly", () => Precision.AlmostEquals(holdNote.ScreenSpaceDrawQuad.BottomLeft, holdNote.Head.ScreenSpaceDrawQuad.BottomLeft));
AddAssert("tail note positioned correctly", () => Precision.AlmostEquals(holdNote.ScreenSpaceDrawQuad.TopLeft, holdNote.Tail.ScreenSpaceDrawQuad.BottomLeft)); AddAssert("tail note positioned correctly", () => Precision.AlmostEquals(holdNote.ScreenSpaceDrawQuad.TopLeft, holdNote.Tail.ScreenSpaceDrawQuad.BottomLeft));
AddAssert("head blueprint positioned correctly", () => this.ChildrenOfType<HoldNoteNoteSelectionBlueprint>().ElementAt(0).DrawPosition == holdNote.Head.DrawPosition); AddAssert("head blueprint positioned correctly", () => this.ChildrenOfType<HoldNoteNoteOverlay>().ElementAt(0).DrawPosition == holdNote.Head.DrawPosition);
AddAssert("tail blueprint positioned correctly", () => this.ChildrenOfType<HoldNoteNoteSelectionBlueprint>().ElementAt(1).DrawPosition == holdNote.Tail.DrawPosition); AddAssert("tail blueprint positioned correctly", () => this.ChildrenOfType<HoldNoteNoteOverlay>().ElementAt(1).DrawPosition == holdNote.Tail.DrawPosition);
} }
private void setScrollStep(ScrollingDirection direction) private void setScrollStep(ScrollingDirection direction)

View File

@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
Child = drawableObject = new DrawableNote(note) Child = drawableObject = new DrawableNote(note)
}; };
AddBlueprint(new NoteSelectionBlueprint(drawableObject)); AddBlueprint(new NoteSelectionBlueprint(note), drawableObject);
} }
} }
} }

View File

@ -2,34 +2,35 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Objects.Drawables;
namespace osu.Game.Rulesets.Mania.Edit.Blueprints namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{ {
public class HoldNoteNoteSelectionBlueprint : ManiaSelectionBlueprint public class HoldNoteNoteOverlay : CompositeDrawable
{ {
protected new DrawableHoldNote DrawableObject => (DrawableHoldNote)base.DrawableObject; private readonly HoldNoteSelectionBlueprint holdNoteBlueprint;
private readonly HoldNotePosition position; private readonly HoldNotePosition position;
public HoldNoteNoteSelectionBlueprint(DrawableHoldNote holdNote, HoldNotePosition position) public HoldNoteNoteOverlay(HoldNoteSelectionBlueprint holdNoteBlueprint, HoldNotePosition position)
: base(holdNote)
{ {
this.holdNoteBlueprint = holdNoteBlueprint;
this.position = position; this.position = position;
InternalChild = new EditNotePiece { RelativeSizeAxes = Axes.X };
Select(); InternalChild = new EditNotePiece { RelativeSizeAxes = Axes.X };
} }
protected override void Update() protected override void Update()
{ {
base.Update(); base.Update();
var drawableObject = holdNoteBlueprint.DrawableObject;
// Todo: This shouldn't exist, mania should not reference the drawable hitobject directly. // Todo: This shouldn't exist, mania should not reference the drawable hitobject directly.
if (DrawableObject.IsLoaded) if (drawableObject.IsLoaded)
{ {
DrawableNote note = position == HoldNotePosition.Start ? (DrawableNote)DrawableObject.Head : DrawableObject.Tail; DrawableNote note = position == HoldNotePosition.Start ? (DrawableNote)drawableObject.Head : drawableObject.Tail;
Anchor = note.Anchor; Anchor = note.Anchor;
Origin = note.Origin; Origin = note.Origin;
@ -38,8 +39,5 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
Position = note.DrawPosition; Position = note.DrawPosition;
} }
} }
// Todo: This is temporary, since the note masks don't do anything special yet. In the future they will handle input.
public override bool HandlePositionalInput => false;
} }
} }

View File

@ -8,13 +8,14 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling;
using osuTK; using osuTK;
namespace osu.Game.Rulesets.Mania.Edit.Blueprints namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{ {
public class HoldNoteSelectionBlueprint : ManiaSelectionBlueprint public class HoldNoteSelectionBlueprint : ManiaSelectionBlueprint<HoldNote>
{ {
public new DrawableHoldNote DrawableObject => (DrawableHoldNote)base.DrawableObject; public new DrawableHoldNote DrawableObject => (DrawableHoldNote)base.DrawableObject;
@ -23,7 +24,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
[Resolved] [Resolved]
private OsuColour colours { get; set; } private OsuColour colours { get; set; }
public HoldNoteSelectionBlueprint(DrawableHoldNote hold) public HoldNoteSelectionBlueprint(HoldNote hold)
: base(hold) : base(hold)
{ {
} }
@ -32,16 +33,11 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
private void load(IScrollingInfo scrollingInfo) private void load(IScrollingInfo scrollingInfo)
{ {
direction.BindTo(scrollingInfo.Direction); direction.BindTo(scrollingInfo.Direction);
}
protected override void LoadComplete()
{
base.LoadComplete();
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
new HoldNoteNoteSelectionBlueprint(DrawableObject, HoldNotePosition.Start), new HoldNoteNoteOverlay(this, HoldNotePosition.Start),
new HoldNoteNoteSelectionBlueprint(DrawableObject, HoldNotePosition.End), new HoldNoteNoteOverlay(this, HoldNotePosition.End),
new Container new Container
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,

View File

@ -4,22 +4,23 @@
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling;
using osuTK; using osuTK;
namespace osu.Game.Rulesets.Mania.Edit.Blueprints namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{ {
public abstract class ManiaSelectionBlueprint : OverlaySelectionBlueprint public abstract class ManiaSelectionBlueprint<T> : HitObjectSelectionBlueprint<T>
where T : ManiaHitObject
{ {
public new DrawableManiaHitObject DrawableObject => (DrawableManiaHitObject)base.DrawableObject; public new DrawableManiaHitObject DrawableObject => (DrawableManiaHitObject)base.DrawableObject;
[Resolved] [Resolved]
private IScrollingInfo scrollingInfo { get; set; } private IScrollingInfo scrollingInfo { get; set; }
protected ManiaSelectionBlueprint(DrawableHitObject drawableObject) protected ManiaSelectionBlueprint(T hitObject)
: base(drawableObject) : base(hitObject)
{ {
RelativeSizeAxes = Axes.None; RelativeSizeAxes = Axes.None;
} }

View File

@ -3,13 +3,13 @@
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Objects;
namespace osu.Game.Rulesets.Mania.Edit.Blueprints namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{ {
public class NoteSelectionBlueprint : ManiaSelectionBlueprint public class NoteSelectionBlueprint : ManiaSelectionBlueprint<Note>
{ {
public NoteSelectionBlueprint(DrawableNote note) public NoteSelectionBlueprint(Note note)
: base(note) : base(note)
{ {
AddInternal(new EditNotePiece { RelativeSizeAxes = Axes.X }); AddInternal(new EditNotePiece { RelativeSizeAxes = Axes.X });

View File

@ -3,9 +3,8 @@
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Edit.Blueprints; using osu.Game.Rulesets.Mania.Edit.Blueprints;
using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Screens.Edit.Compose.Components;
namespace osu.Game.Rulesets.Mania.Edit namespace osu.Game.Rulesets.Mania.Edit
@ -17,18 +16,18 @@ namespace osu.Game.Rulesets.Mania.Edit
{ {
} }
public override OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) public override HitObjectSelectionBlueprint CreateHitObjectBlueprintFor(HitObject hitObject)
{ {
switch (hitObject) switch (hitObject)
{ {
case DrawableNote note: case Note note:
return new NoteSelectionBlueprint(note); return new NoteSelectionBlueprint(note);
case DrawableHoldNote holdNote: case HoldNote holdNote:
return new HoldNoteSelectionBlueprint(holdNote); return new HoldNoteSelectionBlueprint(holdNote);
} }
return base.CreateBlueprintFor(hitObject); return base.CreateHitObjectBlueprintFor(hitObject);
} }
protected override SelectionHandler<HitObject> CreateSelectionHandler() => new ManiaSelectionHandler(); protected override SelectionHandler<HitObject> CreateSelectionHandler() => new ManiaSelectionHandler();

View File

@ -5,7 +5,6 @@ using System;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Edit.Blueprints;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling;
@ -23,8 +22,8 @@ namespace osu.Game.Rulesets.Mania.Edit
public override bool HandleMovement(MoveSelectionEvent<HitObject> moveEvent) public override bool HandleMovement(MoveSelectionEvent<HitObject> moveEvent)
{ {
var maniaBlueprint = (ManiaSelectionBlueprint)moveEvent.Blueprint; var hitObjectBlueprint = (HitObjectSelectionBlueprint)moveEvent.Blueprint;
int lastColumn = maniaBlueprint.DrawableObject.HitObject.Column; int lastColumn = ((ManiaHitObject)hitObjectBlueprint.Item).Column;
performColumnMovement(lastColumn, moveEvent); performColumnMovement(lastColumn, moveEvent);

View File

@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Mania.Mods namespace osu.Game.Rulesets.Mania.Mods
@ -39,5 +40,13 @@ namespace osu.Game.Rulesets.Mania.Mods
})); }));
} }
} }
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state)
{
}
protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state)
{
}
} }
} }

View File

@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 }); hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 });
Add(drawableObject = new DrawableHitCircle(hitCircle)); Add(drawableObject = new DrawableHitCircle(hitCircle));
AddBlueprint(blueprint = new TestBlueprint(drawableObject)); AddBlueprint(blueprint = new TestBlueprint(hitCircle), drawableObject);
}); });
[Test] [Test]
@ -63,8 +63,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
{ {
public new HitCirclePiece CirclePiece => base.CirclePiece; public new HitCirclePiece CirclePiece => base.CirclePiece;
public TestBlueprint(DrawableHitCircle drawableCircle) public TestBlueprint(HitCircle circle)
: base(drawableCircle) : base(circle)
{ {
} }
} }

View File

@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 }); slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 });
Add(drawableObject = new DrawableSlider(slider)); Add(drawableObject = new DrawableSlider(slider));
AddBlueprint(new TestSliderBlueprint(drawableObject)); AddBlueprint(new TestSliderBlueprint(slider), drawableObject);
}); });
[Test] [Test]
@ -150,23 +150,23 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
private class TestSliderBlueprint : SliderSelectionBlueprint private class TestSliderBlueprint : SliderSelectionBlueprint
{ {
public new SliderBodyPiece BodyPiece => base.BodyPiece; public new SliderBodyPiece BodyPiece => base.BodyPiece;
public new TestSliderCircleBlueprint HeadBlueprint => (TestSliderCircleBlueprint)base.HeadBlueprint; public new TestSliderCircleOverlay HeadOverlay => (TestSliderCircleOverlay)base.HeadOverlay;
public new TestSliderCircleBlueprint TailBlueprint => (TestSliderCircleBlueprint)base.TailBlueprint; public new TestSliderCircleOverlay TailOverlay => (TestSliderCircleOverlay)base.TailOverlay;
public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser; public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser;
public TestSliderBlueprint(DrawableSlider slider) public TestSliderBlueprint(Slider slider)
: base(slider) : base(slider)
{ {
} }
protected override SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint(DrawableSlider slider, SliderPosition position) => new TestSliderCircleBlueprint(slider, position); protected override SliderCircleOverlay CreateCircleOverlay(Slider slider, SliderPosition position) => new TestSliderCircleOverlay(slider, position);
} }
private class TestSliderCircleBlueprint : SliderCircleSelectionBlueprint private class TestSliderCircleOverlay : SliderCircleOverlay
{ {
public new HitCirclePiece CirclePiece => base.CirclePiece; public new HitCirclePiece CirclePiece => base.CirclePiece;
public TestSliderCircleBlueprint(DrawableSlider slider, SliderPosition position) public TestSliderCircleOverlay(Slider slider, SliderPosition position)
: base(slider, position) : base(slider, position)
{ {
} }

View File

@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 }); slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 });
Add(drawableObject = new DrawableSlider(slider)); Add(drawableObject = new DrawableSlider(slider));
AddBlueprint(blueprint = new TestSliderBlueprint(drawableObject)); AddBlueprint(blueprint = new TestSliderBlueprint(slider), drawableObject);
}); });
[Test] [Test]
@ -174,10 +174,10 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
AddAssert("body positioned correctly", () => blueprint.BodyPiece.Position == slider.StackedPosition); AddAssert("body positioned correctly", () => blueprint.BodyPiece.Position == slider.StackedPosition);
AddAssert("head positioned correctly", AddAssert("head positioned correctly",
() => Precision.AlmostEquals(blueprint.HeadBlueprint.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.HeadCircle.ScreenSpaceDrawQuad.Centre)); () => Precision.AlmostEquals(blueprint.HeadOverlay.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.HeadCircle.ScreenSpaceDrawQuad.Centre));
AddAssert("tail positioned correctly", AddAssert("tail positioned correctly",
() => Precision.AlmostEquals(blueprint.TailBlueprint.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.TailCircle.ScreenSpaceDrawQuad.Centre)); () => Precision.AlmostEquals(blueprint.TailOverlay.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.TailCircle.ScreenSpaceDrawQuad.Centre));
} }
private void moveMouseToControlPoint(int index) private void moveMouseToControlPoint(int index)
@ -195,23 +195,23 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
private class TestSliderBlueprint : SliderSelectionBlueprint private class TestSliderBlueprint : SliderSelectionBlueprint
{ {
public new SliderBodyPiece BodyPiece => base.BodyPiece; public new SliderBodyPiece BodyPiece => base.BodyPiece;
public new TestSliderCircleBlueprint HeadBlueprint => (TestSliderCircleBlueprint)base.HeadBlueprint; public new TestSliderCircleOverlay HeadOverlay => (TestSliderCircleOverlay)base.HeadOverlay;
public new TestSliderCircleBlueprint TailBlueprint => (TestSliderCircleBlueprint)base.TailBlueprint; public new TestSliderCircleOverlay TailOverlay => (TestSliderCircleOverlay)base.TailOverlay;
public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser; public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser;
public TestSliderBlueprint(DrawableSlider slider) public TestSliderBlueprint(Slider slider)
: base(slider) : base(slider)
{ {
} }
protected override SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint(DrawableSlider slider, SliderPosition position) => new TestSliderCircleBlueprint(slider, position); protected override SliderCircleOverlay CreateCircleOverlay(Slider slider, SliderPosition position) => new TestSliderCircleOverlay(slider, position);
} }
private class TestSliderCircleBlueprint : SliderCircleSelectionBlueprint private class TestSliderCircleOverlay : SliderCircleOverlay
{ {
public new HitCirclePiece CirclePiece => base.CirclePiece; public new HitCirclePiece CirclePiece => base.CirclePiece;
public TestSliderCircleBlueprint(DrawableSlider slider, SliderPosition position) public TestSliderCircleOverlay(Slider slider, SliderPosition position)
: base(slider, position) : base(slider, position)
{ {
} }

View File

@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
Child = drawableSpinner = new DrawableSpinner(spinner) Child = drawableSpinner = new DrawableSpinner(spinner)
}); });
AddBlueprint(new SpinnerSelectionBlueprint(drawableSpinner) { Size = new Vector2(0.5f) }); AddBlueprint(new SpinnerSelectionBlueprint(spinner) { Size = new Vector2(0.5f) }, drawableSpinner);
} }
} }
} }

View File

@ -15,8 +15,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles
protected readonly HitCirclePiece CirclePiece; protected readonly HitCirclePiece CirclePiece;
public HitCircleSelectionBlueprint(DrawableHitCircle drawableCircle) public HitCircleSelectionBlueprint(HitCircle circle)
: base(drawableCircle) : base(circle)
{ {
InternalChild = CirclePiece = new HitCirclePiece(); InternalChild = CirclePiece = new HitCirclePiece();
} }

View File

@ -2,20 +2,20 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints namespace osu.Game.Rulesets.Osu.Edit.Blueprints
{ {
public abstract class OsuSelectionBlueprint<T> : OverlaySelectionBlueprint public abstract class OsuSelectionBlueprint<T> : HitObjectSelectionBlueprint<T>
where T : OsuHitObject where T : OsuHitObject
{ {
protected T HitObject => (T)DrawableObject.HitObject; protected new DrawableOsuHitObject DrawableObject => (DrawableOsuHitObject)base.DrawableObject;
protected override bool AlwaysShowWhenSelected => true; protected override bool AlwaysShowWhenSelected => true;
protected OsuSelectionBlueprint(DrawableHitObject drawableObject) protected OsuSelectionBlueprint(T hitObject)
: base(drawableObject) : base(hitObject)
{ {
} }
} }

View File

@ -1,36 +1,32 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{ {
public class SliderCircleSelectionBlueprint : OsuSelectionBlueprint<Slider> public class SliderCircleOverlay : CompositeDrawable
{ {
protected readonly HitCirclePiece CirclePiece; protected readonly HitCirclePiece CirclePiece;
private readonly Slider slider;
private readonly SliderPosition position; private readonly SliderPosition position;
public SliderCircleSelectionBlueprint(DrawableSlider slider, SliderPosition position) public SliderCircleOverlay(Slider slider, SliderPosition position)
: base(slider)
{ {
this.slider = slider;
this.position = position; this.position = position;
InternalChild = CirclePiece = new HitCirclePiece(); InternalChild = CirclePiece = new HitCirclePiece();
Select();
} }
protected override void Update() protected override void Update()
{ {
base.Update(); base.Update();
CirclePiece.UpdateFrom(position == SliderPosition.Start ? (HitCircle)HitObject.HeadCircle : HitObject.TailCircle); CirclePiece.UpdateFrom(position == SliderPosition.Start ? (HitCircle)slider.HeadCircle : slider.TailCircle);
} }
// Todo: This is temporary, since the slider circle masks don't do anything special yet. In the future they will handle input.
public override bool HandlePositionalInput => false;
} }
} }

View File

@ -16,7 +16,6 @@ using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Screens.Edit; using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose; using osu.Game.Screens.Edit.Compose;
using osuTK; using osuTK;
@ -27,14 +26,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
public class SliderSelectionBlueprint : OsuSelectionBlueprint<Slider> public class SliderSelectionBlueprint : OsuSelectionBlueprint<Slider>
{ {
protected SliderBodyPiece BodyPiece { get; private set; } protected SliderBodyPiece BodyPiece { get; private set; }
protected SliderCircleSelectionBlueprint HeadBlueprint { get; private set; } protected SliderCircleOverlay HeadOverlay { get; private set; }
protected SliderCircleSelectionBlueprint TailBlueprint { get; private set; } protected SliderCircleOverlay TailOverlay { get; private set; }
[CanBeNull] [CanBeNull]
protected PathControlPointVisualiser ControlPointVisualiser { get; private set; } protected PathControlPointVisualiser ControlPointVisualiser { get; private set; }
private readonly DrawableSlider slider;
[Resolved(CanBeNull = true)] [Resolved(CanBeNull = true)]
private HitObjectComposer composer { get; set; } private HitObjectComposer composer { get; set; }
@ -52,10 +49,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
private readonly BindableList<PathControlPoint> controlPoints = new BindableList<PathControlPoint>(); private readonly BindableList<PathControlPoint> controlPoints = new BindableList<PathControlPoint>();
private readonly IBindable<int> pathVersion = new Bindable<int>(); private readonly IBindable<int> pathVersion = new Bindable<int>();
public SliderSelectionBlueprint(DrawableSlider slider) public SliderSelectionBlueprint(Slider slider)
: base(slider) : base(slider)
{ {
this.slider = slider;
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -64,8 +60,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
BodyPiece = new SliderBodyPiece(), BodyPiece = new SliderBodyPiece(),
HeadBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.Start), HeadOverlay = CreateCircleOverlay(HitObject, SliderPosition.Start),
TailBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.End), TailOverlay = CreateCircleOverlay(HitObject, SliderPosition.End),
}; };
} }
@ -103,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
protected override void OnSelected() protected override void OnSelected()
{ {
AddInternal(ControlPointVisualiser = new PathControlPointVisualiser(slider.HitObject, true) AddInternal(ControlPointVisualiser = new PathControlPointVisualiser(HitObject, true)
{ {
RemoveControlPointsRequested = removeControlPoints RemoveControlPointsRequested = removeControlPoints
}); });
@ -215,7 +211,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
} }
// If there are 0 or 1 remaining control points, the slider is in a degenerate (single point) form and should be deleted // If there are 0 or 1 remaining control points, the slider is in a degenerate (single point) form and should be deleted
if (controlPoints.Count <= 1 || !slider.HitObject.Path.HasValidLength) if (controlPoints.Count <= 1 || !HitObject.Path.HasValidLength)
{ {
placementHandler?.Delete(HitObject); placementHandler?.Delete(HitObject);
return; return;
@ -245,6 +241,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) =>
BodyPiece.ReceivePositionalInputAt(screenSpacePos) || ControlPointVisualiser?.Pieces.Any(p => p.ReceivePositionalInputAt(screenSpacePos)) == true; BodyPiece.ReceivePositionalInputAt(screenSpacePos) || ControlPointVisualiser?.Pieces.Any(p => p.ReceivePositionalInputAt(screenSpacePos)) == true;
protected virtual SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint(DrawableSlider slider, SliderPosition position) => new SliderCircleSelectionBlueprint(slider, position); protected virtual SliderCircleOverlay CreateCircleOverlay(Slider slider, SliderPosition position) => new SliderCircleOverlay(slider, position);
} }
} }

View File

@ -3,7 +3,6 @@
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components; using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osuTK; using osuTK;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
@ -12,7 +11,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
{ {
private readonly SpinnerPiece piece; private readonly SpinnerPiece piece;
public SpinnerSelectionBlueprint(DrawableSpinner spinner) public SpinnerSelectionBlueprint(Spinner spinner)
: base(spinner) : base(spinner)
{ {
InternalChild = piece = new SpinnerPiece(); InternalChild = piece = new SpinnerPiece();

View File

@ -3,11 +3,10 @@
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners; using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners;
using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Screens.Edit.Compose.Components;
namespace osu.Game.Rulesets.Osu.Edit namespace osu.Game.Rulesets.Osu.Edit
@ -21,21 +20,21 @@ namespace osu.Game.Rulesets.Osu.Edit
protected override SelectionHandler<HitObject> CreateSelectionHandler() => new OsuSelectionHandler(); protected override SelectionHandler<HitObject> CreateSelectionHandler() => new OsuSelectionHandler();
public override OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) public override HitObjectSelectionBlueprint CreateHitObjectBlueprintFor(HitObject hitObject)
{ {
switch (hitObject) switch (hitObject)
{ {
case DrawableHitCircle circle: case HitCircle circle:
return new HitCircleSelectionBlueprint(circle); return new HitCircleSelectionBlueprint(circle);
case DrawableSlider slider: case Slider slider:
return new SliderSelectionBlueprint(slider); return new SliderSelectionBlueprint(slider);
case DrawableSpinner spinner: case Spinner spinner:
return new SpinnerSelectionBlueprint(spinner); return new SpinnerSelectionBlueprint(spinner);
} }
return base.CreateBlueprintFor(hitObject); return base.CreateHitObjectBlueprintFor(hitObject);
} }
} }
} }

View File

@ -12,12 +12,23 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Screens.Edit.Compose.Components;
using osuTK; using Vector2 = osuTK.Vector2;
namespace osu.Game.Rulesets.Osu.Edit namespace osu.Game.Rulesets.Osu.Edit
{ {
public class OsuSelectionHandler : EditorSelectionHandler public class OsuSelectionHandler : EditorSelectionHandler
{ {
/// <summary>
/// During a transform, the initial origin is stored so it can be used throughout the operation.
/// </summary>
private Vector2? referenceOrigin;
/// <summary>
/// During a transform, the initial path types of a single selected slider are stored so they
/// can be maintained throughout the operation.
/// </summary>
private List<PathType?> referencePathTypes;
protected override void OnSelectionChanged() protected override void OnSelectionChanged()
{ {
base.OnSelectionChanged(); base.OnSelectionChanged();
@ -50,17 +61,6 @@ namespace osu.Game.Rulesets.Osu.Edit
return true; return true;
} }
/// <summary>
/// During a transform, the initial origin is stored so it can be used throughout the operation.
/// </summary>
private Vector2? referenceOrigin;
/// <summary>
/// During a transform, the initial path types of a single selected slider are stored so they
/// can be maintained throughout the operation.
/// </summary>
private List<PathType?> referencePathTypes;
public override bool HandleReverse() public override bool HandleReverse()
{ {
var hitObjects = EditorBeatmap.SelectedHitObjects; var hitObjects = EditorBeatmap.SelectedHitObjects;
@ -114,24 +114,10 @@ namespace osu.Game.Rulesets.Osu.Edit
var hitObjects = selectedMovableObjects; var hitObjects = selectedMovableObjects;
var selectedObjectsQuad = getSurroundingQuad(hitObjects); var selectedObjectsQuad = getSurroundingQuad(hitObjects);
var centre = selectedObjectsQuad.Centre;
foreach (var h in hitObjects) foreach (var h in hitObjects)
{ {
var pos = h.Position; h.Position = GetFlippedPosition(direction, selectedObjectsQuad, h.Position);
switch (direction)
{
case Direction.Horizontal:
pos.X = centre.X - (pos.X - centre.X);
break;
case Direction.Vertical:
pos.Y = centre.Y - (pos.Y - centre.Y);
break;
}
h.Position = pos;
if (h is Slider slider) if (h is Slider slider)
{ {
@ -204,7 +190,7 @@ namespace osu.Game.Rulesets.Osu.Edit
{ {
referencePathTypes ??= slider.Path.ControlPoints.Select(p => p.Type.Value).ToList(); referencePathTypes ??= slider.Path.ControlPoints.Select(p => p.Type.Value).ToList();
Quad sliderQuad = getSurroundingQuad(slider.Path.ControlPoints.Select(p => p.Position.Value)); Quad sliderQuad = GetSurroundingQuad(slider.Path.ControlPoints.Select(p => p.Position.Value));
// Limit minimum distance between control points after scaling to almost 0. Less than 0 causes the slider to flip, exactly 0 causes a crash through division by 0. // Limit minimum distance between control points after scaling to almost 0. Less than 0 causes the slider to flip, exactly 0 causes a crash through division by 0.
scale = Vector2.ComponentMax(new Vector2(Precision.FLOAT_EPSILON), sliderQuad.Size + scale) - sliderQuad.Size; scale = Vector2.ComponentMax(new Vector2(Precision.FLOAT_EPSILON), sliderQuad.Size + scale) - sliderQuad.Size;
@ -333,7 +319,7 @@ namespace osu.Game.Rulesets.Osu.Edit
/// </summary> /// </summary>
/// <param name="hitObjects">The hit objects to calculate a quad for.</param> /// <param name="hitObjects">The hit objects to calculate a quad for.</param>
private Quad getSurroundingQuad(OsuHitObject[] hitObjects) => private Quad getSurroundingQuad(OsuHitObject[] hitObjects) =>
getSurroundingQuad(hitObjects.SelectMany(h => GetSurroundingQuad(hitObjects.SelectMany(h =>
{ {
if (h is IHasPath path) if (h is IHasPath path)
{ {
@ -348,30 +334,6 @@ namespace osu.Game.Rulesets.Osu.Edit
return new[] { h.Position }; return new[] { h.Position };
})); }));
/// <summary>
/// Returns a gamefield-space quad surrounding the provided points.
/// </summary>
/// <param name="points">The points to calculate a quad for.</param>
private Quad getSurroundingQuad(IEnumerable<Vector2> points)
{
if (!EditorBeatmap.SelectedHitObjects.Any())
return new Quad();
Vector2 minPosition = new Vector2(float.MaxValue, float.MaxValue);
Vector2 maxPosition = new Vector2(float.MinValue, float.MinValue);
// 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);
}
Vector2 size = maxPosition - minPosition;
return new Quad(minPosition.X, minPosition.Y, size.X, size.Y);
}
/// <summary> /// <summary>
/// All osu! hitobjects which can be moved/rotated/scaled. /// All osu! hitobjects which can be moved/rotated/scaled.
/// </summary> /// </summary>

View File

@ -43,13 +43,11 @@ namespace osu.Game.Rulesets.Osu.Mods
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state)
{ {
base.ApplyIncreasedVisibilityState(hitObject, state);
applyState(hitObject, true); applyState(hitObject, true);
} }
protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state)
{ {
base.ApplyNormalVisibilityState(hitObject, state);
applyState(hitObject, false); applyState(hitObject, false);
} }
@ -60,20 +58,20 @@ namespace osu.Game.Rulesets.Osu.Mods
OsuHitObject hitObject = drawableOsuObject.HitObject; OsuHitObject hitObject = drawableOsuObject.HitObject;
(double startTime, double duration) fadeOut = getFadeOutParameters(drawableOsuObject); (double fadeStartTime, double fadeDuration) = getFadeOutParameters(drawableOsuObject);
switch (drawableObject) switch (drawableObject)
{ {
case DrawableSliderTail _: case DrawableSliderTail _:
using (drawableObject.BeginAbsoluteSequence(fadeOut.startTime, true)) using (drawableObject.BeginAbsoluteSequence(fadeStartTime))
drawableObject.FadeOut(fadeOut.duration); drawableObject.FadeOut(fadeDuration);
break; break;
case DrawableSliderRepeat sliderRepeat: case DrawableSliderRepeat sliderRepeat:
using (drawableObject.BeginAbsoluteSequence(fadeOut.startTime, true)) using (drawableObject.BeginAbsoluteSequence(fadeStartTime))
// only apply to circle piece reverse arrow is not affected by hidden. // only apply to circle piece reverse arrow is not affected by hidden.
sliderRepeat.CirclePiece.FadeOut(fadeOut.duration); sliderRepeat.CirclePiece.FadeOut(fadeDuration);
break; break;
@ -88,23 +86,23 @@ namespace osu.Game.Rulesets.Osu.Mods
else else
{ {
// we don't want to see the approach circle // we don't want to see the approach circle
using (circle.BeginAbsoluteSequence(hitObject.StartTime - hitObject.TimePreempt, true)) using (circle.BeginAbsoluteSequence(hitObject.StartTime - hitObject.TimePreempt))
circle.ApproachCircle.Hide(); circle.ApproachCircle.Hide();
} }
using (drawableObject.BeginAbsoluteSequence(fadeOut.startTime, true)) using (drawableObject.BeginAbsoluteSequence(fadeStartTime))
fadeTarget.FadeOut(fadeOut.duration); fadeTarget.FadeOut(fadeDuration);
break; break;
case DrawableSlider slider: case DrawableSlider slider:
using (slider.BeginAbsoluteSequence(fadeOut.startTime, true)) using (slider.BeginAbsoluteSequence(fadeStartTime))
slider.Body.FadeOut(fadeOut.duration, Easing.Out); slider.Body.FadeOut(fadeDuration, Easing.Out);
break; break;
case DrawableSliderTick sliderTick: case DrawableSliderTick sliderTick:
using (sliderTick.BeginAbsoluteSequence(fadeOut.startTime, true)) using (sliderTick.BeginAbsoluteSequence(fadeStartTime))
sliderTick.FadeOut(fadeOut.duration); sliderTick.FadeOut(fadeDuration);
break; break;
@ -112,14 +110,14 @@ namespace osu.Game.Rulesets.Osu.Mods
// hide elements we don't care about. // hide elements we don't care about.
// todo: hide background // todo: hide background
using (spinner.BeginAbsoluteSequence(fadeOut.startTime, true)) using (spinner.BeginAbsoluteSequence(fadeStartTime))
spinner.FadeOut(fadeOut.duration); spinner.FadeOut(fadeDuration);
break; break;
} }
} }
private (double startTime, double duration) getFadeOutParameters(DrawableOsuHitObject drawableObject) private (double fadeStartTime, double fadeDuration) getFadeOutParameters(DrawableOsuHitObject drawableObject)
{ {
switch (drawableObject) switch (drawableObject)
{ {
@ -137,7 +135,7 @@ namespace osu.Game.Rulesets.Osu.Mods
return getParameters(drawableObject.HitObject); return getParameters(drawableObject.HitObject);
} }
static (double startTime, double duration) getParameters(OsuHitObject hitObject) static (double fadeStartTime, double fadeDuration) getParameters(OsuHitObject hitObject)
{ {
var fadeOutStartTime = hitObject.StartTime - hitObject.TimePreempt + hitObject.TimeFadeIn; var fadeOutStartTime = hitObject.StartTime - hitObject.TimePreempt + hitObject.TimeFadeIn;
var fadeOutDuration = hitObject.TimePreempt * fade_out_duration_multiplier; var fadeOutDuration = hitObject.TimePreempt * fade_out_duration_multiplier;

View File

@ -166,7 +166,7 @@ namespace osu.Game.Rulesets.Osu.Statistics
var point = new HitPoint(pointType, this) var point = new HitPoint(pointType, this)
{ {
Colour = pointType == HitPointType.Hit ? new Color4(102, 255, 204, 255) : new Color4(255, 102, 102, 255) BaseColour = pointType == HitPointType.Hit ? new Color4(102, 255, 204, 255) : new Color4(255, 102, 102, 255)
}; };
points[r][c] = point; points[r][c] = point;
@ -234,6 +234,11 @@ namespace osu.Game.Rulesets.Osu.Statistics
private class HitPoint : Circle private class HitPoint : Circle
{ {
/// <summary>
/// The base colour which will be lightened/darkened depending on the value of this <see cref="HitPoint"/>.
/// </summary>
public Color4 BaseColour;
private readonly HitPointType pointType; private readonly HitPointType pointType;
private readonly AccuracyHeatmap heatmap; private readonly AccuracyHeatmap heatmap;
@ -284,7 +289,7 @@ namespace osu.Game.Rulesets.Osu.Statistics
Alpha = Math.Min(amount / lighten_cutoff, 1); Alpha = Math.Min(amount / lighten_cutoff, 1);
if (pointType == HitPointType.Hit) if (pointType == HitPointType.Hit)
Colour = ((Color4)Colour).Lighten(Math.Max(0, amount - lighten_cutoff)); Colour = BaseColour.Lighten(Math.Max(0, amount - lighten_cutoff));
} }
} }

View File

@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.UI
var hitWindows = new OsuHitWindows(); var hitWindows = new OsuHitWindows();
foreach (var result in Enum.GetValues(typeof(HitResult)).OfType<HitResult>().Where(r => r > HitResult.None && hitWindows.IsHitResultAllowed(r))) foreach (var result in Enum.GetValues(typeof(HitResult)).OfType<HitResult>().Where(r => r > HitResult.None && hitWindows.IsHitResultAllowed(r)))
poolDictionary.Add(result, new DrawableJudgementPool(result, onJudgmentLoaded)); poolDictionary.Add(result, new DrawableJudgementPool(result, onJudgementLoaded));
AddRangeInternal(poolDictionary.Values); AddRangeInternal(poolDictionary.Values);
@ -102,7 +102,7 @@ namespace osu.Game.Rulesets.Osu.UI
} }
} }
private void onJudgmentLoaded(DrawableOsuJudgement judgement) private void onJudgementLoaded(DrawableOsuJudgement judgement)
{ {
judgementAboveHitObjectLayer.Add(judgement.GetProxyAboveHitObjectsContent()); judgementAboveHitObjectLayer.Add(judgement.GetProxyAboveHitObjectsContent());
} }

View File

@ -3,14 +3,14 @@
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects;
using osuTK; using osuTK;
namespace osu.Game.Rulesets.Taiko.Edit.Blueprints namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
{ {
public class TaikoSelectionBlueprint : OverlaySelectionBlueprint public class TaikoSelectionBlueprint : HitObjectSelectionBlueprint
{ {
public TaikoSelectionBlueprint(DrawableHitObject hitObject) public TaikoSelectionBlueprint(HitObject hitObject)
: base(hitObject) : base(hitObject)
{ {
RelativeSizeAxes = Axes.None; RelativeSizeAxes = Axes.None;

View File

@ -3,7 +3,6 @@
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Taiko.Edit.Blueprints; using osu.Game.Rulesets.Taiko.Edit.Blueprints;
using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Screens.Edit.Compose.Components;
@ -18,7 +17,7 @@ namespace osu.Game.Rulesets.Taiko.Edit
protected override SelectionHandler<HitObject> CreateSelectionHandler() => new TaikoSelectionHandler(); protected override SelectionHandler<HitObject> CreateSelectionHandler() => new TaikoSelectionHandler();
public override OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) => public override HitObjectSelectionBlueprint CreateHitObjectBlueprintFor(HitObject hitObject) =>
new TaikoSelectionBlueprint(hitObject); new TaikoSelectionBlueprint(hitObject);
} }
} }

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Taiko.Mods namespace osu.Game.Rulesets.Taiko.Mods
{ {
@ -10,5 +11,13 @@ namespace osu.Game.Rulesets.Taiko.Mods
public override string Description => @"Beats fade out before you hit them!"; public override string Description => @"Beats fade out before you hit them!";
public override double ScoreMultiplier => 1.06; public override double ScoreMultiplier => 1.06;
public override bool HasImplementation => false; public override bool HasImplementation => false;
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state)
{
}
protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state)
{
}
} }
} }

View File

@ -1,6 +1,8 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
@ -12,6 +14,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
public class TaikoModRandom : ModRandom, IApplicableToBeatmap public class TaikoModRandom : ModRandom, IApplicableToBeatmap
{ {
public override string Description => @"Shuffle around the colours!"; public override string Description => @"Shuffle around the colours!";
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(TaikoModSwap)).ToArray();
public void ApplyToBeatmap(IBeatmap beatmap) public void ApplyToBeatmap(IBeatmap beatmap)
{ {

View File

@ -0,0 +1,33 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Taiko.Beatmaps;
using osu.Game.Rulesets.Taiko.Objects;
namespace osu.Game.Rulesets.Taiko.Mods
{
public class TaikoModSwap : Mod, IApplicableToBeatmap
{
public override string Name => "Swap";
public override string Acronym => "SW";
public override string Description => @"Dons become kats, kats become dons";
public override ModType Type => ModType.Conversion;
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModRandom)).ToArray();
public void ApplyToBeatmap(IBeatmap beatmap)
{
var taikoBeatmap = (TaikoBeatmap)beatmap;
foreach (var obj in taikoBeatmap.HitObjects)
{
if (obj is Hit hit)
hit.Type = hit.Type == HitType.Centre ? HitType.Rim : HitType.Centre;
}
}
}
}

View File

@ -136,6 +136,7 @@ namespace osu.Game.Rulesets.Taiko
new TaikoModRandom(), new TaikoModRandom(),
new TaikoModDifficultyAdjust(), new TaikoModDifficultyAdjust(),
new TaikoModClassic(), new TaikoModClassic(),
new TaikoModSwap(),
}; };
case ModType.Automation: case ModType.Automation:

View File

@ -0,0 +1,173 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Edit.Compose;
using osu.Game.Tests.Visual;
namespace osu.Game.Tests.Editing
{
public class TestSceneHitObjectContainerEventBuffer : OsuTestScene
{
private readonly TestHitObject testObj = new TestHitObject();
private TestPlayfield playfield1;
private TestPlayfield playfield2;
private TestDrawable intermediateDrawable;
private HitObjectUsageEventBuffer eventBuffer;
private HitObject beganUsage;
private HitObject finishedUsage;
private HitObject transferredUsage;
[SetUp]
public void Setup() => Schedule(() =>
{
reset();
if (eventBuffer != null)
{
eventBuffer.HitObjectUsageBegan -= onHitObjectUsageBegan;
eventBuffer.HitObjectUsageFinished -= onHitObjectUsageFinished;
eventBuffer.HitObjectUsageTransferred -= onHitObjectUsageTransferred;
}
var topPlayfield = new TestPlayfield();
topPlayfield.AddNested(playfield1 = new TestPlayfield());
topPlayfield.AddNested(playfield2 = new TestPlayfield());
eventBuffer = new HitObjectUsageEventBuffer(topPlayfield);
eventBuffer.HitObjectUsageBegan += onHitObjectUsageBegan;
eventBuffer.HitObjectUsageFinished += onHitObjectUsageFinished;
eventBuffer.HitObjectUsageTransferred += onHitObjectUsageTransferred;
Children = new Drawable[]
{
topPlayfield,
intermediateDrawable = new TestDrawable(),
};
});
private void onHitObjectUsageBegan(HitObject obj) => beganUsage = obj;
private void onHitObjectUsageFinished(HitObject obj) => finishedUsage = obj;
private void onHitObjectUsageTransferred(HitObject obj, DrawableHitObject drawableObj) => transferredUsage = obj;
[Test]
public void TestUsageBeganAfterAdd()
{
AddStep("add hitobject", () => playfield1.Add(testObj));
addCheckStep(began: true);
}
[Test]
public void TestUsageFinishedAfterRemove()
{
AddStep("add hitobject", () => playfield1.Add(testObj));
addResetStep();
AddStep("remove hitobject", () => playfield1.Remove(testObj));
addCheckStep(finished: true);
}
[Test]
public void TestUsageTransferredWhenMovedBetweenPlayfields()
{
AddStep("add hitobject", () => playfield1.Add(testObj));
addResetStep();
AddStep("transfer hitobject to other playfield", () =>
{
playfield1.Remove(testObj);
playfield2.Add(testObj);
});
addCheckStep(transferred: true);
}
[Test]
public void TestRemoveImmediatelyAfterUsageBegan()
{
AddStep("add hitobject and schedule removal", () =>
{
playfield1.Add(testObj);
intermediateDrawable.Schedule(() => playfield1.Remove(testObj));
});
addCheckStep(began: true, finished: true);
}
[Test]
public void TestRemoveImmediatelyAfterTransferred()
{
AddStep("add hitobject", () => playfield1.Add(testObj));
addResetStep();
AddStep("transfer hitobject to other playfield and schedule removal", () =>
{
playfield1.Remove(testObj);
playfield2.Add(testObj);
intermediateDrawable.Schedule(() => playfield2.Remove(testObj));
});
addCheckStep(transferred: true, finished: true);
}
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
eventBuffer.Update();
}
private void addResetStep() => AddStep("reset", reset);
private void reset()
{
beganUsage = null;
finishedUsage = null;
transferredUsage = null;
}
private void addCheckStep(bool began = false, bool finished = false, bool transferred = false)
=> AddAssert($"began = {began}, finished = {finished}, transferred = {transferred}",
() => (beganUsage == testObj) == began && (finishedUsage == testObj) == finished && (transferredUsage == testObj) == transferred);
private class TestPlayfield : Playfield
{
public TestPlayfield()
{
RegisterPool<TestHitObject, TestDrawableHitObject>(1);
}
public new void AddNested(Playfield playfield)
{
AddInternal(playfield);
base.AddNested(playfield);
}
protected override HitObjectLifetimeEntry CreateLifetimeEntry(HitObject hitObject)
{
var entry = base.CreateLifetimeEntry(hitObject);
entry.KeepAlive = true;
return entry;
}
}
private class TestHitObject : HitObject
{
public override string ToString() => "TestHitObject";
}
private class TestDrawableHitObject : DrawableHitObject
{
}
private class TestDrawable : Drawable
{
public new void Schedule(Action action) => base.Schedule(action);
}
}
}

View File

@ -63,7 +63,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private void seekToBreak(int breakIndex) private void seekToBreak(int breakIndex)
{ {
AddStep($"seek to break {breakIndex}", () => Player.GameplayClockContainer.Seek(destBreak().StartTime)); AddStep($"seek to break {breakIndex}", () => Player.GameplayClockContainer.Seek(destBreak().StartTime));
AddUntilStep("wait for seek to complete", () => Player.HUDOverlay.Progress.ReferenceClock.CurrentTime >= destBreak().StartTime); AddUntilStep("wait for seek to complete", () => Player.DrawableRuleset.FrameStableClock.CurrentTime >= destBreak().StartTime);
BreakPeriod destBreak() => Beatmap.Value.Beatmap.Breaks.ElementAt(breakIndex); BreakPeriod destBreak() => Beatmap.Value.Beatmap.Breaks.ElementAt(breakIndex);
} }

View File

@ -7,6 +7,7 @@ using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using System.Linq; using System.Linq;
using Newtonsoft.Json; using Newtonsoft.Json;
using osu.Framework.Localisation;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.IO.Serialization; using osu.Game.IO.Serialization;
@ -127,6 +128,8 @@ namespace osu.Game.Beatmaps
// Metadata // Metadata
public string Version { get; set; } public string Version { get; set; }
private string versionString => string.IsNullOrEmpty(Version) ? string.Empty : $"[{Version}]";
[JsonProperty("difficulty_rating")] [JsonProperty("difficulty_rating")]
public double StarDifficulty { get; set; } public double StarDifficulty { get; set; }
@ -143,11 +146,12 @@ namespace osu.Game.Beatmaps
Version Version
}.Concat(Metadata?.SearchableTerms ?? Enumerable.Empty<string>()).Where(s => !string.IsNullOrEmpty(s)).ToArray(); }.Concat(Metadata?.SearchableTerms ?? Enumerable.Empty<string>()).Where(s => !string.IsNullOrEmpty(s)).ToArray();
public override string ToString() public override string ToString() => $"{Metadata ?? BeatmapSet?.Metadata} {versionString}".Trim();
{
string version = string.IsNullOrEmpty(Version) ? string.Empty : $"[{Version}]";
return $"{Metadata ?? BeatmapSet?.Metadata} {version}".Trim(); public RomanisableString ToRomanisableString()
{
var metadata = (Metadata ?? BeatmapSet?.Metadata)?.ToRomanisableString() ?? new RomanisableString(null, null);
return new RomanisableString($"{metadata.GetPreferred(true)} {versionString}".Trim(), $"{metadata.GetPreferred(false)} {versionString}".Trim());
} }
public bool Equals(BeatmapInfo other) public bool Equals(BeatmapInfo other)

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using System.Linq; using System.Linq;
using Newtonsoft.Json; using Newtonsoft.Json;
using osu.Framework.Localisation;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Users; using osu.Game.Users;
@ -90,6 +91,12 @@ namespace osu.Game.Beatmaps
return $"{Artist} - {Title} {author}".Trim(); return $"{Artist} - {Title} {author}".Trim();
} }
public RomanisableString ToRomanisableString()
{
string author = Author == null ? string.Empty : $"({Author})";
return new RomanisableString($"{ArtistUnicode} - {TitleUnicode} {author}".Trim(), $"{Artist} - {Title} {author}".Trim());
}
[JsonIgnore] [JsonIgnore]
public string[] SearchableTerms => new[] public string[] SearchableTerms => new[]
{ {

View File

@ -7,7 +7,10 @@ using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Graphics.Sprites;
using osu.Game.Users; using osu.Game.Users;
namespace osu.Game.Graphics.Containers namespace osu.Game.Graphics.Containers
@ -59,6 +62,14 @@ namespace osu.Game.Graphics.Containers
public void AddLink(string text, LinkAction action, string argument, string tooltipText = null, Action<SpriteText> creationParameters = null) public void AddLink(string text, LinkAction action, string argument, string tooltipText = null, Action<SpriteText> creationParameters = null)
=> createLink(AddText(text, creationParameters), new LinkDetails(action, argument), tooltipText); => createLink(AddText(text, creationParameters), new LinkDetails(action, argument), tooltipText);
public void AddLink(LocalisableString text, LinkAction action, string argument, string tooltipText = null, Action<SpriteText> creationParameters = null)
{
var spriteText = new OsuSpriteText { Text = text };
AddText(spriteText, creationParameters);
createLink(spriteText.Yield(), new LinkDetails(action, argument), tooltipText);
}
public void AddLink(IEnumerable<SpriteText> text, LinkAction action = LinkAction.External, string linkArgument = null, string tooltipText = null) public void AddLink(IEnumerable<SpriteText> text, LinkAction action = LinkAction.External, string linkArgument = null, string tooltipText = null)
{ {
foreach (var t in text) foreach (var t in text)

View File

@ -8,6 +8,7 @@ using osu.Framework.Graphics;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays;
using osuTK; using osuTK;
namespace osu.Game.Online.Chat namespace osu.Game.Online.Chat
@ -20,7 +21,10 @@ namespace osu.Game.Online.Chat
/// <summary> /// <summary>
/// Each word part of a chat link (split for word-wrap support). /// Each word part of a chat link (split for word-wrap support).
/// </summary> /// </summary>
public List<Drawable> Parts; public readonly List<Drawable> Parts;
[Resolved(CanBeNull = true)]
private OverlayColourProvider overlayColourProvider { get; set; }
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parts.Any(d => d.ReceivePositionalInputAt(screenSpacePos)); public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parts.Any(d => d.ReceivePositionalInputAt(screenSpacePos));
@ -34,7 +38,7 @@ namespace osu.Game.Online.Chat
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours)
{ {
IdleColour = colours.Blue; IdleColour = overlayColourProvider?.Light2 ?? colours.Blue;
} }
protected override IEnumerable<Drawable> EffectTargets => Parts; protected override IEnumerable<Drawable> EffectTargets => Parts;

View File

@ -92,10 +92,6 @@ namespace osu.Game.Online.Multiplayer
[Resolved] [Resolved]
private UserLookupCache userLookupCache { get; set; } = null!; private UserLookupCache userLookupCache { get; set; } = null!;
// Only exists for compatibility with old osu-server-spectator build.
// Todo: Can be removed on 2021/02/26.
private long defaultPlaylistItemId;
private Room? apiRoom; private Room? apiRoom;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -143,7 +139,6 @@ namespace osu.Game.Online.Multiplayer
{ {
Room = joinedRoom; Room = joinedRoom;
apiRoom = room; apiRoom = room;
defaultPlaylistItemId = apiRoom.Playlist.FirstOrDefault()?.ID ?? 0;
foreach (var user in joinedRoom.Users) foreach (var user in joinedRoom.Users)
updateUserPlayingState(user.UserID, user.State); updateUserPlayingState(user.UserID, user.State);
}, cancellationSource.Token).ConfigureAwait(false); }, cancellationSource.Token).ConfigureAwait(false);
@ -581,7 +576,7 @@ namespace osu.Game.Online.Multiplayer
void updateItem(PlaylistItem item) void updateItem(PlaylistItem item)
{ {
item.ID = settings.PlaylistItemId == 0 ? defaultPlaylistItemId : settings.PlaylistItemId; item.ID = settings.PlaylistItemId;
item.Beatmap.Value = beatmap; item.Beatmap.Value = beatmap;
item.Ruleset.Value = ruleset.RulesetInfo; item.Ruleset.Value = ruleset.RulesetInfo;
item.RequiredMods.Clear(); item.RequiredMods.Clear();

View File

@ -0,0 +1,128 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
using System.IO;
using osu.Framework.Allocation;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Screens;
using osuTK;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Localisation;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Framework.Screens;
namespace osu.Game.Overlays.Settings.Sections.Maintenance
{
public abstract class DirectorySelectScreen : OsuScreen
{
private TriangleButton selectionButton;
private DirectorySelector directorySelector;
/// <summary>
/// Text to display in the header to inform the user of what they are selecting.
/// </summary>
public abstract LocalisableString HeaderText { get; }
/// <summary>
/// Called upon selection of a directory by the user.
/// </summary>
/// <param name="directory">The selected directory</param>
protected abstract void OnSelection(DirectoryInfo directory);
/// <summary>
/// Whether the current directory is considered to be valid and can be selected.
/// </summary>
/// <param name="info">The current directory.</param>
/// <returns>Whether the selected directory is considered valid.</returns>
protected virtual bool IsValidDirectory(DirectoryInfo info) => true;
/// <summary>
/// The path at which to start selection from.
/// </summary>
protected virtual DirectoryInfo InitialPath => null;
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
InternalChild = new Container
{
Masking = true,
CornerRadius = 10,
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(0.5f, 0.8f),
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colours.GreySeafoamDark
},
new GridContainer
{
RelativeSizeAxes = Axes.Both,
RowDimensions = new[]
{
new Dimension(),
new Dimension(GridSizeMode.Relative, 0.8f),
new Dimension(),
},
Content = new[]
{
new Drawable[]
{
new OsuSpriteText
{
Text = HeaderText,
Font = OsuFont.Default.With(size: 40),
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
}
},
new Drawable[]
{
directorySelector = new DirectorySelector
{
RelativeSizeAxes = Axes.Both,
}
},
new Drawable[]
{
selectionButton = new TriangleButton
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Width = 300,
Text = "Select directory",
Action = () => OnSelection(directorySelector.CurrentPath.Value)
},
}
}
}
}
};
}
protected override void LoadComplete()
{
if (InitialPath != null)
directorySelector.CurrentPath.Value = InitialPath;
directorySelector.CurrentPath.BindValueChanged(e => selectionButton.Enabled.Value = e.NewValue != null && IsValidDirectory(e.NewValue), true);
base.LoadComplete();
}
public override void OnSuspending(IScreen next)
{
base.OnSuspending(next);
this.FadeOut(250);
}
}
}

View File

@ -4,24 +4,19 @@
using System; using System;
using System.IO; using System.IO;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Localisation;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Screens;
using osuTK;
namespace osu.Game.Overlays.Settings.Sections.Maintenance namespace osu.Game.Overlays.Settings.Sections.Maintenance
{ {
public class MigrationSelectScreen : OsuScreen public class MigrationSelectScreen : DirectorySelectScreen
{ {
private DirectorySelector directorySelector; [Resolved]
private Storage storage { get; set; }
protected override DirectoryInfo InitialPath => new DirectoryInfo(storage.GetFullPath(string.Empty)).Parent;
public override bool AllowExternalScreenChange => false; public override bool AllowExternalScreenChange => false;
@ -29,84 +24,11 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
public override bool HideOverlaysOnEnter => true; public override bool HideOverlaysOnEnter => true;
[BackgroundDependencyLoader(true)] public override LocalisableString HeaderText => "Please select a new location";
private void load(OsuGame game, Storage storage, OsuColour colours)
{
game?.Toolbar.Hide();
// begin selection in the parent directory of the current storage location protected override void OnSelection(DirectoryInfo directory)
var initialPath = new DirectoryInfo(storage.GetFullPath(string.Empty)).Parent?.FullName;
InternalChild = new Container
{ {
Masking = true, var target = directory;
CornerRadius = 10,
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(0.5f, 0.8f),
Children = new Drawable[]
{
new Box
{
Colour = colours.GreySeafoamDark,
RelativeSizeAxes = Axes.Both,
},
new GridContainer
{
RelativeSizeAxes = Axes.Both,
RowDimensions = new[]
{
new Dimension(),
new Dimension(GridSizeMode.Relative, 0.8f),
new Dimension(),
},
Content = new[]
{
new Drawable[]
{
new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Text = "Please select a new location",
Font = OsuFont.Default.With(size: 40)
},
},
new Drawable[]
{
directorySelector = new DirectorySelector(initialPath)
{
RelativeSizeAxes = Axes.Both,
}
},
new Drawable[]
{
new TriangleButton
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Width = 300,
Text = "Begin folder migration",
Action = start
},
}
}
}
}
};
}
public override void OnSuspending(IScreen next)
{
base.OnSuspending(next);
this.FadeOut(250);
}
private void start()
{
var target = directorySelector.CurrentPath.Value;
try try
{ {

View File

@ -9,12 +9,12 @@ using osuTK;
namespace osu.Game.Rulesets.Edit namespace osu.Game.Rulesets.Edit
{ {
public abstract class OverlaySelectionBlueprint : SelectionBlueprint<HitObject> public abstract class HitObjectSelectionBlueprint : SelectionBlueprint<HitObject>
{ {
/// <summary> /// <summary>
/// The <see cref="DrawableHitObject"/> which this <see cref="OverlaySelectionBlueprint"/> applies to. /// The <see cref="DrawableHitObject"/> which this <see cref="HitObjectSelectionBlueprint"/> applies to.
/// </summary> /// </summary>
public readonly DrawableHitObject DrawableObject; public DrawableHitObject DrawableObject { get; internal set; }
/// <summary> /// <summary>
/// Whether the blueprint should be shown even when the <see cref="DrawableObject"/> is not alive. /// Whether the blueprint should be shown even when the <see cref="DrawableObject"/> is not alive.
@ -23,10 +23,9 @@ namespace osu.Game.Rulesets.Edit
protected override bool ShouldBeAlive => (DrawableObject.IsAlive && DrawableObject.IsPresent) || (AlwaysShowWhenSelected && State == SelectionState.Selected); protected override bool ShouldBeAlive => (DrawableObject.IsAlive && DrawableObject.IsPresent) || (AlwaysShowWhenSelected && State == SelectionState.Selected);
protected OverlaySelectionBlueprint(DrawableHitObject drawableObject) protected HitObjectSelectionBlueprint(HitObject hitObject)
: base(drawableObject.HitObject) : base(hitObject)
{ {
DrawableObject = drawableObject;
} }
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => DrawableObject.ReceivePositionalInputAt(screenSpacePos); public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => DrawableObject.ReceivePositionalInputAt(screenSpacePos);
@ -35,4 +34,15 @@ namespace osu.Game.Rulesets.Edit
public override Quad SelectionQuad => DrawableObject.ScreenSpaceDrawQuad; public override Quad SelectionQuad => DrawableObject.ScreenSpaceDrawQuad;
} }
public abstract class HitObjectSelectionBlueprint<T> : HitObjectSelectionBlueprint
where T : HitObject
{
public T HitObject => (T)Item;
protected HitObjectSelectionBlueprint(T item)
: base(item)
{
}
}
} }

View File

@ -105,34 +105,34 @@ namespace osu.Game.Rulesets.Edit
protected override bool ShouldBeConsideredForInput(Drawable child) => State == SelectionState.Selected; protected override bool ShouldBeConsideredForInput(Drawable child) => State == SelectionState.Selected;
/// <summary> /// <summary>
/// Selects this <see cref="OverlaySelectionBlueprint"/>, causing it to become visible. /// Selects this <see cref="SelectionBlueprint{T}"/>, causing it to become visible.
/// </summary> /// </summary>
public void Select() => State = SelectionState.Selected; public void Select() => State = SelectionState.Selected;
/// <summary> /// <summary>
/// Deselects this <see cref="OverlaySelectionBlueprint"/>, causing it to become invisible. /// Deselects this <see cref="HitObjectSelectionBlueprint"/>, causing it to become invisible.
/// </summary> /// </summary>
public void Deselect() => State = SelectionState.NotSelected; public void Deselect() => State = SelectionState.NotSelected;
/// <summary> /// <summary>
/// Toggles the selection state of this <see cref="OverlaySelectionBlueprint"/>. /// Toggles the selection state of this <see cref="HitObjectSelectionBlueprint"/>.
/// </summary> /// </summary>
public void ToggleSelection() => State = IsSelected ? SelectionState.NotSelected : SelectionState.Selected; public void ToggleSelection() => State = IsSelected ? SelectionState.NotSelected : SelectionState.Selected;
public bool IsSelected => State == SelectionState.Selected; public bool IsSelected => State == SelectionState.Selected;
/// <summary> /// <summary>
/// The <see cref="MenuItem"/>s to be displayed in the context menu for this <see cref="OverlaySelectionBlueprint"/>. /// The <see cref="MenuItem"/>s to be displayed in the context menu for this <see cref="HitObjectSelectionBlueprint"/>.
/// </summary> /// </summary>
public virtual MenuItem[] ContextMenuItems => Array.Empty<MenuItem>(); public virtual MenuItem[] ContextMenuItems => Array.Empty<MenuItem>();
/// <summary> /// <summary>
/// The screen-space point that causes this <see cref="OverlaySelectionBlueprint"/> to be selected via a drag. /// The screen-space point that causes this <see cref="HitObjectSelectionBlueprint"/> to be selected via a drag.
/// </summary> /// </summary>
public virtual Vector2 ScreenSpaceSelectionPoint => ScreenSpaceDrawQuad.Centre; public virtual Vector2 ScreenSpaceSelectionPoint => ScreenSpaceDrawQuad.Centre;
/// <summary> /// <summary>
/// The screen-space quad that outlines this <see cref="OverlaySelectionBlueprint"/> for selections. /// The screen-space quad that outlines this <see cref="HitObjectSelectionBlueprint"/> for selections.
/// </summary> /// </summary>
public virtual Quad SelectionQuad => ScreenSpaceDrawQuad; public virtual Quad SelectionQuad => ScreenSpaceDrawQuad;

View File

@ -1,9 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring; using osu.Game.Scoring;
@ -18,14 +16,6 @@ namespace osu.Game.Rulesets.Mods
public override ModType Type => ModType.DifficultyIncrease; public override ModType Type => ModType.DifficultyIncrease;
public override bool Ranked => true; public override bool Ranked => true;
/// <summary>
/// Check whether the provided hitobject should be considered the "first" hideable object.
/// Can be used to skip spinners, for instance.
/// </summary>
/// <param name="hitObject">The hitobject to check.</param>
[Obsolete("Use IsFirstAdjustableObject() instead.")] // Can be removed 20210506
protected virtual bool IsFirstHideableObject(DrawableHitObject hitObject) => true;
public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor)
{ {
// Default value of ScoreProcessor's Rank in Hidden Mod should be SS+ // Default value of ScoreProcessor's Rank in Hidden Mod should be SS+
@ -46,39 +36,5 @@ namespace osu.Game.Rulesets.Mods
return rank; return rank;
} }
} }
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state)
{
#pragma warning disable 618
ApplyFirstObjectIncreaseVisibilityState(hitObject, state);
#pragma warning restore 618
}
protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state)
{
#pragma warning disable 618
ApplyHiddenState(hitObject, state);
#pragma warning restore 618
}
/// <summary>
/// Apply a special visibility state to the first object in a beatmap, if the user chooses to turn on the "increase first object visibility" setting.
/// </summary>
/// <param name="hitObject">The hit object to apply the state change to.</param>
/// <param name="state">The state of the hit object.</param>
[Obsolete("Use ApplyIncreasedVisibilityState() instead.")] // Can be removed 20210506
protected virtual void ApplyFirstObjectIncreaseVisibilityState(DrawableHitObject hitObject, ArmedState state)
{
}
/// <summary>
/// Apply a hidden state to the provided object.
/// </summary>
/// <param name="hitObject">The hit object to apply the state change to.</param>
/// <param name="state">The state of the hit object.</param>
[Obsolete("Use ApplyNormalVisibilityState() instead.")] // Can be removed 20210506
protected virtual void ApplyHiddenState(DrawableHitObject hitObject, ArmedState state)
{
}
} }
} }

View File

@ -354,9 +354,12 @@ namespace osu.Game.Rulesets.UI
// If this is the first time this DHO is being used, then apply the DHO mods. // If this is the first time this DHO is being used, then apply the DHO mods.
// This is done before Apply() so that the state is updated once when the hitobject is applied. // This is done before Apply() so that the state is updated once when the hitobject is applied.
if (mods != null)
{
foreach (var m in mods.OfType<IApplicableToDrawableHitObjects>()) foreach (var m in mods.OfType<IApplicableToDrawableHitObjects>())
m.ApplyToDrawableHitObjects(dho.Yield()); m.ApplyToDrawableHitObjects(dho.Yield());
} }
}
if (!lifetimeEntryMap.TryGetValue(hitObject, out var entry)) if (!lifetimeEntryMap.TryGetValue(hitObject, out var entry))
lifetimeEntryMap[hitObject] = entry = CreateLifetimeEntry(hitObject); lifetimeEntryMap[hitObject] = entry = CreateLifetimeEntry(hitObject);

View File

@ -299,6 +299,13 @@ namespace osu.Game.Screens.Edit.Compose.Components
{ {
} }
/// <summary>
/// Retrieves an item's blueprint.
/// </summary>
/// <param name="item">The item to retrieve the blueprint of.</param>
/// <returns>The blueprint.</returns>
protected SelectionBlueprint<T> GetBlueprintFor(T item) => blueprintMap[item];
#endregion #endregion
#region Selection #region Selection

View File

@ -74,6 +74,14 @@ namespace osu.Game.Screens.Edit.Compose.Components
} }
} }
protected override void TransferBlueprintFor(HitObject hitObject, DrawableHitObject drawableObject)
{
base.TransferBlueprintFor(hitObject, drawableObject);
var blueprint = (HitObjectSelectionBlueprint)GetBlueprintFor(hitObject);
blueprint.DrawableObject = drawableObject;
}
protected override bool OnKeyDown(KeyDownEvent e) protected override bool OnKeyDown(KeyDownEvent e)
{ {
if (e.ControlPressed) if (e.ControlPressed)
@ -246,10 +254,10 @@ namespace osu.Game.Screens.Edit.Compose.Components
if (drawable == null) if (drawable == null)
return null; return null;
return CreateBlueprintFor(drawable); return CreateHitObjectBlueprintFor(item).With(b => b.DrawableObject = drawable);
} }
public virtual OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) => null; public virtual HitObjectSelectionBlueprint CreateHitObjectBlueprintFor(HitObject hitObject) => null;
protected override void OnBlueprintAdded(HitObject item) protected override void OnBlueprintAdded(HitObject item)
{ {

View File

@ -9,6 +9,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Screens.Edit.Compose.Components namespace osu.Game.Screens.Edit.Compose.Components
{ {
@ -22,6 +23,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
protected readonly HitObjectComposer Composer; protected readonly HitObjectComposer Composer;
private HitObjectUsageEventBuffer usageEventBuffer;
protected EditorBlueprintContainer(HitObjectComposer composer) protected EditorBlueprintContainer(HitObjectComposer composer)
{ {
Composer = composer; Composer = composer;
@ -45,11 +48,19 @@ namespace osu.Game.Screens.Edit.Compose.Components
foreach (var obj in Composer.HitObjects) foreach (var obj in Composer.HitObjects)
AddBlueprintFor(obj.HitObject); AddBlueprintFor(obj.HitObject);
Composer.Playfield.HitObjectUsageBegan += AddBlueprintFor; usageEventBuffer = new HitObjectUsageEventBuffer(Composer.Playfield);
Composer.Playfield.HitObjectUsageFinished += RemoveBlueprintFor; usageEventBuffer.HitObjectUsageBegan += AddBlueprintFor;
usageEventBuffer.HitObjectUsageFinished += RemoveBlueprintFor;
usageEventBuffer.HitObjectUsageTransferred += TransferBlueprintFor;
} }
} }
protected override void Update()
{
base.Update();
usageEventBuffer?.Update();
}
protected override IEnumerable<SelectionBlueprint<HitObject>> SortForMovement(IReadOnlyList<SelectionBlueprint<HitObject>> blueprints) protected override IEnumerable<SelectionBlueprint<HitObject>> SortForMovement(IReadOnlyList<SelectionBlueprint<HitObject>> blueprints)
=> blueprints.OrderBy(b => b.Item.StartTime); => blueprints.OrderBy(b => b.Item.StartTime);
@ -80,6 +91,15 @@ namespace osu.Game.Screens.Edit.Compose.Components
base.AddBlueprintFor(item); base.AddBlueprintFor(item);
} }
/// <summary>
/// Invoked when a <see cref="HitObject"/> has been transferred to another <see cref="DrawableHitObject"/>.
/// </summary>
/// <param name="hitObject">The hit object which has been assigned to a new drawable.</param>
/// <param name="drawableObject">The new drawable that is representing the hit object.</param>
protected virtual void TransferBlueprintFor(HitObject hitObject, DrawableHitObject drawableObject)
{
}
protected override void DragOperationCompleted() protected override void DragOperationCompleted()
{ {
base.DragOperationCompleted(); base.DragOperationCompleted();
@ -133,11 +153,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
Beatmap.HitObjectRemoved -= RemoveBlueprintFor; Beatmap.HitObjectRemoved -= RemoveBlueprintFor;
} }
if (Composer != null) usageEventBuffer?.Dispose();
{
Composer.Playfield.HitObjectUsageBegan -= AddBlueprintFor;
Composer.Playfield.HitObjectUsageFinished -= RemoveBlueprintFor;
}
} }
} }
} }

View File

@ -7,7 +7,7 @@ using osuTK;
namespace osu.Game.Screens.Edit.Compose.Components namespace osu.Game.Screens.Edit.Compose.Components
{ {
/// <summary> /// <summary>
/// An event which occurs when a <see cref="OverlaySelectionBlueprint"/> is moved. /// An event which occurs when a <see cref="SelectionBlueprint{T}"/> is moved.
/// </summary> /// </summary>
public class MoveSelectionEvent<T> public class MoveSelectionEvent<T>
{ {

View File

@ -350,5 +350,55 @@ namespace osu.Game.Screens.Edit.Compose.Components
=> Enumerable.Empty<MenuItem>(); => Enumerable.Empty<MenuItem>();
#endregion #endregion
#region Helper Methods
/// <summary>
/// Given a flip direction, a surrounding quad for all selected objects, and a position,
/// will return the flipped position in screen space coordinates.
/// </summary>
protected static Vector2 GetFlippedPosition(Direction direction, Quad quad, Vector2 position)
{
var centre = quad.Centre;
switch (direction)
{
case Direction.Horizontal:
position.X = centre.X - (position.X - centre.X);
break;
case Direction.Vertical:
position.Y = centre.Y - (position.Y - centre.Y);
break;
}
return position;
}
/// <summary>
/// Returns a quad surrounding the provided points.
/// </summary>
/// <param name="points">The points to calculate a quad for.</param>
protected static Quad GetSurroundingQuad(IEnumerable<Vector2> points)
{
if (!points.Any())
return new Quad();
Vector2 minPosition = new Vector2(float.MaxValue, float.MaxValue);
Vector2 maxPosition = new Vector2(float.MinValue, float.MinValue);
// 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);
}
Vector2 size = maxPosition - minPosition;
return new Quad(minPosition.X, minPosition.Y, size.X, size.Y);
}
#endregion
} }
} }

View File

@ -0,0 +1,83 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI;
namespace osu.Game.Screens.Edit.Compose
{
/// <summary>
/// Buffers events from the many <see cref="HitObjectContainer"/>s in a nested <see cref="Playfield"/> hierarchy
/// to ensure correct ordering of events.
/// </summary>
internal class HitObjectUsageEventBuffer : IDisposable
{
/// <summary>
/// Invoked when a <see cref="HitObject"/> becomes used by a <see cref="DrawableHitObject"/>.
/// </summary>
/// <remarks>
/// If the ruleset uses pooled objects, this represents the time when the <see cref="HitObject"/>s become alive.
/// </remarks>
public event Action<HitObject> HitObjectUsageBegan;
/// <summary>
/// Invoked when a <see cref="HitObject"/> becomes unused by a <see cref="DrawableHitObject"/>.
/// </summary>
/// <remarks>
/// If the ruleset uses pooled objects, this represents the time when the <see cref="HitObject"/>s become dead.
/// </remarks>
public event Action<HitObject> HitObjectUsageFinished;
/// <summary>
/// Invoked when a <see cref="HitObject"/> has been transferred to another <see cref="DrawableHitObject"/>.
/// </summary>
public event Action<HitObject, DrawableHitObject> HitObjectUsageTransferred;
private readonly Playfield playfield;
/// <summary>
/// Creates a new <see cref="HitObjectUsageEventBuffer"/>.
/// </summary>
/// <param name="playfield">The most top-level <see cref="Playfield"/>.</param>
public HitObjectUsageEventBuffer([NotNull] Playfield playfield)
{
this.playfield = playfield;
playfield.HitObjectUsageBegan += onHitObjectUsageBegan;
playfield.HitObjectUsageFinished += onHitObjectUsageFinished;
}
private readonly List<HitObject> usageFinishedHitObjects = new List<HitObject>();
private void onHitObjectUsageBegan(HitObject hitObject)
{
if (usageFinishedHitObjects.Remove(hitObject))
HitObjectUsageTransferred?.Invoke(hitObject, playfield.AllHitObjects.Single(d => d.HitObject == hitObject));
else
HitObjectUsageBegan?.Invoke(hitObject);
}
private void onHitObjectUsageFinished(HitObject hitObject) => usageFinishedHitObjects.Add(hitObject);
public void Update()
{
foreach (var hitObject in usageFinishedHitObjects)
HitObjectUsageFinished?.Invoke(hitObject);
usageFinishedHitObjects.Clear();
}
public void Dispose()
{
if (playfield != null)
{
playfield.HitObjectUsageBegan -= onHitObjectUsageBegan;
playfield.HitObjectUsageFinished -= onHitObjectUsageFinished;
}
}
}
}

View File

@ -108,7 +108,7 @@ namespace osu.Game.Screens.OnlinePlay
difficultyIconContainer.Child = new DifficultyIcon(beatmap.Value, ruleset.Value, requiredMods) { Size = new Vector2(32) }; difficultyIconContainer.Child = new DifficultyIcon(beatmap.Value, ruleset.Value, requiredMods) { Size = new Vector2(32) };
beatmapText.Clear(); beatmapText.Clear();
beatmapText.AddLink(Item.Beatmap.ToString(), LinkAction.OpenBeatmap, Item.Beatmap.Value.OnlineBeatmapID.ToString()); beatmapText.AddLink(Item.Beatmap.Value.ToRomanisableString(), LinkAction.OpenBeatmap, Item.Beatmap.Value.OnlineBeatmapID.ToString());
authorText.Clear(); authorText.Clear();

View File

@ -35,8 +35,12 @@ namespace osu.Game.Screens.Play
/// </summary> /// </summary>
public float TopScoringElementsHeight { get; private set; } public float TopScoringElementsHeight { get; private set; }
/// <summary>
/// The total height of all the bottom of screen scoring elements.
/// </summary>
public float BottomScoringElementsHeight { get; private set; }
public readonly KeyCounterDisplay KeyCounter; public readonly KeyCounterDisplay KeyCounter;
public readonly SongProgress Progress;
public readonly ModDisplay ModDisplay; public readonly ModDisplay ModDisplay;
public readonly HoldForMenuButton HoldToQuit; public readonly HoldForMenuButton HoldToQuit;
public readonly PlayerSettingsOverlay PlayerSettingsOverlay; public readonly PlayerSettingsOverlay PlayerSettingsOverlay;
@ -59,8 +63,6 @@ namespace osu.Game.Screens.Play
private static bool hasShownNotificationOnce; private static bool hasShownNotificationOnce;
public Action<double> RequestSeek;
private readonly FillFlowContainer bottomRightElements; private readonly FillFlowContainer bottomRightElements;
private readonly FillFlowContainer topRightElements; private readonly FillFlowContainer topRightElements;
@ -83,16 +85,6 @@ namespace osu.Game.Screens.Play
{ {
CreateFailingLayer(), CreateFailingLayer(),
visibilityContainer = new Container visibilityContainer = new Container
{
RelativeSizeAxes = Axes.Both,
Child = new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new Drawable[]
{
new Container
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Children = new Drawable[] Children = new Drawable[]
@ -112,19 +104,6 @@ namespace osu.Game.Screens.Play
}, },
} }
}, },
},
new Drawable[]
{
Progress = CreateProgress(),
}
},
RowDimensions = new[]
{
new Dimension(),
new Dimension(GridSizeMode.AutoSize)
}
},
},
topRightElements = new FillFlowContainer topRightElements = new FillFlowContainer
{ {
Anchor = Anchor.TopRight, Anchor = Anchor.TopRight,
@ -164,10 +143,6 @@ namespace osu.Game.Screens.Play
if (drawableRuleset != null) if (drawableRuleset != null)
{ {
BindDrawableRuleset(drawableRuleset); BindDrawableRuleset(drawableRuleset);
Progress.Objects = drawableRuleset.Objects;
Progress.RequestSeek = time => RequestSeek(time);
Progress.ReferenceClock = drawableRuleset.FrameStableClock;
} }
ModDisplay.Current.Value = mods; ModDisplay.Current.Value = mods;
@ -206,26 +181,43 @@ namespace osu.Game.Screens.Play
{ {
base.Update(); base.Update();
Vector2 lowestScreenSpace = Vector2.Zero; Vector2? lowestTopScreenSpace = null;
Vector2? highestBottomScreenSpace = null;
// LINQ cast can be removed when IDrawable interface includes Anchor / RelativeSizeAxes. // LINQ cast can be removed when IDrawable interface includes Anchor / RelativeSizeAxes.
foreach (var element in mainComponents.Components.Cast<Drawable>()) foreach (var element in mainComponents.Components.Cast<Drawable>())
{ {
// for now align top-right components with the bottom-edge of the lowest top-anchored hud element. // for now align top-right components with the bottom-edge of the lowest top-anchored hud element.
if (!element.Anchor.HasFlagFast(Anchor.TopRight) && !element.RelativeSizeAxes.HasFlagFast(Axes.X)) if (!element.RelativeSizeAxes.HasFlagFast(Axes.X))
continue; continue;
if (element.Anchor.HasFlagFast(Anchor.TopRight))
{
// health bars are excluded for the sake of hacky legacy skins which extend the health bar to take up the full screen area. // health bars are excluded for the sake of hacky legacy skins which extend the health bar to take up the full screen area.
if (element is LegacyHealthDisplay) if (element is LegacyHealthDisplay)
continue; continue;
var bottomRight = element.ScreenSpaceDrawQuad.BottomRight; var bottomRight = element.ScreenSpaceDrawQuad.BottomRight;
if (bottomRight.Y > lowestScreenSpace.Y) if (lowestTopScreenSpace == null || bottomRight.Y > lowestTopScreenSpace.Value.Y)
lowestScreenSpace = bottomRight; lowestTopScreenSpace = bottomRight;
}
else if (element.Anchor.HasFlagFast(Anchor.y2))
{
var topLeft = element.ScreenSpaceDrawQuad.TopLeft;
if (highestBottomScreenSpace == null || topLeft.Y < highestBottomScreenSpace.Value.Y)
highestBottomScreenSpace = topLeft;
}
} }
topRightElements.Y = TopScoringElementsHeight = ToLocalSpace(lowestScreenSpace).Y; if (lowestTopScreenSpace.HasValue)
bottomRightElements.Y = -Progress.Height; topRightElements.Y = TopScoringElementsHeight = ToLocalSpace(lowestTopScreenSpace.Value).Y;
else
topRightElements.Y = 0;
if (highestBottomScreenSpace.HasValue)
bottomRightElements.Y = BottomScoringElementsHeight = -(DrawHeight - ToLocalSpace(highestBottomScreenSpace.Value).Y);
else
bottomRightElements.Y = 0;
} }
private void updateVisibility() private void updateVisibility()
@ -281,8 +273,6 @@ namespace osu.Game.Screens.Play
(drawableRuleset as ICanAttachKeyCounter)?.Attach(KeyCounter); (drawableRuleset as ICanAttachKeyCounter)?.Attach(KeyCounter);
replayLoaded.BindTo(drawableRuleset.HasReplayLoaded); replayLoaded.BindTo(drawableRuleset.HasReplayLoaded);
Progress.BindDrawableRuleset(drawableRuleset);
} }
protected FailingLayer CreateFailingLayer() => new FailingLayer protected FailingLayer CreateFailingLayer() => new FailingLayer
@ -296,13 +286,6 @@ namespace osu.Game.Screens.Play
Origin = Anchor.BottomRight, Origin = Anchor.BottomRight,
}; };
protected SongProgress CreateProgress() => new SongProgress
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
};
protected HoldForMenuButton CreateHoldForMenuButton() => new HoldForMenuButton protected HoldForMenuButton CreateHoldForMenuButton() => new HoldForMenuButton
{ {
Anchor = Anchor.BottomRight, Anchor = Anchor.BottomRight,

View File

@ -154,6 +154,9 @@ namespace osu.Game.Screens.Play
{ {
base.LoadComplete(); base.LoadComplete();
if (!LoadedBeatmapSuccessfully)
return;
// replays should never be recorded or played back when autoplay is enabled // replays should never be recorded or played back when autoplay is enabled
if (!Mods.Value.Any(m => m is ModAutoplay)) if (!Mods.Value.Any(m => m is ModAutoplay))
PrepareReplay(); PrepareReplay();
@ -198,6 +201,7 @@ namespace osu.Game.Screens.Play
LocalUserPlaying.BindTo(osuGame.LocalUserPlaying); LocalUserPlaying.BindTo(osuGame.LocalUserPlaying);
DrawableRuleset = ruleset.CreateDrawableRulesetWith(playableBeatmap, Mods.Value); DrawableRuleset = ruleset.CreateDrawableRulesetWith(playableBeatmap, Mods.Value);
dependencies.CacheAs(DrawableRuleset);
ScoreProcessor = ruleset.CreateScoreProcessor(); ScoreProcessor = ruleset.CreateScoreProcessor();
ScoreProcessor.ApplyBeatmap(playableBeatmap); ScoreProcessor.ApplyBeatmap(playableBeatmap);
@ -357,11 +361,6 @@ namespace osu.Game.Screens.Play
AlwaysVisible = { BindTarget = DrawableRuleset.HasReplayLoaded }, AlwaysVisible = { BindTarget = DrawableRuleset.HasReplayLoaded },
IsCounting = false IsCounting = false
}, },
RequestSeek = time =>
{
GameplayClockContainer.Seek(time);
GameplayClockContainer.Start();
},
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre Origin = Anchor.Centre
}, },
@ -566,6 +565,12 @@ namespace osu.Game.Screens.Play
updateSampleDisabledState(); updateSampleDisabledState();
} }
/// <summary>
/// Seek to a specific time in gameplay.
/// </summary>
/// <param name="time">The destination time to seek to.</param>
public void Seek(double time) => GameplayClockContainer.Seek(time);
/// <summary> /// <summary>
/// Restart gameplay via a parent <see cref="PlayerLoader"/>. /// Restart gameplay via a parent <see cref="PlayerLoader"/>.
/// <remarks>This can be called from a child screen in order to trigger the restart process.</remarks> /// <remarks>This can be called from a child screen in order to trigger the restart process.</remarks>
@ -924,11 +929,11 @@ namespace osu.Game.Screens.Play
/// </summary> /// </summary>
/// <param name="score">The <see cref="Score"/> to import.</param> /// <param name="score">The <see cref="Score"/> to import.</param>
/// <returns>The imported score.</returns> /// <returns>The imported score.</returns>
protected virtual Task ImportScore(Score score) protected virtual async Task ImportScore(Score score)
{ {
// Replays are already populated and present in the game's database, so should not be re-imported. // Replays are already populated and present in the game's database, so should not be re-imported.
if (DrawableRuleset.ReplayScore != null) if (DrawableRuleset.ReplayScore != null)
return Task.CompletedTask; return;
LegacyByteArrayReader replayReader; LegacyByteArrayReader replayReader;
@ -938,7 +943,18 @@ namespace osu.Game.Screens.Play
replayReader = new LegacyByteArrayReader(stream.ToArray(), "replay.osr"); replayReader = new LegacyByteArrayReader(stream.ToArray(), "replay.osr");
} }
return scoreManager.Import(score.ScoreInfo, replayReader); // For the time being, online ID responses are not really useful for anything.
// In addition, the IDs provided via new (lazer) endpoints are based on a different autoincrement from legacy (stable) scores.
//
// Until we better define the server-side logic behind this, let's not store the online ID to avoid potential unique constraint
// conflicts across various systems (ie. solo and multiplayer).
long? onlineScoreId = score.ScoreInfo.OnlineScoreID;
score.ScoreInfo.OnlineScoreID = null;
await scoreManager.Import(score.ScoreInfo, replayReader).ConfigureAwait(false);
// ... And restore the online ID for other processes to handle correctly (e.g. de-duplication for the results screen).
score.ScoreInfo.OnlineScoreID = onlineScoreId;
} }
/// <summary> /// <summary>

View File

@ -14,10 +14,11 @@ using osu.Framework.Timing;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Skinning;
namespace osu.Game.Screens.Play namespace osu.Game.Screens.Play
{ {
public class SongProgress : OverlayContainer public class SongProgress : OverlayContainer, ISkinnableDrawable
{ {
private const int info_height = 20; private const int info_height = 20;
private const int bottom_bar_height = 5; private const int bottom_bar_height = 5;
@ -39,9 +40,6 @@ namespace osu.Game.Screens.Play
public readonly Bindable<bool> ShowGraph = new Bindable<bool>(); public readonly Bindable<bool> ShowGraph = new Bindable<bool>();
//TODO: this isn't always correct (consider mania where a non-last object may last for longer than the last in the list).
private double lastHitTime => objects.Last().GetEndTime() + 1;
public override bool HandleNonPositionalInput => AllowSeeking.Value; public override bool HandleNonPositionalInput => AllowSeeking.Value;
public override bool HandlePositionalInput => AllowSeeking.Value; public override bool HandlePositionalInput => AllowSeeking.Value;
@ -49,6 +47,9 @@ namespace osu.Game.Screens.Play
private double firstHitTime => objects.First().StartTime; private double firstHitTime => objects.First().StartTime;
//TODO: this isn't always correct (consider mania where a non-last object may last for longer than the last in the list).
private double lastHitTime => objects.Last().GetEndTime() + 1;
private IEnumerable<HitObject> objects; private IEnumerable<HitObject> objects;
public IEnumerable<HitObject> Objects public IEnumerable<HitObject> Objects
@ -65,16 +66,20 @@ namespace osu.Game.Screens.Play
} }
} }
public IClock ReferenceClock; [Resolved(canBeNull: true)]
private Player player { get; set; }
private IClock gameplayClock; [Resolved(canBeNull: true)]
private GameplayClock gameplayClock { get; set; }
private IClock referenceClock;
public SongProgress() public SongProgress()
{ {
Children = new Drawable[] RelativeSizeAxes = Axes.X;
{ Anchor = Anchor.BottomRight;
new SongProgressDisplay Origin = Anchor.BottomRight;
{
Children = new Drawable[] Children = new Drawable[]
{ {
info = new SongProgressInfo info = new SongProgressInfo
@ -96,20 +101,23 @@ namespace osu.Game.Screens.Play
{ {
Anchor = Anchor.BottomLeft, Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft, Origin = Anchor.BottomLeft,
OnSeek = time => RequestSeek?.Invoke(time), OnSeek = time => player?.Seek(time),
},
}
}, },
}; };
} }
[BackgroundDependencyLoader(true)] [BackgroundDependencyLoader(true)]
private void load(OsuColour colours, GameplayClock clock, OsuConfigManager config) private void load(OsuColour colours, OsuConfigManager config, DrawableRuleset drawableRuleset)
{ {
base.LoadComplete(); base.LoadComplete();
if (clock != null) if (drawableRuleset != null)
gameplayClock = clock; {
AllowSeeking.BindTo(drawableRuleset.HasReplayLoaded);
referenceClock = drawableRuleset.FrameStableClock;
Objects = drawableRuleset.Objects;
}
config.BindWith(OsuSetting.ShowProgressGraph, ShowGraph); config.BindWith(OsuSetting.ShowProgressGraph, ShowGraph);
@ -124,11 +132,6 @@ namespace osu.Game.Screens.Play
ShowGraph.BindValueChanged(_ => updateGraphVisibility(), true); ShowGraph.BindValueChanged(_ => updateGraphVisibility(), true);
} }
public void BindDrawableRuleset(DrawableRuleset drawableRuleset)
{
AllowSeeking.BindTo(drawableRuleset.HasReplayLoaded);
}
protected override void PopIn() protected override void PopIn()
{ {
this.FadeIn(500, Easing.OutQuint); this.FadeIn(500, Easing.OutQuint);
@ -147,7 +150,7 @@ namespace osu.Game.Screens.Play
return; return;
double gameplayTime = gameplayClock?.CurrentTime ?? Time.Current; double gameplayTime = gameplayClock?.CurrentTime ?? Time.Current;
double frameStableTime = ReferenceClock?.CurrentTime ?? gameplayTime; double frameStableTime = referenceClock?.CurrentTime ?? gameplayTime;
double progress = Math.Min(1, (frameStableTime - firstHitTime) / (lastHitTime - firstHitTime)); double progress = Math.Min(1, (frameStableTime - firstHitTime) / (lastHitTime - firstHitTime));
@ -179,19 +182,5 @@ namespace osu.Game.Screens.Play
float finalMargin = bottom_bar_height + (AllowSeeking.Value ? handle_size.Y : 0) + (ShowGraph.Value ? graph_height : 0); float finalMargin = bottom_bar_height + (AllowSeeking.Value ? handle_size.Y : 0) + (ShowGraph.Value ? graph_height : 0);
info.TransformTo(nameof(info.Margin), new MarginPadding { Bottom = finalMargin }, transition_duration, Easing.In); info.TransformTo(nameof(info.Margin), new MarginPadding { Bottom = finalMargin }, transition_duration, Easing.In);
} }
public class SongProgressDisplay : Container
{
public SongProgressDisplay()
{
// TODO: move actual implementation into this.
// exists for skin customisation purposes (interface should be added to this container).
Masking = true;
RelativeSizeAxes = Axes.Both;
Anchor = Anchor.BottomCentre;
Origin = Anchor.BottomCentre;
}
}
} }
} }

View File

@ -116,12 +116,7 @@ namespace osu.Game.Screens.Play
request.Success += s => request.Success += s =>
{ {
// For the time being, online ID responses are not really useful for anything. score.ScoreInfo.OnlineScoreID = s.ID;
// In addition, the IDs provided via new (lazer) endpoints are based on a different autoincrement from legacy (stable) scores.
//
// Until we better define the server-side logic behind this, let's not store the online ID to avoid potential unique constraint
// conflicts across various systems (ie. solo and multiplayer).
// score.ScoreInfo.OnlineScoreID = s.ID;
tcs.SetResult(true); tcs.SetResult(true);
}; };

View File

@ -12,6 +12,7 @@ using osu.Framework.Graphics.Textures;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Extensions; using osu.Game.Extensions;
using osu.Game.IO; using osu.Game.IO;
using osu.Game.Screens.Play;
using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
@ -86,6 +87,7 @@ namespace osu.Game.Skinning
GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreCounter)), GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreCounter)),
GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.AccuracyCounter)), GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.AccuracyCounter)),
GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.HealthDisplay)), GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.HealthDisplay)),
GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.SongProgress)),
} }
}; };
@ -109,6 +111,9 @@ namespace osu.Game.Skinning
case HUDSkinComponents.HealthDisplay: case HUDSkinComponents.HealthDisplay:
return new DefaultHealthDisplay(); return new DefaultHealthDisplay();
case HUDSkinComponents.SongProgress:
return new SongProgress();
} }
break; break;

View File

@ -124,7 +124,7 @@ namespace osu.Game.Skinning.Editor
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => drawable.ReceivePositionalInputAt(screenSpacePos); public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => drawable.ReceivePositionalInputAt(screenSpacePos);
public override Vector2 ScreenSpaceSelectionPoint => drawable.ScreenSpaceDrawQuad.Centre; public override Vector2 ScreenSpaceSelectionPoint => drawable.ToScreenSpace(drawable.OriginPosition);
public override Quad SelectionQuad => drawable.ScreenSpaceDrawQuad; public override Quad SelectionQuad => drawable.ScreenSpaceDrawQuad;
} }

View File

@ -57,7 +57,10 @@ namespace osu.Game.Skinning.Editor
Spacing = new Vector2(20) Spacing = new Vector2(20)
}; };
var skinnableTypes = typeof(OsuGame).Assembly.GetTypes().Where(t => typeof(ISkinnableDrawable).IsAssignableFrom(t)).ToArray(); var skinnableTypes = typeof(OsuGame).Assembly.GetTypes()
.Where(t => !t.IsInterface)
.Where(t => typeof(ISkinnableDrawable).IsAssignableFrom(t))
.ToArray();
foreach (var type in skinnableTypes) foreach (var type in skinnableTypes)
{ {
@ -92,6 +95,10 @@ namespace osu.Game.Skinning.Editor
private class ToolboxComponentButton : OsuButton private class ToolboxComponentButton : OsuButton
{ {
protected override bool ShouldBeConsideredForInput(Drawable child) => false;
public override bool PropagateNonPositionalInputSubTree => false;
private readonly Drawable component; private readonly Drawable component;
public Action<Type> RequestPlacement; public Action<Type> RequestPlacement;

View File

@ -43,10 +43,16 @@ namespace osu.Game.Skinning.Editor
public override bool HandleFlip(Direction direction) public override bool HandleFlip(Direction direction)
{ {
// TODO: this is temporary as well. var selectionQuad = GetSurroundingQuad(SelectedBlueprints.Select(b => b.ScreenSpaceSelectionPoint));
foreach (var c in SelectedBlueprints)
foreach (var b in SelectedBlueprints)
{ {
((Drawable)c.Item).Scale *= new Vector2( var drawableItem = (Drawable)b.Item;
drawableItem.Position =
drawableItem.Parent.ToLocalSpace(GetFlippedPosition(direction, selectionQuad, b.ScreenSpaceSelectionPoint)) - drawableItem.AnchorPosition;
drawableItem.Scale *= new Vector2(
direction == Direction.Horizontal ? -1 : 1, direction == Direction.Horizontal ? -1 : 1,
direction == Direction.Vertical ? -1 : 1 direction == Direction.Vertical ? -1 : 1
); );

View File

@ -9,5 +9,6 @@ namespace osu.Game.Skinning
ScoreCounter, ScoreCounter,
AccuracyCounter, AccuracyCounter,
HealthDisplay, HealthDisplay,
SongProgress,
} }
} }

View File

@ -17,6 +17,7 @@ using osu.Game.Audio;
using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Formats;
using osu.Game.IO; using osu.Game.IO;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play;
using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD;
using osuTK.Graphics; using osuTK.Graphics;
@ -349,6 +350,7 @@ namespace osu.Game.Skinning
GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreCounter)) ?? new DefaultScoreCounter(), GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreCounter)) ?? new DefaultScoreCounter(),
GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.AccuracyCounter)) ?? new DefaultAccuracyCounter(), GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.AccuracyCounter)) ?? new DefaultAccuracyCounter(),
GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.HealthDisplay)) ?? new DefaultHealthDisplay(), GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.HealthDisplay)) ?? new DefaultHealthDisplay(),
GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.SongProgress)) ?? new SongProgress(),
} }
}; };

View File

@ -5,6 +5,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Timing; using osu.Framework.Timing;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Tests.Visual namespace osu.Game.Tests.Visual
{ {
@ -22,10 +23,11 @@ namespace osu.Game.Tests.Visual
}); });
} }
protected void AddBlueprint(OverlaySelectionBlueprint blueprint) protected void AddBlueprint(HitObjectSelectionBlueprint blueprint, DrawableHitObject drawableObject)
{ {
Add(blueprint.With(d => Add(blueprint.With(d =>
{ {
d.DrawableObject = drawableObject;
d.Depth = float.MinValue; d.Depth = float.MinValue;
d.Select(); d.Select();
})); }));