mirror of
https://github.com/ppy/osu.git
synced 2024-12-14 08:33:21 +08:00
Merge branch 'master' into arbitrary-tourney-size
This commit is contained in:
commit
c8b53b6e45
@ -15,6 +15,8 @@ Rhythm is just a *click* away. The future of [osu!](https://osu.ppy.sh) and the
|
||||
|
||||
This project is under heavy development, but is in a stable state. Users are encouraged to try it out and keep it installed alongside the stable *osu!* client. It will continue to evolve to the point of eventually replacing the existing stable client as an update.
|
||||
|
||||
**IMPORTANT:** Gameplay mechanics (and other features which you may have come to know and love) are in a constant state of flux. Game balance and final quality-of-life passses come at the end of development, preceeded by experimentation and changes which may potentially **reduce playability or usability**. This is done in order to allow us to move forward as developers and designers more efficiently. If this offends you, please consider sticking to the stable releases of osu! (found on the website). We are not yet open to heated discussion over game mechanics and will not be using github as a forum for such discussions just yet.
|
||||
|
||||
We are accepting bug reports (please report with as much detail as possible and follow the existing issue templates). Feature requests are also welcome, but understand that our focus is on completing the game to feature parity before adding new features. A few resources are available as starting points to getting involved and understanding the project:
|
||||
|
||||
- Detailed release changelogs are available on the [official osu! site](https://osu.ppy.sh/home/changelog/lazer).
|
||||
|
@ -52,6 +52,6 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.512.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.519.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.528.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -8,7 +8,7 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Formats;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.IO.Archives;
|
||||
using osu.Game.Resources;
|
||||
using osu.Game.Tests.Resources;
|
||||
|
||||
namespace osu.Game.Benchmarks
|
||||
{
|
||||
@ -18,8 +18,8 @@ namespace osu.Game.Benchmarks
|
||||
|
||||
public override void SetUp()
|
||||
{
|
||||
using (var resources = new DllResourceStore(OsuResources.ResourceAssembly))
|
||||
using (var archive = resources.GetStream("Beatmaps/241526 Soleily - Renatus.osz"))
|
||||
using (var resources = new DllResourceStore(typeof(TestResources).Assembly))
|
||||
using (var archive = resources.GetStream("Resources/Archives/241526 Soleily - Renatus.osz"))
|
||||
using (var reader = new ZipArchiveReader(archive))
|
||||
reader.GetStream("Soleily - Renatus (Gamu) [Insane].osu").CopyTo(beatmapStream);
|
||||
}
|
||||
|
@ -13,6 +13,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\osu.Game.Tests\osu.Game.Tests.csproj" />
|
||||
<ProjectReference Include="..\osu.Game\osu.Game.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||
|
||||
switch (obj)
|
||||
{
|
||||
case IHasCurve curveData:
|
||||
case IHasPathWithRepeats curveData:
|
||||
return new JuiceStream
|
||||
{
|
||||
StartTime = obj.StartTime,
|
||||
@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||
LegacyLastTickOffset = (obj as IHasLegacyLastTickOffset)?.LegacyLastTickOffset ?? 0
|
||||
}.Yield();
|
||||
|
||||
case IHasEndTime endTime:
|
||||
case IHasDuration endTime:
|
||||
return new BananaShower
|
||||
{
|
||||
StartTime = obj.StartTime,
|
||||
|
@ -1,12 +1,13 @@
|
||||
// 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.Threading;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects
|
||||
{
|
||||
public class BananaShower : CatchHitObject, IHasEndTime
|
||||
public class BananaShower : CatchHitObject, IHasDuration
|
||||
{
|
||||
public override FruitVisualRepresentation VisualRepresentation => FruitVisualRepresentation.Banana;
|
||||
|
||||
@ -14,13 +15,13 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
|
||||
public override Judgement CreateJudgement() => new IgnoreJudgement();
|
||||
|
||||
protected override void CreateNestedHitObjects()
|
||||
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
||||
{
|
||||
base.CreateNestedHitObjects();
|
||||
createBananas();
|
||||
base.CreateNestedHitObjects(cancellationToken);
|
||||
createBananas(cancellationToken);
|
||||
}
|
||||
|
||||
private void createBananas()
|
||||
private void createBananas(CancellationToken cancellationToken)
|
||||
{
|
||||
double spacing = Duration;
|
||||
while (spacing > 100)
|
||||
@ -31,6 +32,8 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
|
||||
for (double i = StartTime; i <= EndTime; i += spacing)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
AddNested(new Banana
|
||||
{
|
||||
Samples = Samples,
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
@ -13,7 +14,7 @@ using osu.Game.Rulesets.Objects.Types;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects
|
||||
{
|
||||
public class JuiceStream : CatchHitObject, IHasCurve
|
||||
public class JuiceStream : CatchHitObject, IHasPathWithRepeats
|
||||
{
|
||||
/// <summary>
|
||||
/// Positional distance that results in a duration of one second, before any speed adjustments.
|
||||
@ -45,9 +46,9 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
TickDistance = scoringDistance / difficulty.SliderTickRate;
|
||||
}
|
||||
|
||||
protected override void CreateNestedHitObjects()
|
||||
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
||||
{
|
||||
base.CreateNestedHitObjects();
|
||||
base.CreateNestedHitObjects(cancellationToken);
|
||||
|
||||
var dropletSamples = Samples.Select(s => new HitSampleInfo
|
||||
{
|
||||
@ -58,7 +59,7 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
|
||||
SliderEventDescriptor? lastEvent = null;
|
||||
|
||||
foreach (var e in SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset))
|
||||
foreach (var e in SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset, cancellationToken))
|
||||
{
|
||||
// generate tiny droplets since the last point
|
||||
if (lastEvent != null)
|
||||
@ -73,6 +74,8 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
|
||||
for (double t = timeBetweenTiny; t < sinceLastTick; t += timeBetweenTiny)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
AddNested(new TinyDroplet
|
||||
{
|
||||
StartTime = t + lastEvent.Value.Time,
|
||||
@ -112,15 +115,15 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
}
|
||||
}
|
||||
|
||||
public double EndTime
|
||||
public float EndX => X + this.CurvePositionAt(1).X / CatchPlayfield.BASE_WIDTH;
|
||||
|
||||
public double Duration
|
||||
{
|
||||
get => StartTime + this.SpanCount() * Path.Distance / Velocity;
|
||||
get => this.SpanCount() * Path.Distance / Velocity;
|
||||
set => throw new System.NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed.
|
||||
}
|
||||
|
||||
public float EndX => X + this.CurvePositionAt(1).X / CatchPlayfield.BASE_WIDTH;
|
||||
|
||||
public double Duration => EndTime - StartTime;
|
||||
public double EndTime => StartTime + Duration;
|
||||
|
||||
private readonly SliderPath path = new SliderPath();
|
||||
|
||||
|
@ -8,7 +8,6 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Mania.Edit;
|
||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@ -19,8 +18,7 @@ using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests
|
||||
{
|
||||
[Cached(Type = typeof(IManiaHitObjectComposer))]
|
||||
public abstract class ManiaPlacementBlueprintTestScene : PlacementBlueprintTestScene, IManiaHitObjectComposer
|
||||
public abstract class ManiaPlacementBlueprintTestScene : PlacementBlueprintTestScene
|
||||
{
|
||||
private readonly Column column;
|
||||
|
||||
@ -48,7 +46,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
var time = column.TimeAtScreenSpacePosition(InputManager.CurrentState.Mouse.Position);
|
||||
var pos = column.ScreenSpacePositionAtTime(time);
|
||||
|
||||
return new ManiaSnapResult(pos, time, column);
|
||||
return new SnapResult(pos, time, column);
|
||||
}
|
||||
|
||||
protected override Container CreateHitObjectContainer() => new ScrollingTestContainer(ScrollingDirection.Down) { RelativeSizeAxes = Axes.Both };
|
||||
|
@ -4,15 +4,13 @@
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Rulesets.Mania.Edit;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests
|
||||
{
|
||||
[Cached(Type = typeof(IManiaHitObjectComposer))]
|
||||
public abstract class ManiaSelectionBlueprintTestScene : SelectionBlueprintTestScene, IManiaHitObjectComposer
|
||||
public abstract class ManiaSelectionBlueprintTestScene : SelectionBlueprintTestScene
|
||||
{
|
||||
[Cached(Type = typeof(IAdjustableClock))]
|
||||
private readonly IAdjustableClock clock = new StopwatchClock();
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 165 B |
Binary file not shown.
After Width: | Height: | Size: 899 B |
@ -2,23 +2,27 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Edit;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests
|
||||
{
|
||||
[Cached(typeof(IManiaHitObjectComposer))]
|
||||
public class TestSceneManiaBeatSnapGrid : EditorClockTestScene, IManiaHitObjectComposer
|
||||
public class TestSceneManiaBeatSnapGrid : EditorClockTestScene
|
||||
{
|
||||
[Cached(typeof(IScrollingInfo))]
|
||||
private ScrollingTestContainer.TestScrollingInfo scrollingInfo = new ScrollingTestContainer.TestScrollingInfo();
|
||||
@ -50,7 +54,10 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
{
|
||||
Clock = new FramedClock(new StopwatchClock())
|
||||
},
|
||||
beatSnapGrid = new ManiaBeatSnapGrid()
|
||||
new TestHitObjectComposer(Playfield)
|
||||
{
|
||||
Child = beatSnapGrid = new ManiaBeatSnapGrid()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ -67,4 +74,51 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
|
||||
public ManiaPlayfield Playfield { get; }
|
||||
}
|
||||
|
||||
public class TestHitObjectComposer : HitObjectComposer
|
||||
{
|
||||
public override Playfield Playfield { get; }
|
||||
public override IEnumerable<DrawableHitObject> HitObjects => Enumerable.Empty<DrawableHitObject>();
|
||||
public override bool CursorInPlacementArea => false;
|
||||
|
||||
public TestHitObjectComposer(Playfield playfield)
|
||||
{
|
||||
Playfield = playfield;
|
||||
}
|
||||
|
||||
public Drawable Child
|
||||
{
|
||||
set => InternalChild = value;
|
||||
}
|
||||
|
||||
public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public override float GetBeatSnapDistanceAt(double referenceTime)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public override float DurationToDistance(double referenceTime, double duration)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public override double DistanceToDuration(double referenceTime, float distance)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public override double GetSnappedDurationFromDistance(double referenceTime, float distance)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public override float GetSnappedDistanceFromDistance(double referenceTime, float distance)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -156,7 +156,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
|
||||
foreach (var obj in content.OfType<DrawableHitObject>())
|
||||
{
|
||||
if (!(obj.HitObject is IHasEndTime endTime))
|
||||
if (!(obj.HitObject is IHasDuration endTime))
|
||||
continue;
|
||||
|
||||
foreach (var nested in obj.NestedHitObjects)
|
||||
|
@ -54,7 +54,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
||||
}
|
||||
else
|
||||
{
|
||||
float percentSliderOrSpinner = (float)beatmap.HitObjects.Count(h => h is IHasEndTime) / beatmap.HitObjects.Count;
|
||||
float percentSliderOrSpinner = (float)beatmap.HitObjects.Count(h => h is IHasDuration) / beatmap.HitObjects.Count;
|
||||
if (percentSliderOrSpinner < 0.2)
|
||||
TargetColumns = 7;
|
||||
else if (percentSliderOrSpinner < 0.3 || roundedCircleSize >= 5)
|
||||
@ -175,7 +175,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
||||
break;
|
||||
}
|
||||
|
||||
case IHasEndTime endTimeData:
|
||||
case IHasDuration endTimeData:
|
||||
{
|
||||
conversion = new EndTimeObjectPatternGenerator(Random, original, beatmap, originalBeatmap);
|
||||
|
||||
@ -231,7 +231,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
||||
|
||||
var pattern = new Pattern();
|
||||
|
||||
if (HitObject is IHasEndTime endTimeData)
|
||||
if (HitObject is IHasDuration endTimeData)
|
||||
{
|
||||
pattern.Add(new HoldNote
|
||||
{
|
||||
|
@ -474,7 +474,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
/// <returns></returns>
|
||||
private IList<HitSampleInfo> sampleInfoListAt(double time)
|
||||
{
|
||||
if (!(HitObject is IHasCurve curveData))
|
||||
if (!(HitObject is IHasPathWithRepeats curveData))
|
||||
return HitObject.Samples;
|
||||
|
||||
double segmentTime = (EndTime - HitObject.StartTime) / spanCount;
|
||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
public EndTimeObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, IBeatmap originalBeatmap)
|
||||
: base(random, hitObject, beatmap, new Pattern(), originalBeatmap)
|
||||
{
|
||||
endTime = (HitObject as IHasEndTime)?.EndTime ?? 0;
|
||||
endTime = (HitObject as IHasDuration)?.EndTime ?? 0;
|
||||
}
|
||||
|
||||
public override IEnumerable<Pattern> Generate()
|
||||
|
@ -8,6 +8,7 @@ using osu.Framework.Input.Events;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
@ -20,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||
private readonly EditNotePiece tailPiece;
|
||||
|
||||
[Resolved]
|
||||
private IManiaHitObjectComposer composer { get; set; }
|
||||
private IScrollingInfo scrollingInfo { get; set; }
|
||||
|
||||
public HoldNotePlacementBlueprint()
|
||||
: base(new HoldNote())
|
||||
@ -43,6 +44,19 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||
{
|
||||
headPiece.Y = Parent.ToLocalSpace(Column.ScreenSpacePositionAtTime(HitObject.StartTime)).Y;
|
||||
tailPiece.Y = Parent.ToLocalSpace(Column.ScreenSpacePositionAtTime(HitObject.EndTime)).Y;
|
||||
|
||||
switch (scrollingInfo.Direction.Value)
|
||||
{
|
||||
case ScrollingDirection.Down:
|
||||
headPiece.Y -= headPiece.DrawHeight / 2;
|
||||
tailPiece.Y -= tailPiece.DrawHeight / 2;
|
||||
break;
|
||||
|
||||
case ScrollingDirection.Up:
|
||||
headPiece.Y += headPiece.DrawHeight / 2;
|
||||
tailPiece.Y += tailPiece.DrawHeight / 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var topPosition = new Vector2(headPiece.DrawPosition.X, Math.Min(headPiece.DrawPosition.Y, tailPiece.DrawPosition.Y));
|
||||
@ -78,9 +92,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||
}
|
||||
else
|
||||
{
|
||||
if (result is ManiaSnapResult maniaResult)
|
||||
if (result.Playfield != null)
|
||||
{
|
||||
headPiece.Width = tailPiece.Width = maniaResult.Column.DrawWidth;
|
||||
headPiece.Width = tailPiece.Width = result.Playfield.DrawWidth;
|
||||
headPiece.X = tailPiece.X = ToLocalSpace(result.ScreenSpacePosition).X;
|
||||
}
|
||||
|
||||
|
@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||
base.UpdatePosition(result);
|
||||
|
||||
if (!PlacementActive)
|
||||
Column = (result as ManiaSnapResult)?.Column;
|
||||
Column = result.Playfield as Column;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,17 +11,14 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||
{
|
||||
public class ManiaSelectionBlueprint : OverlaySelectionBlueprint
|
||||
public abstract class ManiaSelectionBlueprint : OverlaySelectionBlueprint
|
||||
{
|
||||
public new DrawableManiaHitObject DrawableObject => (DrawableManiaHitObject)base.DrawableObject;
|
||||
|
||||
[Resolved]
|
||||
private IScrollingInfo scrollingInfo { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private IManiaHitObjectComposer composer { get; set; }
|
||||
|
||||
public ManiaSelectionBlueprint(DrawableHitObject drawableObject)
|
||||
protected ManiaSelectionBlueprint(DrawableHitObject drawableObject)
|
||||
: base(drawableObject)
|
||||
{
|
||||
RelativeSizeAxes = Axes.None;
|
||||
|
@ -26,9 +26,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||
{
|
||||
base.UpdatePosition(result);
|
||||
|
||||
if (result is ManiaSnapResult maniaResult)
|
||||
if (result.Playfield != null)
|
||||
{
|
||||
piece.Width = maniaResult.Column.DrawWidth;
|
||||
piece.Width = result.Playfield.DrawWidth;
|
||||
piece.Position = ToLocalSpace(result.ScreenSpacePosition);
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +0,0 @@
|
||||
// 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.Game.Rulesets.Mania.UI;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Edit
|
||||
{
|
||||
public interface IManiaHitObjectComposer
|
||||
{
|
||||
ManiaPlayfield Playfield { get; }
|
||||
}
|
||||
}
|
@ -11,6 +11,8 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
@ -63,9 +65,9 @@ namespace osu.Game.Rulesets.Mania.Edit
|
||||
private (double start, double end)? selectionTimeRange;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(IManiaHitObjectComposer composer)
|
||||
private void load(HitObjectComposer composer)
|
||||
{
|
||||
foreach (var stage in composer.Playfield.Stages)
|
||||
foreach (var stage in ((ManiaPlayfield)composer.Playfield).Stages)
|
||||
{
|
||||
foreach (var column in stage.Columns)
|
||||
{
|
||||
|
@ -9,9 +9,11 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Input;
|
||||
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Screens.Edit.Compose.Components;
|
||||
@ -19,8 +21,7 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Edit
|
||||
{
|
||||
[Cached(Type = typeof(IManiaHitObjectComposer))]
|
||||
public class ManiaHitObjectComposer : HitObjectComposer<ManiaHitObject>, IManiaHitObjectComposer
|
||||
public class ManiaHitObjectComposer : HitObjectComposer<ManiaHitObject>
|
||||
{
|
||||
private DrawableManiaEditRuleset drawableRuleset;
|
||||
private ManiaBeatSnapGrid beatSnapGrid;
|
||||
@ -49,28 +50,35 @@ namespace osu.Game.Rulesets.Mania.Edit
|
||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||
=> dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
||||
|
||||
public ManiaPlayfield Playfield => ((ManiaPlayfield)drawableRuleset.Playfield);
|
||||
public new ManiaPlayfield Playfield => ((ManiaPlayfield)drawableRuleset.Playfield);
|
||||
|
||||
public IScrollingInfo ScrollingInfo => drawableRuleset.ScrollingInfo;
|
||||
|
||||
protected override Playfield PlayfieldAtScreenSpacePosition(Vector2 screenSpacePosition) =>
|
||||
Playfield.GetColumnByPosition(screenSpacePosition);
|
||||
|
||||
public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition)
|
||||
{
|
||||
var column = Playfield.GetColumnByPosition(screenSpacePosition);
|
||||
var result = base.SnapScreenSpacePositionToValidTime(screenSpacePosition);
|
||||
|
||||
if (column == null)
|
||||
return new SnapResult(screenSpacePosition, null);
|
||||
switch (ScrollingInfo.Direction.Value)
|
||||
{
|
||||
case ScrollingDirection.Down:
|
||||
result.ScreenSpacePosition -= new Vector2(0, getNoteHeight() / 2);
|
||||
break;
|
||||
|
||||
double targetTime = column.TimeAtScreenSpacePosition(screenSpacePosition);
|
||||
case ScrollingDirection.Up:
|
||||
result.ScreenSpacePosition += new Vector2(0, getNoteHeight() / 2);
|
||||
break;
|
||||
}
|
||||
|
||||
// apply beat snapping
|
||||
targetTime = BeatSnapProvider.SnapTime(targetTime);
|
||||
|
||||
// convert back to screen space
|
||||
screenSpacePosition = column.ScreenSpacePositionAtTime(targetTime);
|
||||
|
||||
return new ManiaSnapResult(screenSpacePosition, targetTime, column);
|
||||
return result;
|
||||
}
|
||||
|
||||
private float getNoteHeight() =>
|
||||
Playfield.GetColumn(0).ToScreenSpace(new Vector2(DefaultNotePiece.NOTE_HEIGHT)).Y -
|
||||
Playfield.GetColumn(0).ToScreenSpace(Vector2.Zero).Y;
|
||||
|
||||
protected override DrawableRuleset<ManiaHitObject> CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods = null)
|
||||
{
|
||||
drawableRuleset = new DrawableManiaEditRuleset(ruleset, beatmap, mods);
|
||||
@ -81,7 +89,8 @@ namespace osu.Game.Rulesets.Mania.Edit
|
||||
return drawableRuleset;
|
||||
}
|
||||
|
||||
protected override ComposeBlueprintContainer CreateBlueprintContainer() => new ManiaBlueprintContainer(drawableRuleset.Playfield.AllHitObjects);
|
||||
protected override ComposeBlueprintContainer CreateBlueprintContainer(IEnumerable<DrawableHitObject> hitObjects)
|
||||
=> new ManiaBlueprintContainer(hitObjects);
|
||||
|
||||
protected override IReadOnlyList<HitObjectCompositionTool> CompositionTools => new HitObjectCompositionTool[]
|
||||
{
|
||||
|
@ -4,6 +4,7 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Mania.Edit.Blueprints;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
@ -17,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Edit
|
||||
private IScrollingInfo scrollingInfo { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private IManiaHitObjectComposer composer { get; set; }
|
||||
private HitObjectComposer composer { get; set; }
|
||||
|
||||
public override bool HandleMovement(MoveSelectionEvent moveEvent)
|
||||
{
|
||||
@ -31,7 +32,9 @@ namespace osu.Game.Rulesets.Mania.Edit
|
||||
|
||||
private void performColumnMovement(int lastColumn, MoveSelectionEvent moveEvent)
|
||||
{
|
||||
var currentColumn = composer.Playfield.GetColumnByPosition(moveEvent.ScreenSpacePosition);
|
||||
var maniaPlayfield = ((ManiaHitObjectComposer)composer).Playfield;
|
||||
|
||||
var currentColumn = maniaPlayfield.GetColumnByPosition(moveEvent.ScreenSpacePosition);
|
||||
if (currentColumn == null)
|
||||
return;
|
||||
|
||||
@ -50,7 +53,7 @@ namespace osu.Game.Rulesets.Mania.Edit
|
||||
maxColumn = obj.Column;
|
||||
}
|
||||
|
||||
columnDelta = Math.Clamp(columnDelta, -minColumn, composer.Playfield.TotalColumns - 1 - maxColumn);
|
||||
columnDelta = Math.Clamp(columnDelta, -minColumn, maniaPlayfield.TotalColumns - 1 - maxColumn);
|
||||
|
||||
foreach (var obj in SelectedHitObjects.OfType<ManiaHitObject>())
|
||||
obj.Column += columnDelta;
|
||||
|
@ -1,20 +0,0 @@
|
||||
// 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.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Edit
|
||||
{
|
||||
public class ManiaSnapResult : SnapResult
|
||||
{
|
||||
public readonly Column Column;
|
||||
|
||||
public ManiaSnapResult(Vector2 screenSpacePosition, double time, Column column)
|
||||
: base(screenSpacePosition, time)
|
||||
{
|
||||
Column = column;
|
||||
}
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
@ -14,7 +15,7 @@ namespace osu.Game.Rulesets.Mania.Objects
|
||||
/// <summary>
|
||||
/// Represents a hit object which requires pressing, holding, and releasing a key.
|
||||
/// </summary>
|
||||
public class HoldNote : ManiaHitObject, IHasEndTime
|
||||
public class HoldNote : ManiaHitObject, IHasDuration
|
||||
{
|
||||
public double EndTime
|
||||
{
|
||||
@ -91,11 +92,11 @@ namespace osu.Game.Rulesets.Mania.Objects
|
||||
tickSpacing = timingPoint.BeatLength / difficulty.SliderTickRate;
|
||||
}
|
||||
|
||||
protected override void CreateNestedHitObjects()
|
||||
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
||||
{
|
||||
base.CreateNestedHitObjects();
|
||||
base.CreateNestedHitObjects(cancellationToken);
|
||||
|
||||
createTicks();
|
||||
createTicks(cancellationToken);
|
||||
|
||||
AddNested(Head = new Note
|
||||
{
|
||||
@ -112,13 +113,15 @@ namespace osu.Game.Rulesets.Mania.Objects
|
||||
});
|
||||
}
|
||||
|
||||
private void createTicks()
|
||||
private void createTicks(CancellationToken cancellationToken)
|
||||
{
|
||||
if (tickSpacing == 0)
|
||||
return;
|
||||
|
||||
for (double t = StartTime + tickSpacing; t <= EndTime - tickSpacing; t += tickSpacing)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
AddNested(new HoldNoteTick
|
||||
{
|
||||
StartTime = t,
|
||||
|
@ -52,10 +52,10 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
||||
base.Update();
|
||||
|
||||
if (leftSprite?.Height > 0)
|
||||
leftSprite.Scale = new Vector2(DrawHeight / leftSprite.Height);
|
||||
leftSprite.Scale = new Vector2(1, DrawHeight / leftSprite.Height);
|
||||
|
||||
if (rightSprite?.Height > 0)
|
||||
rightSprite.Scale = new Vector2(DrawHeight / rightSprite.Height);
|
||||
rightSprite.Scale = new Vector2(1, DrawHeight / rightSprite.Height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,6 @@ using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.UI
|
||||
{
|
||||
@ -143,54 +142,5 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos)
|
||||
// This probably shouldn't exist as is, but the columns in the stage are separated by a 1px border
|
||||
=> DrawRectangle.Inflate(new Vector2(Stage.COLUMN_SPACING / 2, 0)).Contains(ToLocalSpace(screenSpacePos));
|
||||
|
||||
/// <summary>
|
||||
/// Given a time, return the screen space position within this column.
|
||||
/// </summary>
|
||||
public Vector2 ScreenSpacePositionAtTime(double time)
|
||||
{
|
||||
var pos = ScrollingInfo.Algorithm.PositionAt(time, Time.Current, ScrollingInfo.TimeRange.Value, HitObjectContainer.DrawHeight);
|
||||
|
||||
switch (ScrollingInfo.Direction.Value)
|
||||
{
|
||||
case ScrollingDirection.Down:
|
||||
// We're dealing with screen coordinates in which the position decreases towards the centre of the screen resulting in an increase in start time.
|
||||
// The scrolling algorithm instead assumes a top anchor meaning an increase in time corresponds to an increase in position,
|
||||
// so when scrolling downwards the coordinates need to be flipped.
|
||||
pos = HitObjectContainer.DrawHeight - pos;
|
||||
|
||||
// Blueprints are centred on the mouse position, such that the hitobject position is anchored at the top or bottom of the blueprint depending on the scroll direction.
|
||||
pos -= DefaultNotePiece.NOTE_HEIGHT / 2;
|
||||
break;
|
||||
|
||||
case ScrollingDirection.Up:
|
||||
pos += DefaultNotePiece.NOTE_HEIGHT / 2;
|
||||
break;
|
||||
}
|
||||
|
||||
return HitObjectContainer.ToScreenSpace(new Vector2(HitObjectContainer.DrawWidth / 2, pos));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a position in screen space, return the time within this column.
|
||||
/// </summary>
|
||||
public double TimeAtScreenSpacePosition(Vector2 screenSpacePosition)
|
||||
{
|
||||
// convert to local space of column so we can snap and fetch correct location.
|
||||
Vector2 localPosition = HitObjectContainer.ToLocalSpace(screenSpacePosition);
|
||||
|
||||
switch (ScrollingInfo.Direction.Value)
|
||||
{
|
||||
case ScrollingDirection.Down:
|
||||
// as above
|
||||
localPosition.Y = HitObjectContainer.DrawHeight - localPosition.Y;
|
||||
break;
|
||||
}
|
||||
|
||||
// offset for the fact that blueprints are centered, as above.
|
||||
localPosition.Y -= DefaultNotePiece.NOTE_HEIGHT / 2;
|
||||
|
||||
return ScrollingInfo.Algorithm.TimeAt(localPosition.Y, Time.Current, ScrollingInfo.TimeRange.Value, HitObjectContainer.DrawHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
|
||||
switch (original)
|
||||
{
|
||||
case IHasCurve curveData:
|
||||
case IHasPathWithRepeats curveData:
|
||||
return new Slider
|
||||
{
|
||||
StartTime = original.StartTime,
|
||||
@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
TickDistanceMultiplier = beatmap.BeatmapInfo.BeatmapVersion < 8 ? 1f / beatmap.ControlPointInfo.DifficultyPointAt(original.StartTime).SpeedMultiplier : 1
|
||||
}.Yield();
|
||||
|
||||
case IHasEndTime endTimeData:
|
||||
case IHasDuration endTimeData:
|
||||
return new Spinner
|
||||
{
|
||||
StartTime = original.StartTime,
|
||||
|
@ -12,6 +12,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints
|
||||
{
|
||||
protected new T HitObject => (T)DrawableObject.HitObject;
|
||||
|
||||
protected override bool AlwaysShowWhenSelected => true;
|
||||
|
||||
protected OsuSelectionBlueprint(DrawableHitObject drawableObject)
|
||||
: base(drawableObject)
|
||||
{
|
||||
|
@ -4,14 +4,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Caching;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Edit.Tools;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Edit.Compose.Components;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
@ -32,9 +38,81 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
new SpinnerCompositionTool()
|
||||
};
|
||||
|
||||
protected override ComposeBlueprintContainer CreateBlueprintContainer() => new OsuBlueprintContainer(HitObjects);
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
LayerBelowRuleset.Add(distanceSnapGridContainer = new Container { RelativeSizeAxes = Axes.Both });
|
||||
|
||||
protected override DistanceSnapGrid CreateDistanceSnapGrid(IEnumerable<HitObject> selectedHitObjects)
|
||||
EditorBeatmap.SelectedHitObjects.CollectionChanged += (_, __) => updateDistanceSnapGrid();
|
||||
EditorBeatmap.PlacementObject.ValueChanged += _ => updateDistanceSnapGrid();
|
||||
}
|
||||
|
||||
protected override ComposeBlueprintContainer CreateBlueprintContainer(IEnumerable<DrawableHitObject> hitObjects)
|
||||
=> new OsuBlueprintContainer(hitObjects);
|
||||
|
||||
private DistanceSnapGrid distanceSnapGrid;
|
||||
private Container distanceSnapGridContainer;
|
||||
|
||||
private readonly Cached distanceSnapGridCache = new Cached();
|
||||
private double? lastDistanceSnapGridTime;
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (!(BlueprintContainer.CurrentTool is SelectTool))
|
||||
{
|
||||
if (EditorClock.CurrentTime != lastDistanceSnapGridTime)
|
||||
{
|
||||
distanceSnapGridCache.Invalidate();
|
||||
lastDistanceSnapGridTime = EditorClock.CurrentTime;
|
||||
}
|
||||
|
||||
if (!distanceSnapGridCache.IsValid)
|
||||
updateDistanceSnapGrid();
|
||||
}
|
||||
}
|
||||
|
||||
public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition)
|
||||
{
|
||||
if (distanceSnapGrid == null)
|
||||
return base.SnapScreenSpacePositionToValidTime(screenSpacePosition);
|
||||
|
||||
(Vector2 pos, double time) = distanceSnapGrid.GetSnappedPosition(distanceSnapGrid.ToLocalSpace(screenSpacePosition));
|
||||
|
||||
return new SnapResult(distanceSnapGrid.ToScreenSpace(pos), time, PlayfieldAtScreenSpacePosition(screenSpacePosition));
|
||||
}
|
||||
|
||||
private void updateDistanceSnapGrid()
|
||||
{
|
||||
distanceSnapGridContainer.Clear();
|
||||
distanceSnapGridCache.Invalidate();
|
||||
|
||||
switch (BlueprintContainer.CurrentTool)
|
||||
{
|
||||
case SelectTool _:
|
||||
if (!EditorBeatmap.SelectedHitObjects.Any())
|
||||
return;
|
||||
|
||||
distanceSnapGrid = createDistanceSnapGrid(EditorBeatmap.SelectedHitObjects);
|
||||
break;
|
||||
|
||||
default:
|
||||
if (!CursorInPlacementArea)
|
||||
return;
|
||||
|
||||
distanceSnapGrid = createDistanceSnapGrid(Enumerable.Empty<HitObject>());
|
||||
break;
|
||||
}
|
||||
|
||||
if (distanceSnapGrid != null)
|
||||
{
|
||||
distanceSnapGridContainer.Add(distanceSnapGrid);
|
||||
distanceSnapGridCache.Validate();
|
||||
}
|
||||
}
|
||||
|
||||
private DistanceSnapGrid createDistanceSnapGrid(IEnumerable<HitObject> selectedHitObjects)
|
||||
{
|
||||
if (BlueprintContainer.CurrentTool is SpinnerCompositionTool)
|
||||
return null;
|
||||
@ -42,7 +120,8 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
var objects = selectedHitObjects.ToList();
|
||||
|
||||
if (objects.Count == 0)
|
||||
return createGrid(h => h.StartTime <= EditorClock.CurrentTime);
|
||||
// use accurate time value to give more instantaneous feedback to the user.
|
||||
return createGrid(h => h.StartTime <= EditorClock.CurrentTimeAccurate);
|
||||
|
||||
double minTime = objects.Min(h => h.StartTime);
|
||||
return createGrid(h => h.StartTime < minTime, objects.Count + 1);
|
||||
|
@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
break;
|
||||
|
||||
// already hit or beyond the hittable end time.
|
||||
if (h.IsHit || (h.HitObject is IHasEndTime hasEnd && time > hasEnd.EndTime))
|
||||
if (h.IsHit || (h.HitObject is IHasDuration hasEnd && time > hasEnd.EndTime))
|
||||
continue;
|
||||
|
||||
switch (h)
|
||||
|
@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
}
|
||||
|
||||
// Keep wiggling sliders and spinners for their duration
|
||||
if (!(osuObject is IHasEndTime endTime))
|
||||
if (!(osuObject is IHasDuration endTime))
|
||||
return;
|
||||
|
||||
amountWiggles = (int)(endTime.Duration / wiggle_duration);
|
||||
|
@ -6,6 +6,7 @@ using osu.Game.Rulesets.Objects.Types;
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using osu.Framework.Caching;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
@ -16,16 +17,16 @@ using osu.Game.Rulesets.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects
|
||||
{
|
||||
public class Slider : OsuHitObject, IHasCurve
|
||||
public class Slider : OsuHitObject, IHasPathWithRepeats
|
||||
{
|
||||
public double EndTime
|
||||
public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity;
|
||||
|
||||
public double Duration
|
||||
{
|
||||
get => StartTime + this.SpanCount() * Path.Distance / Velocity;
|
||||
get => EndTime - StartTime;
|
||||
set => throw new System.NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed.
|
||||
}
|
||||
|
||||
public double Duration => EndTime - StartTime;
|
||||
|
||||
private readonly Cached<Vector2> endPositionCache = new Cached<Vector2>();
|
||||
|
||||
public override Vector2 EndPosition => endPositionCache.IsValid ? endPositionCache.Value : endPositionCache.Value = Position + this.CurvePositionAt(1);
|
||||
@ -133,12 +134,12 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
TickDistance = scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier;
|
||||
}
|
||||
|
||||
protected override void CreateNestedHitObjects()
|
||||
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
||||
{
|
||||
base.CreateNestedHitObjects();
|
||||
base.CreateNestedHitObjects(cancellationToken);
|
||||
|
||||
foreach (var e in
|
||||
SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset))
|
||||
SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset, cancellationToken))
|
||||
{
|
||||
switch (e.Type)
|
||||
{
|
||||
|
@ -11,7 +11,7 @@ using osu.Game.Rulesets.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects
|
||||
{
|
||||
public class Spinner : OsuHitObject, IHasEndTime
|
||||
public class Spinner : OsuHitObject, IHasDuration
|
||||
{
|
||||
public double EndTime
|
||||
{
|
||||
|
@ -8,7 +8,7 @@ using osu.Game.Rulesets.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Scoring
|
||||
{
|
||||
internal class OsuScoreProcessor : ScoreProcessor
|
||||
public class OsuScoreProcessor : ScoreProcessor
|
||||
{
|
||||
protected override JudgementResult CreateResult(HitObject hitObject, Judgement judgement) => new OsuJudgementResult(hitObject, judgement);
|
||||
|
||||
|
@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
||||
|
||||
if (!isForCurrentRuleset && tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength)
|
||||
{
|
||||
List<IList<HitSampleInfo>> allSamples = obj is IHasCurve curveData ? curveData.NodeSamples : new List<IList<HitSampleInfo>>(new[] { samples });
|
||||
List<IList<HitSampleInfo>> allSamples = obj is IHasPathWithRepeats curveData ? curveData.NodeSamples : new List<IList<HitSampleInfo>>(new[] { samples });
|
||||
|
||||
int i = 0;
|
||||
|
||||
@ -150,7 +150,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
||||
break;
|
||||
}
|
||||
|
||||
case IHasEndTime endTimeData:
|
||||
case IHasDuration endTimeData:
|
||||
{
|
||||
double hitMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, 3, 5, 7.5) * swell_hit_multiplier;
|
||||
|
||||
|
@ -237,7 +237,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
|
||||
case ArmedState.Miss:
|
||||
case ArmedState.Hit:
|
||||
using (BeginAbsoluteSequence(Time.Current, true))
|
||||
using (BeginDelayedSequence(HitObject.Duration, true))
|
||||
{
|
||||
this.FadeOut(transition_duration, Easing.Out);
|
||||
bodyContainer.ScaleTo(1.4f, transition_duration);
|
||||
|
@ -3,8 +3,7 @@
|
||||
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Audio;
|
||||
using System.Threading;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
@ -16,7 +15,7 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Objects
|
||||
{
|
||||
public class DrumRoll : TaikoHitObject, IHasCurve
|
||||
public class DrumRoll : TaikoHitObject, IHasPath
|
||||
{
|
||||
/// <summary>
|
||||
/// Drum roll distance that results in a duration of 1 speed-adjusted beat length.
|
||||
@ -73,17 +72,17 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
||||
overallDifficulty = difficulty.OverallDifficulty;
|
||||
}
|
||||
|
||||
protected override void CreateNestedHitObjects()
|
||||
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
||||
{
|
||||
createTicks();
|
||||
createTicks(cancellationToken);
|
||||
|
||||
RequiredGoodHits = NestedHitObjects.Count * Math.Min(0.15, 0.05 + 0.10 / 6 * overallDifficulty);
|
||||
RequiredGreatHits = NestedHitObjects.Count * Math.Min(0.30, 0.10 + 0.20 / 6 * overallDifficulty);
|
||||
|
||||
base.CreateNestedHitObjects();
|
||||
base.CreateNestedHitObjects(cancellationToken);
|
||||
}
|
||||
|
||||
private void createTicks()
|
||||
private void createTicks(CancellationToken cancellationToken)
|
||||
{
|
||||
if (tickSpacing == 0)
|
||||
return;
|
||||
@ -92,6 +91,8 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
||||
|
||||
for (double t = StartTime; t < EndTime + tickSpacing / 2; t += tickSpacing)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
AddNested(new DrumRollTick
|
||||
{
|
||||
FirstTick = first,
|
||||
@ -112,11 +113,7 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
||||
|
||||
double IHasDistance.Distance => Duration * Velocity;
|
||||
|
||||
int IHasRepeats.RepeatCount { get => 0; set { } }
|
||||
|
||||
List<IList<HitSampleInfo>> IHasRepeats.NodeSamples => new List<IList<HitSampleInfo>>();
|
||||
|
||||
SliderPath IHasCurve.Path
|
||||
SliderPath IHasPath.Path
|
||||
=> new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(1) }, ((IHasDistance)this).Distance / TaikoBeatmapConverter.LEGACY_VELOCITY_MULTIPLIER);
|
||||
|
||||
#endregion
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
@ -9,7 +10,7 @@ using osu.Game.Rulesets.Taiko.Judgements;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Objects
|
||||
{
|
||||
public class Swell : TaikoHitObject, IHasEndTime
|
||||
public class Swell : TaikoHitObject, IHasDuration
|
||||
{
|
||||
public double EndTime
|
||||
{
|
||||
@ -29,12 +30,15 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
||||
set => throw new NotSupportedException($"{nameof(Swell)} cannot be a strong hitobject.");
|
||||
}
|
||||
|
||||
protected override void CreateNestedHitObjects()
|
||||
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
||||
{
|
||||
base.CreateNestedHitObjects();
|
||||
base.CreateNestedHitObjects(cancellationToken);
|
||||
|
||||
for (int i = 0; i < RequiredHits; i++)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
AddNested(new SwellTick());
|
||||
}
|
||||
}
|
||||
|
||||
public override Judgement CreateJudgement() => new TaikoSwellJudgement();
|
||||
|
@ -1,6 +1,7 @@
|
||||
// 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.Threading;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
@ -32,9 +33,9 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
||||
/// </summary>
|
||||
public virtual bool IsStrong { get; set; }
|
||||
|
||||
protected override void CreateNestedHitObjects()
|
||||
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
||||
{
|
||||
base.CreateNestedHitObjects();
|
||||
base.CreateNestedHitObjects(cancellationToken);
|
||||
|
||||
if (IsStrong)
|
||||
AddNested(new StrongHitObject { StartTime = this.GetEndTime() });
|
||||
|
@ -365,7 +365,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
{
|
||||
var hitObjects = decoder.Decode(stream).HitObjects;
|
||||
|
||||
var curveData = hitObjects[0] as IHasCurve;
|
||||
var curveData = hitObjects[0] as IHasPathWithRepeats;
|
||||
var positionData = hitObjects[0] as IHasPosition;
|
||||
|
||||
Assert.IsNotNull(positionData);
|
||||
|
@ -95,7 +95,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
{
|
||||
var beatmap = decodeAsJson(normal);
|
||||
|
||||
var curveData = beatmap.HitObjects[0] as IHasCurve;
|
||||
var curveData = beatmap.HitObjects[0] as IHasPathWithRepeats;
|
||||
var positionData = beatmap.HitObjects[0] as IHasPosition;
|
||||
|
||||
Assert.IsNotNull(positionData);
|
||||
|
@ -156,7 +156,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
var manager = osu.Dependencies.Get<BeatmapManager>();
|
||||
|
||||
// ReSharper disable once AccessToModifiedClosure
|
||||
manager.ItemAdded.BindValueChanged(_ => Interlocked.Increment(ref itemAddRemoveFireCount));
|
||||
manager.ItemUpdated.BindValueChanged(_ => Interlocked.Increment(ref itemAddRemoveFireCount));
|
||||
manager.ItemRemoved.BindValueChanged(_ => Interlocked.Increment(ref itemAddRemoveFireCount));
|
||||
|
||||
var imported = await LoadOszIntoOsu(osu);
|
||||
@ -166,7 +166,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
imported.Hash += "-changed";
|
||||
manager.Update(imported);
|
||||
|
||||
Assert.AreEqual(0, itemAddRemoveFireCount -= 2);
|
||||
Assert.AreEqual(0, itemAddRemoveFireCount -= 1);
|
||||
|
||||
checkBeatmapSetCount(osu, 1);
|
||||
checkBeatmapCount(osu, 12);
|
||||
|
@ -16,7 +16,7 @@ namespace osu.Game.Tests.Beatmaps
|
||||
[Test]
|
||||
public void TestSingleSpan()
|
||||
{
|
||||
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, null).ToArray();
|
||||
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, null, default).ToArray();
|
||||
|
||||
Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head));
|
||||
Assert.That(events[0].Time, Is.EqualTo(start_time));
|
||||
@ -31,7 +31,7 @@ namespace osu.Game.Tests.Beatmaps
|
||||
[Test]
|
||||
public void TestRepeat()
|
||||
{
|
||||
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 2, null).ToArray();
|
||||
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 2, null, default).ToArray();
|
||||
|
||||
Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head));
|
||||
Assert.That(events[0].Time, Is.EqualTo(start_time));
|
||||
@ -52,7 +52,7 @@ namespace osu.Game.Tests.Beatmaps
|
||||
[Test]
|
||||
public void TestNonEvenTicks()
|
||||
{
|
||||
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, 300, span_duration, 2, null).ToArray();
|
||||
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, 300, span_duration, 2, null, default).ToArray();
|
||||
|
||||
Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head));
|
||||
Assert.That(events[0].Time, Is.EqualTo(start_time));
|
||||
@ -85,7 +85,7 @@ namespace osu.Game.Tests.Beatmaps
|
||||
[Test]
|
||||
public void TestLegacyLastTickOffset()
|
||||
{
|
||||
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, 100).ToArray();
|
||||
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, 100, default).ToArray();
|
||||
|
||||
Assert.That(events[2].Type, Is.EqualTo(SliderEventType.LegacyLastTick));
|
||||
Assert.That(events[2].Time, Is.EqualTo(900));
|
||||
@ -97,7 +97,7 @@ namespace osu.Game.Tests.Beatmaps
|
||||
const double velocity = 5;
|
||||
const double min_distance = velocity * 10;
|
||||
|
||||
var events = SliderEventGenerator.Generate(start_time, span_duration, velocity, velocity, span_duration, 2, 0).ToArray();
|
||||
var events = SliderEventGenerator.Generate(start_time, span_duration, velocity, velocity, span_duration, 2, 0, default).ToArray();
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
|
@ -2,12 +2,12 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
@ -18,7 +18,6 @@ namespace osu.Game.Tests.Gameplay
|
||||
[HeadlessTest]
|
||||
public class TestSceneDrainingHealthProcessor : OsuTestScene
|
||||
{
|
||||
private Bindable<bool> breakTime;
|
||||
private HealthProcessor processor;
|
||||
private ManualClock clock;
|
||||
|
||||
@ -41,6 +40,64 @@ namespace osu.Game.Tests.Gameplay
|
||||
assertHealthEqualTo(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHealthDrainBetweenBreakAndObjects()
|
||||
{
|
||||
createProcessor(createBeatmap(0, 2000, new BreakPeriod(325, 375)));
|
||||
|
||||
// 275 300 325 350 375 400 425
|
||||
// hitobjects o o
|
||||
// break [-------------]
|
||||
// no drain [---------------------------]
|
||||
|
||||
setTime(285);
|
||||
setHealth(1);
|
||||
|
||||
setTime(295);
|
||||
assertHealthNotEqualTo(1);
|
||||
|
||||
setTime(305);
|
||||
setHealth(1);
|
||||
|
||||
setTime(315);
|
||||
assertHealthEqualTo(1);
|
||||
|
||||
setTime(365);
|
||||
assertHealthEqualTo(1);
|
||||
|
||||
setTime(395);
|
||||
assertHealthEqualTo(1);
|
||||
|
||||
setTime(425);
|
||||
assertHealthNotEqualTo(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHealthDrainDuringMaximalBreak()
|
||||
{
|
||||
createProcessor(createBeatmap(0, 2000, new BreakPeriod(300, 400)));
|
||||
|
||||
// 275 300 325 350 375 400 425
|
||||
// hitobjects o o
|
||||
// break [---------------------------]
|
||||
// no drain [---------------------------]
|
||||
|
||||
setTime(285);
|
||||
setHealth(1);
|
||||
|
||||
setTime(295);
|
||||
assertHealthNotEqualTo(1);
|
||||
|
||||
setTime(305);
|
||||
setHealth(1);
|
||||
|
||||
setTime(395);
|
||||
assertHealthEqualTo(1);
|
||||
|
||||
setTime(425);
|
||||
assertHealthNotEqualTo(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHealthNotDrainedAfterGameplayEnd()
|
||||
{
|
||||
@ -54,18 +111,6 @@ namespace osu.Game.Tests.Gameplay
|
||||
assertHealthEqualTo(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHealthNotDrainedDuringBreak()
|
||||
{
|
||||
createProcessor(createBeatmap(0, 2000));
|
||||
setBreak(true);
|
||||
|
||||
setTime(700);
|
||||
assertHealthEqualTo(1);
|
||||
setTime(900);
|
||||
assertHealthEqualTo(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHealthDrainedDuringGameplay()
|
||||
{
|
||||
@ -112,30 +157,31 @@ namespace osu.Game.Tests.Gameplay
|
||||
assertHealthNotEqualTo(1);
|
||||
}
|
||||
|
||||
private Beatmap createBeatmap(double startTime, double endTime)
|
||||
private Beatmap createBeatmap(double startTime, double endTime, params BreakPeriod[] breaks)
|
||||
{
|
||||
var beatmap = new Beatmap
|
||||
{
|
||||
BeatmapInfo = { BaseDifficulty = { DrainRate = 5 } },
|
||||
BeatmapInfo = { BaseDifficulty = { DrainRate = 10 } },
|
||||
};
|
||||
|
||||
for (double time = startTime; time <= endTime; time += 100)
|
||||
{
|
||||
beatmap.HitObjects.Add(new JudgeableHitObject { StartTime = time });
|
||||
}
|
||||
|
||||
beatmap.Breaks.AddRange(breaks);
|
||||
|
||||
return beatmap;
|
||||
}
|
||||
|
||||
private void createProcessor(Beatmap beatmap) => AddStep("create processor", () =>
|
||||
{
|
||||
breakTime = new Bindable<bool>();
|
||||
|
||||
Child = processor = new DrainingHealthProcessor(beatmap.HitObjects[0].StartTime).With(d =>
|
||||
{
|
||||
d.RelativeSizeAxes = Axes.Both;
|
||||
d.Clock = new FramedClock(clock = new ManualClock());
|
||||
});
|
||||
|
||||
processor.IsBreakTime.BindTo(breakTime);
|
||||
processor.ApplyBeatmap(beatmap);
|
||||
});
|
||||
|
||||
@ -143,8 +189,6 @@ namespace osu.Game.Tests.Gameplay
|
||||
|
||||
private void setHealth(double health) => AddStep($"set health = {health}", () => processor.Health.Value = health);
|
||||
|
||||
private void setBreak(bool enabled) => AddStep($"{(enabled ? "enable" : "disable")} break", () => breakTime.Value = enabled);
|
||||
|
||||
private void assertHealthEqualTo(double value)
|
||||
=> AddAssert($"health = {value}", () => Precision.AlmostEquals(value, processor.Health.Value, 0.0001f));
|
||||
|
||||
|
@ -68,7 +68,7 @@ namespace osu.Game.Tests.Visual.Background
|
||||
/// Check if <see cref="PlayerLoader"/> properly triggers the visual settings preview when a user hovers over the visual settings panel.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void PlayerLoaderSettingsHoverTest()
|
||||
public void TestPlayerLoaderSettingsHover()
|
||||
{
|
||||
setupUserSettings();
|
||||
AddStep("Start player loader", () => songSelect.Push(playerLoader = new TestPlayerLoader(player = new LoadBlockingTestPlayer { BlockLoad = true })));
|
||||
@ -79,11 +79,9 @@ namespace osu.Game.Tests.Visual.Background
|
||||
InputManager.MoveMouseTo(playerLoader.ScreenPos);
|
||||
InputManager.MoveMouseTo(playerLoader.VisualSettingsPos);
|
||||
});
|
||||
waitForDim();
|
||||
AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
|
||||
AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
|
||||
AddStep("Stop background preview", () => InputManager.MoveMouseTo(playerLoader.ScreenPos));
|
||||
waitForDim();
|
||||
AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && playerLoader.IsBlurCorrect());
|
||||
AddUntilStep("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && playerLoader.IsBlurCorrect());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -92,20 +90,19 @@ namespace osu.Game.Tests.Visual.Background
|
||||
/// We need to check that in this scenario, the dim and blur is still properly applied after entering player.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void PlayerLoaderTransitionTest()
|
||||
public void TestPlayerLoaderTransition()
|
||||
{
|
||||
performFullSetup();
|
||||
AddStep("Trigger hover event", () => playerLoader.TriggerOnHover());
|
||||
AddAssert("Background retained from song select", () => songSelect.IsBackgroundCurrent());
|
||||
waitForDim();
|
||||
AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
|
||||
AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Make sure the background is fully invisible (Alpha == 0) when the background should be disabled by the storyboard.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void StoryboardBackgroundVisibilityTest()
|
||||
public void TestStoryboardBackgroundVisibility()
|
||||
{
|
||||
performFullSetup();
|
||||
createFakeStoryboard();
|
||||
@ -114,52 +111,46 @@ namespace osu.Game.Tests.Visual.Background
|
||||
player.ReplacesBackground.Value = true;
|
||||
player.StoryboardEnabled.Value = true;
|
||||
});
|
||||
waitForDim();
|
||||
AddAssert("Background is invisible, storyboard is visible", () => songSelect.IsBackgroundInvisible() && player.IsStoryboardVisible);
|
||||
AddUntilStep("Background is invisible, storyboard is visible", () => songSelect.IsBackgroundInvisible() && player.IsStoryboardVisible);
|
||||
AddStep("Disable Storyboard", () =>
|
||||
{
|
||||
player.ReplacesBackground.Value = false;
|
||||
player.StoryboardEnabled.Value = false;
|
||||
});
|
||||
waitForDim();
|
||||
AddAssert("Background is visible, storyboard is invisible", () => songSelect.IsBackgroundVisible() && !player.IsStoryboardVisible);
|
||||
AddUntilStep("Background is visible, storyboard is invisible", () => songSelect.IsBackgroundVisible() && !player.IsStoryboardVisible);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When exiting player, the screen that it suspends/exits to needs to have a fully visible (Alpha == 1) background.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void StoryboardTransitionTest()
|
||||
public void TestStoryboardTransition()
|
||||
{
|
||||
performFullSetup();
|
||||
createFakeStoryboard();
|
||||
AddStep("Exit to song select", () => player.Exit());
|
||||
waitForDim();
|
||||
AddAssert("Background is visible", () => songSelect.IsBackgroundVisible());
|
||||
AddUntilStep("Background is visible", () => songSelect.IsBackgroundVisible());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensure <see cref="UserDimContainer"/> is properly accepting user-defined visual changes for a background.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void DisableUserDimBackgroundTest()
|
||||
public void TestDisableUserDimBackground()
|
||||
{
|
||||
performFullSetup();
|
||||
waitForDim();
|
||||
AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
|
||||
AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
|
||||
AddStep("Enable user dim", () => songSelect.DimEnabled.Value = false);
|
||||
waitForDim();
|
||||
AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsUserBlurDisabled());
|
||||
AddUntilStep("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsUserBlurDisabled());
|
||||
AddStep("Disable user dim", () => songSelect.DimEnabled.Value = true);
|
||||
waitForDim();
|
||||
AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
|
||||
AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensure <see cref="UserDimContainer"/> is properly accepting user-defined visual changes for a storyboard.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void DisableUserDimStoryboardTest()
|
||||
public void TestDisableUserDimStoryboard()
|
||||
{
|
||||
performFullSetup();
|
||||
createFakeStoryboard();
|
||||
@ -170,41 +161,36 @@ namespace osu.Game.Tests.Visual.Background
|
||||
});
|
||||
AddStep("Enable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = true);
|
||||
AddStep("Set dim level to 1", () => songSelect.DimLevel.Value = 1f);
|
||||
waitForDim();
|
||||
AddAssert("Storyboard is invisible", () => !player.IsStoryboardVisible);
|
||||
AddUntilStep("Storyboard is invisible", () => !player.IsStoryboardVisible);
|
||||
AddStep("Disable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = false);
|
||||
waitForDim();
|
||||
AddAssert("Storyboard is visible", () => player.IsStoryboardVisible);
|
||||
AddUntilStep("Storyboard is visible", () => player.IsStoryboardVisible);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the visual settings container retains dim and blur when pausing
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void PauseTest()
|
||||
public void TestPause()
|
||||
{
|
||||
performFullSetup(true);
|
||||
AddStep("Pause", () => player.Pause());
|
||||
waitForDim();
|
||||
AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
|
||||
AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
|
||||
AddStep("Unpause", () => player.Resume());
|
||||
waitForDim();
|
||||
AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
|
||||
AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the visual settings container removes user dim when suspending <see cref="Player"/> for <see cref="ResultsScreen"/>
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TransitionTest()
|
||||
public void TestTransition()
|
||||
{
|
||||
performFullSetup();
|
||||
FadeAccessibleResults results = null;
|
||||
AddStep("Transition to Results", () => player.Push(results =
|
||||
new FadeAccessibleResults(new ScoreInfo { User = new User { Username = "osu!" } })));
|
||||
AddUntilStep("Wait for results is current", () => results.IsCurrentScreen());
|
||||
waitForDim();
|
||||
AddAssert("Screen is undimmed, original background retained", () =>
|
||||
AddUntilStep("Screen is undimmed, original background retained", () =>
|
||||
songSelect.IsBackgroundUndimmed() && songSelect.IsBackgroundCurrent() && results.IsBlurCorrect());
|
||||
}
|
||||
|
||||
@ -212,32 +198,27 @@ namespace osu.Game.Tests.Visual.Background
|
||||
/// Check if background gets undimmed and unblurred when leaving <see cref="Player"/> for <see cref="PlaySongSelect"/>
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TransitionOutTest()
|
||||
public void TestTransitionOut()
|
||||
{
|
||||
performFullSetup();
|
||||
AddStep("Exit to song select", () => player.Exit());
|
||||
waitForDim();
|
||||
AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsBlurCorrect());
|
||||
AddUntilStep("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsBlurCorrect());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if hovering on the visual settings dialogue after resuming from player still previews the background dim.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ResumeFromPlayerTest()
|
||||
public void TestResumeFromPlayer()
|
||||
{
|
||||
performFullSetup();
|
||||
AddStep("Move mouse to Visual Settings", () => InputManager.MoveMouseTo(playerLoader.VisualSettingsPos));
|
||||
AddStep("Resume PlayerLoader", () => player.Restart());
|
||||
waitForDim();
|
||||
AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
|
||||
AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
|
||||
AddStep("Move mouse to center of screen", () => InputManager.MoveMouseTo(playerLoader.ScreenPos));
|
||||
waitForDim();
|
||||
AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && playerLoader.IsBlurCorrect());
|
||||
AddUntilStep("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && playerLoader.IsBlurCorrect());
|
||||
}
|
||||
|
||||
private void waitForDim() => AddWaitStep("Wait for dim", 5);
|
||||
|
||||
private void createFakeStoryboard() => AddStep("Create storyboard", () =>
|
||||
{
|
||||
player.StoryboardEnabled.Value = false;
|
||||
|
@ -4,8 +4,8 @@
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Edit.Components;
|
||||
using osuTK;
|
||||
|
||||
@ -17,9 +17,8 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
var clock = new DecoupleableInterpolatingFramedClock { IsCoupled = false };
|
||||
Dependencies.CacheAs<IAdjustableClock>(clock);
|
||||
Dependencies.CacheAs<IFrameBasedClock>(clock);
|
||||
var clock = new EditorClock { IsCoupled = false };
|
||||
Dependencies.CacheAs(clock);
|
||||
|
||||
var playback = new PlaybackControl
|
||||
{
|
||||
|
@ -7,7 +7,6 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
@ -69,7 +68,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
private IBindable<WorkingBeatmap> beatmap { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private IAdjustableClock adjustableClock { get; set; }
|
||||
private EditorClock editorClock { get; set; }
|
||||
|
||||
public AudioVisualiser()
|
||||
{
|
||||
@ -96,13 +95,15 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
base.Update();
|
||||
|
||||
if (beatmap.Value.Track.IsLoaded)
|
||||
marker.X = (float)(adjustableClock.CurrentTime / beatmap.Value.Track.Length);
|
||||
marker.X = (float)(editorClock.CurrentTime / beatmap.Value.Track.Length);
|
||||
}
|
||||
}
|
||||
|
||||
private class StartStopButton : OsuButton
|
||||
{
|
||||
private IAdjustableClock adjustableClock;
|
||||
[Resolved]
|
||||
private EditorClock editorClock { get; set; }
|
||||
|
||||
private bool started;
|
||||
|
||||
public StartStopButton()
|
||||
@ -114,22 +115,16 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
Action = onClick;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(IAdjustableClock adjustableClock)
|
||||
{
|
||||
this.adjustableClock = adjustableClock;
|
||||
}
|
||||
|
||||
private void onClick()
|
||||
{
|
||||
if (started)
|
||||
{
|
||||
adjustableClock.Stop();
|
||||
editorClock.Stop();
|
||||
Text = "Start";
|
||||
}
|
||||
else
|
||||
{
|
||||
adjustableClock.Start();
|
||||
editorClock.Start();
|
||||
Text = "Stop";
|
||||
}
|
||||
|
||||
|
@ -281,7 +281,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
yield return new TestHitObject
|
||||
{
|
||||
StartTime = original.StartTime,
|
||||
EndTime = (original as IHasEndTime)?.EndTime ?? (original.StartTime + 100)
|
||||
Duration = (original as IHasDuration)?.Duration ?? 100
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -290,11 +290,11 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
#region HitObject
|
||||
|
||||
private class TestHitObject : ConvertHitObject, IHasEndTime
|
||||
private class TestHitObject : ConvertHitObject, IHasDuration
|
||||
{
|
||||
public double EndTime { get; set; }
|
||||
public double EndTime => StartTime + Duration;
|
||||
|
||||
public double Duration => EndTime - StartTime;
|
||||
public double Duration { get; set; }
|
||||
}
|
||||
|
||||
private class DrawableTestHitObject : DrawableHitObject<TestHitObject>
|
||||
|
@ -28,6 +28,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddStep(@"locally available state", () => downloadButton.SetDownloadState(DownloadState.LocallyAvailable));
|
||||
AddStep(@"not downloaded state", () => downloadButton.SetDownloadState(DownloadState.NotDownloaded));
|
||||
createButton(false);
|
||||
createButtonNoScore();
|
||||
}
|
||||
|
||||
private void createButton(bool withReplay)
|
||||
@ -40,6 +41,22 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
Origin = Anchor.Centre,
|
||||
};
|
||||
});
|
||||
|
||||
AddUntilStep("wait for load", () => downloadButton.IsLoaded);
|
||||
}
|
||||
|
||||
private void createButtonNoScore()
|
||||
{
|
||||
AddStep("create button with null score", () =>
|
||||
{
|
||||
Child = downloadButton = new TestReplayDownloadButton(null)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
};
|
||||
});
|
||||
|
||||
AddUntilStep("wait for load", () => downloadButton.IsLoaded);
|
||||
}
|
||||
|
||||
private ScoreInfo getScoreInfo(bool replayAvailable)
|
||||
|
@ -0,0 +1,61 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Ranking;
|
||||
using osu.Game.Screens.Ranking.Contracted;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Ranking
|
||||
{
|
||||
public class TestSceneContractedPanelMiddleContent : OsuTestScene
|
||||
{
|
||||
[Resolved]
|
||||
private RulesetStore rulesetStore { get; set; }
|
||||
|
||||
[Test]
|
||||
public void TestShowPanel()
|
||||
{
|
||||
AddStep("show example score", () => showPanel(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), new TestScoreInfo(new OsuRuleset().RulesetInfo)));
|
||||
}
|
||||
|
||||
private void showPanel(WorkingBeatmap workingBeatmap, ScoreInfo score)
|
||||
{
|
||||
Child = new ContractedPanelMiddleContentContainer(workingBeatmap, score);
|
||||
}
|
||||
|
||||
private class ContractedPanelMiddleContentContainer : Container
|
||||
{
|
||||
[Cached]
|
||||
private Bindable<WorkingBeatmap> workingBeatmap { get; set; }
|
||||
|
||||
public ContractedPanelMiddleContentContainer(WorkingBeatmap beatmap, ScoreInfo score)
|
||||
{
|
||||
workingBeatmap = new Bindable<WorkingBeatmap>(beatmap);
|
||||
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
Size = new Vector2(ScorePanel.CONTRACTED_WIDTH, 460);
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4Extensions.FromHex("#353535"),
|
||||
},
|
||||
new ContractedPanelMiddleContent(score),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
// 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 NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
@ -14,10 +13,7 @@ using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Ranking;
|
||||
using osu.Game.Screens.Ranking.Expanded;
|
||||
@ -37,7 +33,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
{
|
||||
var author = new User { Username = "mapper_name" };
|
||||
|
||||
AddStep("show example score", () => showPanel(createTestBeatmap(author), createTestScore()));
|
||||
AddStep("show example score", () => showPanel(createTestBeatmap(author), new TestScoreInfo(new OsuRuleset().RulesetInfo)));
|
||||
|
||||
AddAssert("mapper name present", () => this.ChildrenOfType<OsuSpriteText>().Any(spriteText => spriteText.Text == "mapper_name"));
|
||||
}
|
||||
@ -45,7 +41,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
[Test]
|
||||
public void TestMapWithUnknownMapper()
|
||||
{
|
||||
AddStep("show example score", () => showPanel(createTestBeatmap(null), createTestScore()));
|
||||
AddStep("show example score", () => showPanel(createTestBeatmap(null), new TestScoreInfo(new OsuRuleset().RulesetInfo)));
|
||||
|
||||
AddAssert("mapped by text not present", () =>
|
||||
this.ChildrenOfType<OsuSpriteText>().All(spriteText => !containsAny(spriteText.Text, "mapped", "by")));
|
||||
@ -66,29 +62,6 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
return new TestWorkingBeatmap(beatmap);
|
||||
}
|
||||
|
||||
private ScoreInfo createTestScore() => new ScoreInfo
|
||||
{
|
||||
User = new User
|
||||
{
|
||||
Id = 2,
|
||||
Username = "peppy",
|
||||
},
|
||||
Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo,
|
||||
Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() },
|
||||
TotalScore = 999999,
|
||||
Accuracy = 0.95,
|
||||
MaxCombo = 999,
|
||||
Rank = ScoreRank.S,
|
||||
Date = DateTimeOffset.Now,
|
||||
Statistics =
|
||||
{
|
||||
{ HitResult.Miss, 1 },
|
||||
{ HitResult.Meh, 50 },
|
||||
{ HitResult.Good, 100 },
|
||||
{ HitResult.Great, 300 },
|
||||
}
|
||||
};
|
||||
|
||||
private bool containsAny(string text, params string[] stringsToMatch) => stringsToMatch.Any(text.Contains);
|
||||
|
||||
private class ExpandedPanelMiddleContentContainer : Container
|
||||
|
@ -5,8 +5,8 @@ using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.Ranking.Expanded;
|
||||
using osu.Game.Users;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Ranking
|
||||
@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4Extensions.FromHex("#444"),
|
||||
},
|
||||
new ExpandedPanelTopContent(new User { Id = 2, Username = "peppy" }),
|
||||
new ExpandedPanelTopContent(new TestScoreInfo(new OsuRuleset().RulesetInfo).User),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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 NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
@ -11,13 +9,10 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Ranking;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Ranking
|
||||
{
|
||||
@ -41,26 +36,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo);
|
||||
}
|
||||
|
||||
private TestSoloResults createResultsScreen() => new TestSoloResults(new ScoreInfo
|
||||
{
|
||||
TotalScore = 2845370,
|
||||
Accuracy = 0.98,
|
||||
MaxCombo = 123,
|
||||
Rank = ScoreRank.A,
|
||||
Date = DateTimeOffset.Now,
|
||||
Statistics = new Dictionary<HitResult, int>
|
||||
{
|
||||
{ HitResult.Great, 50 },
|
||||
{ HitResult.Good, 20 },
|
||||
{ HitResult.Meh, 50 },
|
||||
{ HitResult.Miss, 1 }
|
||||
},
|
||||
Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo,
|
||||
User = new User
|
||||
{
|
||||
Username = "peppy",
|
||||
}
|
||||
});
|
||||
private TestSoloResults createResultsScreen() => new TestSoloResults(new TestScoreInfo(new OsuRuleset().RulesetInfo));
|
||||
|
||||
[Test]
|
||||
public void ResultsWithoutPlayer()
|
||||
|
@ -1,28 +1,23 @@
|
||||
// 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.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Ranking;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Ranking
|
||||
{
|
||||
public class TestSceneScorePanel : OsuTestScene
|
||||
{
|
||||
private ScorePanel panel;
|
||||
|
||||
[Test]
|
||||
public void TestDRank()
|
||||
{
|
||||
var score = createScore();
|
||||
score.Accuracy = 0.5;
|
||||
score.Rank = ScoreRank.D;
|
||||
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.5, Rank = ScoreRank.D };
|
||||
|
||||
addPanelStep(score);
|
||||
}
|
||||
@ -30,9 +25,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
[Test]
|
||||
public void TestCRank()
|
||||
{
|
||||
var score = createScore();
|
||||
score.Accuracy = 0.75;
|
||||
score.Rank = ScoreRank.C;
|
||||
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.75, Rank = ScoreRank.C };
|
||||
|
||||
addPanelStep(score);
|
||||
}
|
||||
@ -40,9 +33,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
[Test]
|
||||
public void TestBRank()
|
||||
{
|
||||
var score = createScore();
|
||||
score.Accuracy = 0.85;
|
||||
score.Rank = ScoreRank.B;
|
||||
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.85, Rank = ScoreRank.B };
|
||||
|
||||
addPanelStep(score);
|
||||
}
|
||||
@ -50,9 +41,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
[Test]
|
||||
public void TestARank()
|
||||
{
|
||||
var score = createScore();
|
||||
score.Accuracy = 0.925;
|
||||
score.Rank = ScoreRank.A;
|
||||
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.925, Rank = ScoreRank.A };
|
||||
|
||||
addPanelStep(score);
|
||||
}
|
||||
@ -60,9 +49,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
[Test]
|
||||
public void TestSRank()
|
||||
{
|
||||
var score = createScore();
|
||||
score.Accuracy = 0.975;
|
||||
score.Rank = ScoreRank.S;
|
||||
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.975, Rank = ScoreRank.S };
|
||||
|
||||
addPanelStep(score);
|
||||
}
|
||||
@ -70,9 +57,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
[Test]
|
||||
public void TestAlmostSSRank()
|
||||
{
|
||||
var score = createScore();
|
||||
score.Accuracy = 0.9999;
|
||||
score.Rank = ScoreRank.S;
|
||||
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.9999, Rank = ScoreRank.S };
|
||||
|
||||
addPanelStep(score);
|
||||
}
|
||||
@ -80,9 +65,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
[Test]
|
||||
public void TestSSRank()
|
||||
{
|
||||
var score = createScore();
|
||||
score.Accuracy = 1;
|
||||
score.Rank = ScoreRank.X;
|
||||
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 1, Rank = ScoreRank.X };
|
||||
|
||||
addPanelStep(score);
|
||||
}
|
||||
@ -90,44 +73,42 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
[Test]
|
||||
public void TestAllHitResults()
|
||||
{
|
||||
var score = createScore();
|
||||
score.Statistics[HitResult.Perfect] = 350;
|
||||
score.Statistics[HitResult.Ok] = 200;
|
||||
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Statistics = { [HitResult.Perfect] = 350, [HitResult.Ok] = 200 } };
|
||||
|
||||
addPanelStep(score);
|
||||
}
|
||||
|
||||
private void addPanelStep(ScoreInfo score) => AddStep("add panel", () =>
|
||||
[Test]
|
||||
public void TestContractedPanel()
|
||||
{
|
||||
Child = new ScorePanel(score)
|
||||
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.925, Rank = ScoreRank.A };
|
||||
|
||||
addPanelStep(score, PanelState.Contracted);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestExpandAndContract()
|
||||
{
|
||||
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.925, Rank = ScoreRank.A };
|
||||
|
||||
addPanelStep(score, PanelState.Contracted);
|
||||
AddWaitStep("wait for transition", 10);
|
||||
|
||||
AddStep("expand panel", () => panel.State = PanelState.Expanded);
|
||||
AddWaitStep("wait for transition", 10);
|
||||
|
||||
AddStep("contract panel", () => panel.State = PanelState.Contracted);
|
||||
AddWaitStep("wait for transition", 10);
|
||||
}
|
||||
|
||||
private void addPanelStep(ScoreInfo score, PanelState state = PanelState.Expanded) => AddStep("add panel", () =>
|
||||
{
|
||||
Child = panel = new ScorePanel(score)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
State = PanelState.Expanded
|
||||
State = state
|
||||
};
|
||||
});
|
||||
|
||||
private ScoreInfo createScore() => new ScoreInfo
|
||||
{
|
||||
User = new User
|
||||
{
|
||||
Id = 2,
|
||||
Username = "peppy",
|
||||
},
|
||||
Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo,
|
||||
Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() },
|
||||
TotalScore = 2845370,
|
||||
Accuracy = 0.95,
|
||||
MaxCombo = 999,
|
||||
Rank = ScoreRank.S,
|
||||
Date = DateTimeOffset.Now,
|
||||
Statistics =
|
||||
{
|
||||
{ HitResult.Miss, 1 },
|
||||
{ HitResult.Meh, 50 },
|
||||
{ HitResult.Good, 100 },
|
||||
{ HitResult.Great, 300 },
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
208
osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs
Normal file
208
osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs
Normal file
@ -0,0 +1,208 @@
|
||||
// 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 NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Ranking;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Ranking
|
||||
{
|
||||
public class TestSceneScorePanelList : OsuManualInputManagerTestScene
|
||||
{
|
||||
private ScorePanelList list;
|
||||
|
||||
[Test]
|
||||
public void TestEmptyList()
|
||||
{
|
||||
createListStep(() => new ScorePanelList());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestEmptyListWithSelectedScore()
|
||||
{
|
||||
createListStep(() => new ScorePanelList
|
||||
{
|
||||
SelectedScore = { Value = new TestScoreInfo(new OsuRuleset().RulesetInfo) }
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAddPanelAfterSelectingScore()
|
||||
{
|
||||
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo);
|
||||
|
||||
createListStep(() => new ScorePanelList
|
||||
{
|
||||
SelectedScore = { Value = score }
|
||||
});
|
||||
|
||||
AddStep("add panel", () => list.AddScore(score));
|
||||
|
||||
assertScoreState(score, true);
|
||||
assertExpandedPanelCentred();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAddPanelBeforeSelectingScore()
|
||||
{
|
||||
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo);
|
||||
|
||||
createListStep(() => new ScorePanelList());
|
||||
|
||||
AddStep("add panel", () => list.AddScore(score));
|
||||
|
||||
assertScoreState(score, false);
|
||||
assertFirstPanelCentred();
|
||||
|
||||
AddStep("select score", () => list.SelectedScore.Value = score);
|
||||
|
||||
assertScoreState(score, true);
|
||||
assertExpandedPanelCentred();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAddManyNonExpandedPanels()
|
||||
{
|
||||
createListStep(() => new ScorePanelList());
|
||||
|
||||
AddStep("add many scores", () =>
|
||||
{
|
||||
for (int i = 0; i < 20; i++)
|
||||
list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo));
|
||||
});
|
||||
|
||||
assertFirstPanelCentred();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAddManyScoresAfterExpandedPanel()
|
||||
{
|
||||
var initialScore = new TestScoreInfo(new OsuRuleset().RulesetInfo);
|
||||
|
||||
createListStep(() => new ScorePanelList());
|
||||
|
||||
AddStep("add initial panel and select", () =>
|
||||
{
|
||||
list.AddScore(initialScore);
|
||||
list.SelectedScore.Value = initialScore;
|
||||
});
|
||||
|
||||
AddStep("add many scores", () =>
|
||||
{
|
||||
for (int i = 0; i < 20; i++)
|
||||
list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo) { TotalScore = initialScore.TotalScore - i - 1 });
|
||||
});
|
||||
|
||||
assertScoreState(initialScore, true);
|
||||
assertExpandedPanelCentred();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAddManyScoresBeforeExpandedPanel()
|
||||
{
|
||||
var initialScore = new TestScoreInfo(new OsuRuleset().RulesetInfo);
|
||||
|
||||
createListStep(() => new ScorePanelList());
|
||||
|
||||
AddStep("add initial panel and select", () =>
|
||||
{
|
||||
list.AddScore(initialScore);
|
||||
list.SelectedScore.Value = initialScore;
|
||||
});
|
||||
|
||||
AddStep("add scores", () =>
|
||||
{
|
||||
for (int i = 0; i < 20; i++)
|
||||
list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo) { TotalScore = initialScore.TotalScore + i + 1 });
|
||||
});
|
||||
|
||||
assertScoreState(initialScore, true);
|
||||
assertExpandedPanelCentred();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAddManyPanelsOnBothSidesOfExpandedPanel()
|
||||
{
|
||||
var initialScore = new TestScoreInfo(new OsuRuleset().RulesetInfo);
|
||||
|
||||
createListStep(() => new ScorePanelList());
|
||||
|
||||
AddStep("add initial panel and select", () =>
|
||||
{
|
||||
list.AddScore(initialScore);
|
||||
list.SelectedScore.Value = initialScore;
|
||||
});
|
||||
|
||||
AddStep("add scores after", () =>
|
||||
{
|
||||
for (int i = 0; i < 20; i++)
|
||||
list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo) { TotalScore = initialScore.TotalScore - i - 1 });
|
||||
|
||||
for (int i = 0; i < 20; i++)
|
||||
list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo) { TotalScore = initialScore.TotalScore + i + 1 });
|
||||
});
|
||||
|
||||
assertScoreState(initialScore, true);
|
||||
assertExpandedPanelCentred();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSelectMultipleScores()
|
||||
{
|
||||
var firstScore = new TestScoreInfo(new OsuRuleset().RulesetInfo);
|
||||
var secondScore = new TestScoreInfo(new OsuRuleset().RulesetInfo);
|
||||
|
||||
createListStep(() => new ScorePanelList());
|
||||
|
||||
AddStep("add scores and select first", () =>
|
||||
{
|
||||
list.AddScore(firstScore);
|
||||
list.AddScore(secondScore);
|
||||
list.SelectedScore.Value = firstScore;
|
||||
});
|
||||
|
||||
assertScoreState(firstScore, true);
|
||||
assertScoreState(secondScore, false);
|
||||
|
||||
AddStep("select second score", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(list.ChildrenOfType<ScorePanel>().Single(p => p.Score == secondScore));
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
assertScoreState(firstScore, false);
|
||||
assertScoreState(secondScore, true);
|
||||
assertExpandedPanelCentred();
|
||||
}
|
||||
|
||||
private void createListStep(Func<ScorePanelList> creationFunc)
|
||||
{
|
||||
AddStep("create list", () => Child = list = creationFunc().With(d =>
|
||||
{
|
||||
d.Anchor = Anchor.Centre;
|
||||
d.Origin = Anchor.Centre;
|
||||
}));
|
||||
|
||||
AddUntilStep("wait for load", () => list.IsLoaded);
|
||||
}
|
||||
|
||||
private void assertExpandedPanelCentred() => AddUntilStep("expanded panel centred", () =>
|
||||
{
|
||||
var expandedPanel = list.ChildrenOfType<ScorePanel>().Single(p => p.State == PanelState.Expanded);
|
||||
return Precision.AlmostEquals(expandedPanel.ScreenSpaceDrawQuad.Centre.X, list.ScreenSpaceDrawQuad.Centre.X, 1);
|
||||
});
|
||||
|
||||
private void assertFirstPanelCentred()
|
||||
=> AddUntilStep("first panel centred", () => Precision.AlmostEquals(list.ChildrenOfType<ScorePanel>().First().ScreenSpaceDrawQuad.Centre.X, list.ScreenSpaceDrawQuad.Centre.X, 1));
|
||||
|
||||
private void assertScoreState(ScoreInfo score, bool expanded)
|
||||
=> AddUntilStep($"score expanded = {expanded}", () => (list.ChildrenOfType<ScorePanel>().Single(p => p.Score == score).State == PanelState.Expanded) == expanded);
|
||||
}
|
||||
}
|
@ -27,7 +27,6 @@ using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using Decoder = osu.Game.Beatmaps.Formats.Decoder;
|
||||
using ZipArchive = SharpCompress.Archives.Zip.ZipArchive;
|
||||
|
||||
namespace osu.Game.Beatmaps
|
||||
{
|
||||
@ -66,7 +65,6 @@ namespace osu.Game.Beatmaps
|
||||
private readonly AudioManager audioManager;
|
||||
private readonly GameHost host;
|
||||
private readonly BeatmapOnlineLookupQueue onlineLookupQueue;
|
||||
private readonly Storage exportStorage;
|
||||
|
||||
public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, AudioManager audioManager, GameHost host = null,
|
||||
WorkingBeatmap defaultBeatmap = null)
|
||||
@ -83,7 +81,6 @@ namespace osu.Game.Beatmaps
|
||||
beatmaps.BeatmapRestored += b => beatmapRestored.Value = new WeakReference<BeatmapInfo>(b);
|
||||
|
||||
onlineLookupQueue = new BeatmapOnlineLookupQueue(api, storage);
|
||||
exportStorage = storage.GetStorageForDirectory("exports");
|
||||
}
|
||||
|
||||
protected override ArchiveDownloadRequest<BeatmapSetInfo> CreateDownloadRequest(BeatmapSetInfo set, bool minimiseDownloadSize) =>
|
||||
@ -214,26 +211,6 @@ namespace osu.Game.Beatmaps
|
||||
workingCache.Remove(working);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exports a <see cref="BeatmapSetInfo"/> to an .osz package.
|
||||
/// </summary>
|
||||
/// <param name="set">The <see cref="BeatmapSetInfo"/> to export.</param>
|
||||
public void Export(BeatmapSetInfo set)
|
||||
{
|
||||
var localSet = QueryBeatmapSet(s => s.ID == set.ID);
|
||||
|
||||
using (var archive = ZipArchive.Create())
|
||||
{
|
||||
foreach (var file in localSet.Files)
|
||||
archive.AddEntry(file.Filename, Files.Storage.GetStream(file.FileInfo.StoragePath));
|
||||
|
||||
using (var outputStream = exportStorage.GetStream($"{set}.osz", FileAccess.Write, FileMode.Create))
|
||||
archive.SaveTo(outputStream);
|
||||
|
||||
exportStorage.OpenInNativeExplorer();
|
||||
}
|
||||
}
|
||||
|
||||
private readonly WeakList<WorkingBeatmap> workingCache = new WeakList<WorkingBeatmap>();
|
||||
|
||||
/// <summary>
|
||||
|
@ -233,14 +233,14 @@ namespace osu.Game.Beatmaps.Formats
|
||||
writer.Write(FormattableString.Invariant($"{(int)getObjectType(hitObject)},"));
|
||||
writer.Write(FormattableString.Invariant($"{(int)toLegacyHitSoundType(hitObject.Samples)},"));
|
||||
|
||||
if (hitObject is IHasCurve curveData)
|
||||
if (hitObject is IHasPath path)
|
||||
{
|
||||
addCurveData(writer, curveData, position);
|
||||
addPathData(writer, path, position);
|
||||
writer.Write(getSampleBank(hitObject.Samples, zeroBanks: true));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (hitObject is IHasEndTime)
|
||||
if (hitObject is IHasDuration)
|
||||
addEndTimeData(writer, hitObject);
|
||||
|
||||
writer.Write(getSampleBank(hitObject.Samples));
|
||||
@ -263,11 +263,11 @@ namespace osu.Game.Beatmaps.Formats
|
||||
|
||||
switch (hitObject)
|
||||
{
|
||||
case IHasCurve _:
|
||||
case IHasPath _:
|
||||
type |= LegacyHitObjectType.Slider;
|
||||
break;
|
||||
|
||||
case IHasEndTime _:
|
||||
case IHasDuration _:
|
||||
if (beatmap.BeatmapInfo.RulesetID == 3)
|
||||
type |= LegacyHitObjectType.Hold;
|
||||
else
|
||||
@ -282,13 +282,13 @@ namespace osu.Game.Beatmaps.Formats
|
||||
return type;
|
||||
}
|
||||
|
||||
private void addCurveData(TextWriter writer, IHasCurve curveData, Vector2 position)
|
||||
private void addPathData(TextWriter writer, IHasPath pathData, Vector2 position)
|
||||
{
|
||||
PathType? lastType = null;
|
||||
|
||||
for (int i = 0; i < curveData.Path.ControlPoints.Count; i++)
|
||||
for (int i = 0; i < pathData.Path.ControlPoints.Count; i++)
|
||||
{
|
||||
PathControlPoint point = curveData.Path.ControlPoints[i];
|
||||
PathControlPoint point = pathData.Path.ControlPoints[i];
|
||||
|
||||
if (point.Type.Value != null)
|
||||
{
|
||||
@ -325,29 +325,34 @@ namespace osu.Game.Beatmaps.Formats
|
||||
if (i != 0)
|
||||
{
|
||||
writer.Write(FormattableString.Invariant($"{position.X + point.Position.Value.X}:{position.Y + point.Position.Value.Y}"));
|
||||
writer.Write(i != curveData.Path.ControlPoints.Count - 1 ? "|" : ",");
|
||||
writer.Write(i != pathData.Path.ControlPoints.Count - 1 ? "|" : ",");
|
||||
}
|
||||
}
|
||||
|
||||
writer.Write(FormattableString.Invariant($"{curveData.RepeatCount + 1},"));
|
||||
writer.Write(FormattableString.Invariant($"{curveData.Path.Distance},"));
|
||||
var curveData = pathData as IHasPathWithRepeats;
|
||||
|
||||
for (int i = 0; i < curveData.NodeSamples.Count; i++)
|
||||
{
|
||||
writer.Write(FormattableString.Invariant($"{(int)toLegacyHitSoundType(curveData.NodeSamples[i])}"));
|
||||
writer.Write(i != curveData.NodeSamples.Count - 1 ? "|" : ",");
|
||||
}
|
||||
writer.Write(FormattableString.Invariant($"{(curveData?.RepeatCount ?? 0) + 1},"));
|
||||
writer.Write(FormattableString.Invariant($"{pathData.Path.Distance},"));
|
||||
|
||||
for (int i = 0; i < curveData.NodeSamples.Count; i++)
|
||||
if (curveData != null)
|
||||
{
|
||||
writer.Write(getSampleBank(curveData.NodeSamples[i], true));
|
||||
writer.Write(i != curveData.NodeSamples.Count - 1 ? "|" : ",");
|
||||
for (int i = 0; i < curveData.NodeSamples.Count; i++)
|
||||
{
|
||||
writer.Write(FormattableString.Invariant($"{(int)toLegacyHitSoundType(curveData.NodeSamples[i])}"));
|
||||
writer.Write(i != curveData.NodeSamples.Count - 1 ? "|" : ",");
|
||||
}
|
||||
|
||||
for (int i = 0; i < curveData.NodeSamples.Count; i++)
|
||||
{
|
||||
writer.Write(getSampleBank(curveData.NodeSamples[i], true));
|
||||
writer.Write(i != curveData.NodeSamples.Count - 1 ? "|" : ",");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addEndTimeData(TextWriter writer, HitObject hitObject)
|
||||
{
|
||||
var endTimeData = (IHasEndTime)hitObject;
|
||||
var endTimeData = (IHasDuration)hitObject;
|
||||
var type = getObjectType(hitObject);
|
||||
|
||||
char suffix = ',';
|
||||
|
@ -63,7 +63,7 @@ namespace osu.Game.Beatmaps
|
||||
length = emptyLength;
|
||||
break;
|
||||
|
||||
case IHasEndTime endTime:
|
||||
case IHasDuration endTime:
|
||||
length = endTime.EndTime + excess_length;
|
||||
break;
|
||||
|
||||
@ -129,12 +129,19 @@ namespace osu.Game.Beatmaps
|
||||
processor?.PreProcess();
|
||||
|
||||
// Compute default values for hitobjects, including creating nested hitobjects in-case they're needed
|
||||
foreach (var obj in converted.HitObjects)
|
||||
try
|
||||
{
|
||||
if (cancellationSource.IsCancellationRequested)
|
||||
throw new BeatmapLoadTimeoutException(BeatmapInfo);
|
||||
foreach (var obj in converted.HitObjects)
|
||||
{
|
||||
if (cancellationSource.IsCancellationRequested)
|
||||
throw new BeatmapLoadTimeoutException(BeatmapInfo);
|
||||
|
||||
obj.ApplyDefaults(converted.ControlPointInfo, converted.BeatmapInfo.BaseDifficulty);
|
||||
obj.ApplyDefaults(converted.ControlPointInfo, converted.BeatmapInfo.BaseDifficulty, cancellationSource.Token);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw new BeatmapLoadTimeoutException(BeatmapInfo);
|
||||
}
|
||||
|
||||
foreach (var mod in mods.OfType<IApplicableToHitObject>())
|
||||
|
@ -22,6 +22,7 @@ using osu.Game.IO.Archives;
|
||||
using osu.Game.IPC;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using osu.Game.Utils;
|
||||
using SharpCompress.Archives.Zip;
|
||||
using SharpCompress.Common;
|
||||
using FileInfo = osu.Game.IO.FileInfo;
|
||||
|
||||
@ -54,12 +55,12 @@ namespace osu.Game.Database
|
||||
public Action<Notification> PostNotification { protected get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Fired when a new <typeparamref name="TModel"/> becomes available in the database.
|
||||
/// Fired when a new or updated <typeparamref name="TModel"/> becomes available in the database.
|
||||
/// This is not guaranteed to run on the update thread.
|
||||
/// </summary>
|
||||
public IBindable<WeakReference<TModel>> ItemAdded => itemAdded;
|
||||
public IBindable<WeakReference<TModel>> ItemUpdated => itemUpdated;
|
||||
|
||||
private readonly Bindable<WeakReference<TModel>> itemAdded = new Bindable<WeakReference<TModel>>();
|
||||
private readonly Bindable<WeakReference<TModel>> itemUpdated = new Bindable<WeakReference<TModel>>();
|
||||
|
||||
/// <summary>
|
||||
/// Fired when a <typeparamref name="TModel"/> is removed from the database.
|
||||
@ -82,14 +83,18 @@ namespace osu.Game.Database
|
||||
// ReSharper disable once NotAccessedField.Local (we should keep a reference to this so it is not finalised)
|
||||
private ArchiveImportIPCChannel ipc;
|
||||
|
||||
private readonly Storage exportStorage;
|
||||
|
||||
protected ArchiveModelManager(Storage storage, IDatabaseContextFactory contextFactory, MutableDatabaseBackedStoreWithFileIncludes<TModel, TFileModel> modelStore, IIpcHost importHost = null)
|
||||
{
|
||||
ContextFactory = contextFactory;
|
||||
|
||||
ModelStore = modelStore;
|
||||
ModelStore.ItemAdded += item => handleEvent(() => itemAdded.Value = new WeakReference<TModel>(item));
|
||||
ModelStore.ItemUpdated += item => handleEvent(() => itemUpdated.Value = new WeakReference<TModel>(item));
|
||||
ModelStore.ItemRemoved += item => handleEvent(() => itemRemoved.Value = new WeakReference<TModel>(item));
|
||||
|
||||
exportStorage = storage.GetStorageForDirectory("exports");
|
||||
|
||||
Files = new FileStore(contextFactory, storage);
|
||||
|
||||
if (importHost != null)
|
||||
@ -369,6 +374,29 @@ namespace osu.Game.Database
|
||||
return item;
|
||||
}, cancellationToken, TaskCreationOptions.HideScheduler, import_scheduler).Unwrap();
|
||||
|
||||
/// <summary>
|
||||
/// Exports an item to a legacy (.zip based) package.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to export.</param>
|
||||
public void Export(TModel item)
|
||||
{
|
||||
var retrievedItem = ModelStore.ConsumableItems.FirstOrDefault(s => s.ID == item.ID);
|
||||
|
||||
if (retrievedItem == null)
|
||||
throw new ArgumentException("Specified model could not be found", nameof(item));
|
||||
|
||||
using (var archive = ZipArchive.Create())
|
||||
{
|
||||
foreach (var file in retrievedItem.Files)
|
||||
archive.AddEntry(file.Filename, Files.Storage.GetStream(file.FileInfo.StoragePath));
|
||||
|
||||
using (var outputStream = exportStorage.GetStream($"{getValidFilename(item.ToString())}{HandledExtensions.First()}", FileAccess.Write, FileMode.Create))
|
||||
archive.SaveTo(outputStream);
|
||||
|
||||
exportStorage.OpenInNativeExplorer();
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateFile(TModel model, TFileModel file, Stream contents)
|
||||
{
|
||||
using (var usage = ContextFactory.GetForWrite())
|
||||
@ -710,5 +738,12 @@ namespace osu.Game.Database
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private string getValidFilename(string filename)
|
||||
{
|
||||
foreach (char c in Path.GetInvalidFileNameChars())
|
||||
filename = filename.Replace(c, '_');
|
||||
return filename;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ namespace osu.Game.Database
|
||||
public interface IModelManager<TModel>
|
||||
where TModel : class
|
||||
{
|
||||
IBindable<WeakReference<TModel>> ItemAdded { get; }
|
||||
IBindable<WeakReference<TModel>> ItemUpdated { get; }
|
||||
|
||||
IBindable<WeakReference<TModel>> ItemRemoved { get; }
|
||||
}
|
||||
|
@ -16,7 +16,14 @@ namespace osu.Game.Database
|
||||
public abstract class MutableDatabaseBackedStore<T> : DatabaseBackedStore
|
||||
where T : class, IHasPrimaryKey, ISoftDelete
|
||||
{
|
||||
public event Action<T> ItemAdded;
|
||||
/// <summary>
|
||||
/// Fired when an item was added or updated.
|
||||
/// </summary>
|
||||
public event Action<T> ItemUpdated;
|
||||
|
||||
/// <summary>
|
||||
/// Fired when an item was removed.
|
||||
/// </summary>
|
||||
public event Action<T> ItemRemoved;
|
||||
|
||||
protected MutableDatabaseBackedStore(IDatabaseContextFactory contextFactory, Storage storage = null)
|
||||
@ -41,7 +48,7 @@ namespace osu.Game.Database
|
||||
context.Attach(item);
|
||||
}
|
||||
|
||||
ItemAdded?.Invoke(item);
|
||||
ItemUpdated?.Invoke(item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -53,8 +60,7 @@ namespace osu.Game.Database
|
||||
using (var usage = ContextFactory.GetForWrite())
|
||||
usage.Context.Update(item);
|
||||
|
||||
ItemRemoved?.Invoke(item);
|
||||
ItemAdded?.Invoke(item);
|
||||
ItemUpdated?.Invoke(item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -91,7 +97,7 @@ namespace osu.Game.Database
|
||||
item.DeletePending = false;
|
||||
}
|
||||
|
||||
ItemAdded?.Invoke(item);
|
||||
ItemUpdated?.Invoke(item);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -69,7 +69,7 @@ namespace osu.Game.IO
|
||||
|
||||
public override void DeleteDatabase(string name) => UnderlyingStorage.DeleteDatabase(MutatePath(name));
|
||||
|
||||
public override void OpenInNativeExplorer() => UnderlyingStorage.OpenInNativeExplorer();
|
||||
public override void OpenPathInNativeExplorer(string path) => UnderlyingStorage.OpenPathInNativeExplorer(MutatePath(path));
|
||||
|
||||
public override Storage GetStorageForDirectory(string path)
|
||||
{
|
||||
|
@ -27,7 +27,15 @@ namespace osu.Game.Online.API.Requests
|
||||
|
||||
private string directionString => SortDirection == SortDirection.Descending ? @"desc" : @"asc";
|
||||
|
||||
public SearchBeatmapSetsRequest(string query, RulesetInfo ruleset, Cursor cursor = null, SearchCategory searchCategory = SearchCategory.Any, SortCriteria sortCriteria = SortCriteria.Ranked, SortDirection sortDirection = SortDirection.Descending)
|
||||
public SearchBeatmapSetsRequest(
|
||||
string query,
|
||||
RulesetInfo ruleset,
|
||||
Cursor cursor = null,
|
||||
SearchCategory searchCategory = SearchCategory.Any,
|
||||
SortCriteria sortCriteria = SortCriteria.Ranked,
|
||||
SortDirection sortDirection = SortDirection.Descending,
|
||||
SearchGenre genre = SearchGenre.Any,
|
||||
SearchLanguage language = SearchLanguage.Any)
|
||||
{
|
||||
this.query = string.IsNullOrEmpty(query) ? string.Empty : System.Uri.EscapeDataString(query);
|
||||
this.ruleset = ruleset;
|
||||
@ -36,8 +44,8 @@ namespace osu.Game.Online.API.Requests
|
||||
SearchCategory = searchCategory;
|
||||
SortCriteria = sortCriteria;
|
||||
SortDirection = sortDirection;
|
||||
Genre = SearchGenre.Any;
|
||||
Language = SearchLanguage.Any;
|
||||
Genre = genre;
|
||||
Language = language;
|
||||
}
|
||||
|
||||
protected override WebRequest CreateWebRequest()
|
||||
|
@ -34,7 +34,7 @@ namespace osu.Game.Online
|
||||
Model.Value = model;
|
||||
}
|
||||
|
||||
private IBindable<WeakReference<TModel>> managerAdded;
|
||||
private IBindable<WeakReference<TModel>> managedUpdated;
|
||||
private IBindable<WeakReference<TModel>> managerRemoved;
|
||||
private IBindable<WeakReference<ArchiveDownloadRequest<TModel>>> managerDownloadBegan;
|
||||
private IBindable<WeakReference<ArchiveDownloadRequest<TModel>>> managerDownloadFailed;
|
||||
@ -56,8 +56,8 @@ namespace osu.Game.Online
|
||||
managerDownloadBegan.BindValueChanged(downloadBegan);
|
||||
managerDownloadFailed = manager.DownloadFailed.GetBoundCopy();
|
||||
managerDownloadFailed.BindValueChanged(downloadFailed);
|
||||
managerAdded = manager.ItemAdded.GetBoundCopy();
|
||||
managerAdded.BindValueChanged(itemAdded);
|
||||
managedUpdated = manager.ItemUpdated.GetBoundCopy();
|
||||
managedUpdated.BindValueChanged(itemUpdated);
|
||||
managerRemoved = manager.ItemRemoved.GetBoundCopy();
|
||||
managerRemoved.BindValueChanged(itemRemoved);
|
||||
}
|
||||
@ -128,7 +128,7 @@ namespace osu.Game.Online
|
||||
|
||||
private void onRequestFailure(Exception e) => Schedule(() => attachDownload(null));
|
||||
|
||||
private void itemAdded(ValueChangedEvent<WeakReference<TModel>> weakItem)
|
||||
private void itemUpdated(ValueChangedEvent<WeakReference<TModel>> weakItem)
|
||||
{
|
||||
if (weakItem.NewValue.TryGetTarget(out var item))
|
||||
setDownloadStateFromManager(item, DownloadState.LocallyAvailable);
|
||||
|
@ -192,7 +192,7 @@ namespace osu.Game
|
||||
ScoreManager.Delete(getBeatmapScores(item), true);
|
||||
});
|
||||
|
||||
BeatmapManager.ItemAdded.BindValueChanged(i =>
|
||||
BeatmapManager.ItemUpdated.BindValueChanged(i =>
|
||||
{
|
||||
if (i.NewValue.TryGetTarget(out var item))
|
||||
ScoreManager.Undelete(getBeatmapScores(item), true);
|
||||
@ -229,8 +229,8 @@ namespace osu.Game
|
||||
|
||||
FileStore.Cleanup();
|
||||
|
||||
if (API is APIAccess apiAcces)
|
||||
AddInternal(apiAcces);
|
||||
if (API is APIAccess apiAccess)
|
||||
AddInternal(apiAccess);
|
||||
AddInternal(RulesetConfigCache);
|
||||
|
||||
GlobalActionContainer globalBinding;
|
||||
|
@ -177,7 +177,9 @@ namespace osu.Game.Overlays.BeatmapListing
|
||||
lastResponse?.Cursor,
|
||||
searchControl.Category.Value,
|
||||
sortControl.Current.Value,
|
||||
sortControl.SortDirection.Value);
|
||||
sortControl.SortDirection.Value,
|
||||
searchControl.Genre.Value,
|
||||
searchControl.Language.Value);
|
||||
|
||||
getSetsRequest.Success += response =>
|
||||
{
|
||||
@ -186,6 +188,9 @@ namespace osu.Game.Overlays.BeatmapListing
|
||||
if (sets.Count == 0)
|
||||
noMoreResults = true;
|
||||
|
||||
if (CurrentPage == 0)
|
||||
searchControl.BeatmapSet = sets.FirstOrDefault();
|
||||
|
||||
lastResponse = response;
|
||||
getSetsRequest = null;
|
||||
|
||||
|
@ -60,14 +60,14 @@ namespace osu.Game.Overlays
|
||||
[Resolved(canBeNull: true)]
|
||||
private OnScreenDisplay onScreenDisplay { get; set; }
|
||||
|
||||
private IBindable<WeakReference<BeatmapSetInfo>> managerAdded;
|
||||
private IBindable<WeakReference<BeatmapSetInfo>> managerUpdated;
|
||||
private IBindable<WeakReference<BeatmapSetInfo>> managerRemoved;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
managerAdded = beatmaps.ItemAdded.GetBoundCopy();
|
||||
managerAdded.BindValueChanged(beatmapAdded);
|
||||
managerUpdated = beatmaps.ItemUpdated.GetBoundCopy();
|
||||
managerUpdated.BindValueChanged(beatmapUpdated);
|
||||
managerRemoved = beatmaps.ItemRemoved.GetBoundCopy();
|
||||
managerRemoved.BindValueChanged(beatmapRemoved);
|
||||
|
||||
@ -98,14 +98,14 @@ namespace osu.Game.Overlays
|
||||
/// </summary>
|
||||
public bool IsPlaying => current?.Track.IsRunning ?? false;
|
||||
|
||||
private void beatmapAdded(ValueChangedEvent<WeakReference<BeatmapSetInfo>> weakSet)
|
||||
private void beatmapUpdated(ValueChangedEvent<WeakReference<BeatmapSetInfo>> weakSet)
|
||||
{
|
||||
if (weakSet.NewValue.TryGetTarget(out var set))
|
||||
{
|
||||
Schedule(() =>
|
||||
{
|
||||
if (!beatmapSets.Contains(set))
|
||||
beatmapSets.Add(set);
|
||||
beatmapSets.Remove(set);
|
||||
beatmapSets.Add(set);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Skinning;
|
||||
@ -31,16 +32,18 @@ namespace osu.Game.Overlays.Settings.Sections
|
||||
[Resolved]
|
||||
private SkinManager skins { get; set; }
|
||||
|
||||
private IBindable<WeakReference<SkinInfo>> managerAdded;
|
||||
private IBindable<WeakReference<SkinInfo>> managerUpdated;
|
||||
private IBindable<WeakReference<SkinInfo>> managerRemoved;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuConfigManager config)
|
||||
{
|
||||
FlowContent.Spacing = new Vector2(0, 5);
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
skinDropdown = new SkinSettingsDropdown(),
|
||||
new ExportSkinButton(),
|
||||
new SettingsSlider<float, SizeSlider>
|
||||
{
|
||||
LabelText = "Menu cursor size",
|
||||
@ -70,8 +73,8 @@ namespace osu.Game.Overlays.Settings.Sections
|
||||
},
|
||||
};
|
||||
|
||||
managerAdded = skins.ItemAdded.GetBoundCopy();
|
||||
managerAdded.BindValueChanged(itemAdded);
|
||||
managerUpdated = skins.ItemUpdated.GetBoundCopy();
|
||||
managerUpdated.BindValueChanged(itemUpdated);
|
||||
|
||||
managerRemoved = skins.ItemRemoved.GetBoundCopy();
|
||||
managerRemoved.BindValueChanged(itemRemoved);
|
||||
@ -89,10 +92,10 @@ namespace osu.Game.Overlays.Settings.Sections
|
||||
dropdownBindable.BindValueChanged(skin => configBindable.Value = skin.NewValue.ID);
|
||||
}
|
||||
|
||||
private void itemAdded(ValueChangedEvent<WeakReference<SkinInfo>> weakItem)
|
||||
private void itemUpdated(ValueChangedEvent<WeakReference<SkinInfo>> weakItem)
|
||||
{
|
||||
if (weakItem.NewValue.TryGetTarget(out var item))
|
||||
Schedule(() => skinDropdown.Items = skinDropdown.Items.Append(item).ToArray());
|
||||
Schedule(() => skinDropdown.Items = skinDropdown.Items.Where(i => !i.Equals(item)).Append(item).ToArray());
|
||||
}
|
||||
|
||||
private void itemRemoved(ValueChangedEvent<WeakReference<SkinInfo>> weakItem)
|
||||
@ -117,5 +120,35 @@ namespace osu.Game.Overlays.Settings.Sections
|
||||
protected override DropdownMenu CreateMenu() => base.CreateMenu().With(m => m.MaxHeight = 200);
|
||||
}
|
||||
}
|
||||
|
||||
private class ExportSkinButton : SettingsButton
|
||||
{
|
||||
[Resolved]
|
||||
private SkinManager skins { get; set; }
|
||||
|
||||
private Bindable<Skin> currentSkin;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Text = "Export selected skin";
|
||||
Action = export;
|
||||
|
||||
currentSkin = skins.CurrentSkin.GetBoundCopy();
|
||||
currentSkin.BindValueChanged(skin => Enabled.Value = skin.NewValue.SkinInfo.ID > 0, true);
|
||||
}
|
||||
|
||||
private void export()
|
||||
{
|
||||
try
|
||||
{
|
||||
skins.Export(currentSkin.Value.SkinInfo);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log($"Could not export current skin: {e.Message}", level: LogLevel.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,15 +3,14 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Configuration;
|
||||
@ -20,6 +19,7 @@ using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Edit.Components.RadioButtons;
|
||||
using osu.Game.Screens.Edit.Compose;
|
||||
@ -38,23 +38,19 @@ namespace osu.Game.Rulesets.Edit
|
||||
protected readonly Ruleset Ruleset;
|
||||
|
||||
[Resolved]
|
||||
protected IFrameBasedClock EditorClock { get; private set; }
|
||||
protected EditorClock EditorClock { get; private set; }
|
||||
|
||||
[Resolved]
|
||||
protected EditorBeatmap EditorBeatmap { get; private set; }
|
||||
|
||||
[Resolved]
|
||||
private IAdjustableClock adjustableClock { get; set; }
|
||||
|
||||
[Resolved]
|
||||
protected IBeatSnapProvider BeatSnapProvider { get; private set; }
|
||||
|
||||
protected ComposeBlueprintContainer BlueprintContainer { get; private set; }
|
||||
|
||||
private DrawableEditRulesetWrapper<TObject> drawableRulesetWrapper;
|
||||
private Container distanceSnapGridContainer;
|
||||
private DistanceSnapGrid distanceSnapGrid;
|
||||
private readonly List<Container> layerContainers = new List<Container>();
|
||||
|
||||
protected readonly Container LayerBelowRuleset = new Container { RelativeSizeAxes = Axes.Both };
|
||||
|
||||
private InputManager inputManager;
|
||||
|
||||
@ -63,11 +59,10 @@ namespace osu.Game.Rulesets.Edit
|
||||
protected HitObjectComposer(Ruleset ruleset)
|
||||
{
|
||||
Ruleset = ruleset;
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(IFrameBasedClock framedClock)
|
||||
private void load()
|
||||
{
|
||||
Config = Dependencies.Get<RulesetConfigCache>().GetConfigFor(Ruleset);
|
||||
|
||||
@ -75,7 +70,7 @@ namespace osu.Game.Rulesets.Edit
|
||||
{
|
||||
drawableRulesetWrapper = new DrawableEditRulesetWrapper<TObject>(CreateDrawableRuleset(Ruleset, EditorBeatmap.PlayableBeatmap))
|
||||
{
|
||||
Clock = framedClock,
|
||||
Clock = EditorClock,
|
||||
ProcessCustomClock = false
|
||||
};
|
||||
}
|
||||
@ -85,17 +80,6 @@ namespace osu.Game.Rulesets.Edit
|
||||
return;
|
||||
}
|
||||
|
||||
var layerBelowRuleset = drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer().WithChildren(new Drawable[]
|
||||
{
|
||||
distanceSnapGridContainer = new Container { RelativeSizeAxes = Axes.Both },
|
||||
new EditorPlayfieldBorder { RelativeSizeAxes = Axes.Both }
|
||||
});
|
||||
|
||||
var layerAboveRuleset = drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer().WithChild(BlueprintContainer = CreateBlueprintContainer());
|
||||
|
||||
layerContainers.Add(layerBelowRuleset);
|
||||
layerContainers.Add(layerAboveRuleset);
|
||||
|
||||
InternalChild = new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
@ -117,11 +101,19 @@ namespace osu.Game.Rulesets.Edit
|
||||
{
|
||||
Name = "Content",
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
layerBelowRuleset,
|
||||
// layers below playfield
|
||||
drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer().WithChildren(new Drawable[]
|
||||
{
|
||||
LayerBelowRuleset,
|
||||
new EditorPlayfieldBorder { RelativeSizeAxes = Axes.Both }
|
||||
}),
|
||||
drawableRulesetWrapper,
|
||||
layerAboveRuleset
|
||||
// layers above playfield
|
||||
drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer()
|
||||
.WithChild(BlueprintContainer = CreateBlueprintContainer(HitObjects))
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -139,9 +131,54 @@ namespace osu.Game.Rulesets.Edit
|
||||
|
||||
setSelectTool();
|
||||
|
||||
BlueprintContainer.SelectionChanged += selectionChanged;
|
||||
EditorBeatmap.SelectedHitObjects.CollectionChanged += selectionChanged;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
inputManager = GetContainingInputManager();
|
||||
}
|
||||
|
||||
public override Playfield Playfield => drawableRulesetWrapper.Playfield;
|
||||
|
||||
public override IEnumerable<DrawableHitObject> HitObjects => drawableRulesetWrapper.Playfield.AllHitObjects;
|
||||
|
||||
public override bool CursorInPlacementArea => drawableRulesetWrapper.Playfield.ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position);
|
||||
|
||||
/// <summary>
|
||||
/// Defines all available composition tools, listed on the left side of the editor screen as button controls.
|
||||
/// This should usually define one tool for each <see cref="HitObject"/> type used in the target ruleset.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A "select" tool is automatically added as the first tool.
|
||||
/// </remarks>
|
||||
protected abstract IReadOnlyList<HitObjectCompositionTool> CompositionTools { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Construct a relevant blueprint container. This will manage hitobject selection/placement input handling and display logic.
|
||||
/// </summary>
|
||||
/// <param name="hitObjects">A live collection of all <see cref="DrawableHitObject"/>s in the editor beatmap.</param>
|
||||
protected virtual ComposeBlueprintContainer CreateBlueprintContainer(IEnumerable<DrawableHitObject> hitObjects)
|
||||
=> new ComposeBlueprintContainer(hitObjects);
|
||||
|
||||
/// <summary>
|
||||
/// Construct a drawable ruleset for the provided ruleset.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Can be overridden to add editor-specific logical changes to a <see cref="Ruleset"/>'s standard <see cref="DrawableRuleset{TObject}"/>.
|
||||
/// For example, hit animations or judgement logic may be changed to give a better editor user experience.
|
||||
/// </remarks>
|
||||
/// <param name="ruleset">The ruleset used to construct its drawable counterpart.</param>
|
||||
/// <param name="beatmap">The loaded beatmap.</param>
|
||||
/// <param name="mods">The mods to be applied.</param>
|
||||
/// <returns>An editor-relevant <see cref="DrawableRuleset{TObject}"/>.</returns>
|
||||
protected virtual DrawableRuleset<TObject> CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods = null)
|
||||
=> (DrawableRuleset<TObject>)ruleset.CreateDrawableRulesetWith(beatmap, mods);
|
||||
|
||||
#region Tool selection logic
|
||||
|
||||
protected override bool OnKeyDown(KeyDownEvent e)
|
||||
{
|
||||
if (e.Key >= Key.Number1 && e.Key <= Key.Number9)
|
||||
@ -158,49 +195,13 @@ namespace osu.Game.Rulesets.Edit
|
||||
return base.OnKeyDown(e);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
private void selectionChanged(object sender, NotifyCollectionChangedEventArgs changedArgs)
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
inputManager = GetContainingInputManager();
|
||||
}
|
||||
|
||||
private double lastGridUpdateTime;
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (EditorClock.CurrentTime != lastGridUpdateTime && !(BlueprintContainer.CurrentTool is SelectTool))
|
||||
showGridFor(Enumerable.Empty<HitObject>());
|
||||
}
|
||||
|
||||
protected override void UpdateAfterChildren()
|
||||
{
|
||||
base.UpdateAfterChildren();
|
||||
|
||||
layerContainers.ForEach(l =>
|
||||
{
|
||||
l.Anchor = drawableRulesetWrapper.Playfield.Anchor;
|
||||
l.Origin = drawableRulesetWrapper.Playfield.Origin;
|
||||
l.Position = drawableRulesetWrapper.Playfield.Position;
|
||||
l.Size = drawableRulesetWrapper.Playfield.Size;
|
||||
});
|
||||
}
|
||||
|
||||
private void selectionChanged(IEnumerable<HitObject> selectedHitObjects)
|
||||
{
|
||||
var hitObjects = selectedHitObjects.ToArray();
|
||||
|
||||
if (hitObjects.Any())
|
||||
if (EditorBeatmap.SelectedHitObjects.Any())
|
||||
{
|
||||
// ensure in selection mode if a selection is made.
|
||||
setSelectTool();
|
||||
|
||||
showGridFor(hitObjects);
|
||||
}
|
||||
else
|
||||
distanceSnapGridContainer.Hide();
|
||||
}
|
||||
|
||||
private void setSelectTool() => toolboxCollection.Items.First().Select();
|
||||
@ -209,37 +210,13 @@ namespace osu.Game.Rulesets.Edit
|
||||
{
|
||||
BlueprintContainer.CurrentTool = tool;
|
||||
|
||||
if (tool is SelectTool)
|
||||
distanceSnapGridContainer.Hide();
|
||||
else
|
||||
{
|
||||
if (!(tool is SelectTool))
|
||||
EditorBeatmap.SelectedHitObjects.Clear();
|
||||
showGridFor(Enumerable.Empty<HitObject>());
|
||||
}
|
||||
}
|
||||
|
||||
private void showGridFor(IEnumerable<HitObject> selectedHitObjects)
|
||||
{
|
||||
distanceSnapGridContainer.Clear();
|
||||
distanceSnapGrid = CreateDistanceSnapGrid(selectedHitObjects);
|
||||
#endregion
|
||||
|
||||
if (distanceSnapGrid != null)
|
||||
{
|
||||
distanceSnapGridContainer.Child = distanceSnapGrid;
|
||||
distanceSnapGridContainer.Show();
|
||||
}
|
||||
|
||||
lastGridUpdateTime = EditorClock.CurrentTime;
|
||||
}
|
||||
|
||||
public override IEnumerable<DrawableHitObject> HitObjects => drawableRulesetWrapper.Playfield.AllHitObjects;
|
||||
public override bool CursorInPlacementArea => drawableRulesetWrapper.Playfield.ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position);
|
||||
|
||||
protected abstract IReadOnlyList<HitObjectCompositionTool> CompositionTools { get; }
|
||||
|
||||
protected abstract ComposeBlueprintContainer CreateBlueprintContainer();
|
||||
|
||||
protected abstract DrawableRuleset<TObject> CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods = null);
|
||||
#region IPlacementHandler
|
||||
|
||||
public void BeginPlacement(HitObject hitObject)
|
||||
{
|
||||
@ -254,23 +231,43 @@ namespace osu.Game.Rulesets.Edit
|
||||
{
|
||||
EditorBeatmap.Add(hitObject);
|
||||
|
||||
if (adjustableClock.CurrentTime < hitObject.StartTime)
|
||||
adjustableClock.Seek(hitObject.StartTime);
|
||||
if (EditorClock.CurrentTime < hitObject.StartTime)
|
||||
EditorClock.SeekTo(hitObject.StartTime);
|
||||
}
|
||||
|
||||
showGridFor(Enumerable.Empty<HitObject>());
|
||||
}
|
||||
|
||||
public void Delete(HitObject hitObject) => EditorBeatmap.Remove(hitObject);
|
||||
|
||||
#endregion
|
||||
|
||||
#region IPositionSnapProvider
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve the relevant <see cref="Playfield"/> at a specified screen-space position.
|
||||
/// In cases where a ruleset doesn't require custom logic (due to nested playfields, for example)
|
||||
/// this will return the ruleset's main playfield.
|
||||
/// </summary>
|
||||
/// <param name="screenSpacePosition">The screen-space position to query.</param>
|
||||
/// <returns>The most relevant <see cref="Playfield"/>.</returns>
|
||||
protected virtual Playfield PlayfieldAtScreenSpacePosition(Vector2 screenSpacePosition) => drawableRulesetWrapper.Playfield;
|
||||
|
||||
public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition)
|
||||
{
|
||||
if (distanceSnapGrid == null) return new SnapResult(screenSpacePosition, null);
|
||||
var playfield = PlayfieldAtScreenSpacePosition(screenSpacePosition);
|
||||
double? targetTime = null;
|
||||
|
||||
// TODO: move distance snap grid to OsuHitObjectComposer.
|
||||
(Vector2 pos, double time) = distanceSnapGrid.GetSnappedPosition(distanceSnapGrid.ToLocalSpace(screenSpacePosition));
|
||||
if (playfield is ScrollingPlayfield scrollingPlayfield)
|
||||
{
|
||||
targetTime = scrollingPlayfield.TimeAtScreenSpacePosition(screenSpacePosition);
|
||||
|
||||
return new SnapResult(distanceSnapGrid.ToScreenSpace(pos), time);
|
||||
// apply beat snapping
|
||||
targetTime = BeatSnapProvider.SnapTime(targetTime.Value);
|
||||
|
||||
// convert back to screen space
|
||||
screenSpacePosition = scrollingPlayfield.ScreenSpacePositionAtTime(targetTime.Value);
|
||||
}
|
||||
|
||||
return new SnapResult(screenSpacePosition, targetTime, playfield);
|
||||
}
|
||||
|
||||
public override float GetBeatSnapDistanceAt(double referenceTime)
|
||||
@ -300,19 +297,30 @@ namespace osu.Game.Rulesets.Edit
|
||||
|
||||
return DurationToDistance(referenceTime, snappedEndTime - referenceTime);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A non-generic definition of a HitObject composer class.
|
||||
/// Generally used to access certain methods without requiring a generic type for <see cref="HitObjectComposer{T}" />.
|
||||
/// </summary>
|
||||
[Cached(typeof(HitObjectComposer))]
|
||||
[Cached(typeof(IPositionSnapProvider))]
|
||||
public abstract class HitObjectComposer : CompositeDrawable, IPositionSnapProvider
|
||||
{
|
||||
internal HitObjectComposer()
|
||||
protected HitObjectComposer()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// All the <see cref="DrawableHitObject"/>s.
|
||||
/// The target ruleset's playfield.
|
||||
/// </summary>
|
||||
public abstract Playfield Playfield { get; }
|
||||
|
||||
/// <summary>
|
||||
/// All <see cref="DrawableHitObject"/>s in currently loaded beatmap.
|
||||
/// </summary>
|
||||
public abstract IEnumerable<DrawableHitObject> HitObjects { get; }
|
||||
|
||||
@ -321,13 +329,7 @@ namespace osu.Game.Rulesets.Edit
|
||||
/// </summary>
|
||||
public abstract bool CursorInPlacementArea { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates the <see cref="DistanceSnapGrid"/> applicable for a <see cref="HitObject"/> selection.
|
||||
/// </summary>
|
||||
/// <param name="selectedHitObjects">The <see cref="HitObject"/> selection.</param>
|
||||
/// <returns>The <see cref="DistanceSnapGrid"/> for <paramref name="selectedHitObjects"/>. If empty, a grid is returned for the current point in time.</returns>
|
||||
[CanBeNull]
|
||||
protected virtual DistanceSnapGrid CreateDistanceSnapGrid([NotNull] IEnumerable<HitObject> selectedHitObjects) => null;
|
||||
#region IPositionSnapProvider
|
||||
|
||||
public abstract SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition);
|
||||
|
||||
@ -340,5 +342,7 @@ namespace osu.Game.Rulesets.Edit
|
||||
public abstract double GetSnappedDurationFromDistance(double referenceTime, float distance);
|
||||
|
||||
public abstract float GetSnappedDistanceFromDistance(double referenceTime, float distance);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,12 @@ namespace osu.Game.Rulesets.Edit
|
||||
/// </summary>
|
||||
public readonly DrawableHitObject DrawableObject;
|
||||
|
||||
protected override bool ShouldBeAlive => (DrawableObject.IsAlive && DrawableObject.IsPresent) || State == SelectionState.Selected;
|
||||
/// <summary>
|
||||
/// Whether the blueprint should be shown even when the <see cref="DrawableObject"/> is not alive.
|
||||
/// </summary>
|
||||
protected virtual bool AlwaysShowWhenSelected => false;
|
||||
|
||||
protected override bool ShouldBeAlive => (DrawableObject.IsAlive && DrawableObject.IsPresent) || (AlwaysShowWhenSelected && State == SelectionState.Selected);
|
||||
|
||||
protected OverlaySelectionBlueprint(DrawableHitObject drawableObject)
|
||||
: base(drawableObject.HitObject)
|
||||
|
@ -1,15 +1,16 @@
|
||||
// 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.Threading;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Edit.Compose;
|
||||
using osuTK;
|
||||
|
||||
@ -30,10 +31,13 @@ namespace osu.Game.Rulesets.Edit
|
||||
/// </summary>
|
||||
protected readonly HitObject HitObject;
|
||||
|
||||
protected IClock EditorClock { get; private set; }
|
||||
[Resolved(canBeNull: true)]
|
||||
protected EditorClock EditorClock { get; private set; }
|
||||
|
||||
private readonly IBindable<WorkingBeatmap> beatmap = new Bindable<WorkingBeatmap>();
|
||||
|
||||
private Bindable<double> startTimeBindable;
|
||||
|
||||
[Resolved]
|
||||
private IPlacementHandler placementHandler { get; set; }
|
||||
|
||||
@ -49,13 +53,12 @@ namespace osu.Game.Rulesets.Edit
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(IBindable<WorkingBeatmap> beatmap, IAdjustableClock clock)
|
||||
private void load(IBindable<WorkingBeatmap> beatmap)
|
||||
{
|
||||
this.beatmap.BindTo(beatmap);
|
||||
|
||||
EditorClock = clock;
|
||||
|
||||
ApplyDefaultsToHitObject();
|
||||
startTimeBindable = HitObject.StartTimeBindable.GetBoundCopy();
|
||||
startTimeBindable.BindValueChanged(_ => ApplyDefaultsToHitObject(), true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -81,9 +84,6 @@ namespace osu.Game.Rulesets.Edit
|
||||
PlacementActive = false;
|
||||
}
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
private IFrameBasedClock editorClock { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Updates the position of this <see cref="PlacementBlueprint"/> to a new screen-space position.
|
||||
/// </summary>
|
||||
@ -91,11 +91,11 @@ namespace osu.Game.Rulesets.Edit
|
||||
public virtual void UpdatePosition(SnapResult snapResult)
|
||||
{
|
||||
if (!PlacementActive)
|
||||
HitObject.StartTime = snapResult.Time ?? editorClock?.CurrentTime ?? Time.Current;
|
||||
HitObject.StartTime = snapResult.Time ?? EditorClock?.CurrentTime ?? Time.Current;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes <see cref="Objects.HitObject.ApplyDefaults(ControlPointInfo,BeatmapDifficulty)"/>,
|
||||
/// Invokes <see cref="Objects.HitObject.ApplyDefaults(ControlPointInfo,BeatmapDifficulty, CancellationToken)"/>,
|
||||
/// refreshing <see cref="Objects.HitObject.NestedHitObjects"/> and parameters for the <see cref="HitObject"/>.
|
||||
/// </summary>
|
||||
protected void ApplyDefaultsToHitObject() => HitObject.ApplyDefaults(beatmap.Value.Beatmap.ControlPointInfo, beatmap.Value.Beatmap.BeatmapInfo.BaseDifficulty);
|
||||
|
@ -1,6 +1,7 @@
|
||||
// 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.Game.Rulesets.UI;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Edit
|
||||
@ -20,10 +21,13 @@ namespace osu.Game.Rulesets.Edit
|
||||
/// </summary>
|
||||
public double? Time;
|
||||
|
||||
public SnapResult(Vector2 screenSpacePosition, double? time)
|
||||
public readonly Playfield Playfield;
|
||||
|
||||
public SnapResult(Vector2 screenSpacePosition, double? time, Playfield playfield = null)
|
||||
{
|
||||
ScreenSpacePosition = screenSpacePosition;
|
||||
Time = time;
|
||||
Playfield = playfield;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using JetBrains.Annotations;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Framework.Bindables;
|
||||
@ -99,7 +100,8 @@ namespace osu.Game.Rulesets.Objects
|
||||
/// </summary>
|
||||
/// <param name="controlPointInfo">The control points.</param>
|
||||
/// <param name="difficulty">The difficulty settings to use.</param>
|
||||
public void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
public void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
||||
|
||||
@ -108,7 +110,7 @@ namespace osu.Game.Rulesets.Objects
|
||||
|
||||
nestedHitObjects.Clear();
|
||||
|
||||
CreateNestedHitObjects();
|
||||
CreateNestedHitObjects(cancellationToken);
|
||||
|
||||
if (this is IHasComboInformation hasCombo)
|
||||
{
|
||||
@ -122,7 +124,7 @@ namespace osu.Game.Rulesets.Objects
|
||||
nestedHitObjects.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime));
|
||||
|
||||
foreach (var h in nestedHitObjects)
|
||||
h.ApplyDefaults(controlPointInfo, difficulty);
|
||||
h.ApplyDefaults(controlPointInfo, difficulty, cancellationToken);
|
||||
|
||||
DefaultsApplied?.Invoke(this);
|
||||
}
|
||||
@ -136,6 +138,14 @@ namespace osu.Game.Rulesets.Objects
|
||||
HitWindows?.SetDifficulty(difficulty.OverallDifficulty);
|
||||
}
|
||||
|
||||
protected virtual void CreateNestedHitObjects(CancellationToken cancellationToken)
|
||||
{
|
||||
// ReSharper disable once MethodSupportsCancellation (https://youtrack.jetbrains.com/issue/RIDER-44520)
|
||||
#pragma warning disable 618
|
||||
CreateNestedHitObjects();
|
||||
#pragma warning restore 618
|
||||
}
|
||||
|
||||
protected virtual void CreateNestedHitObjects()
|
||||
{
|
||||
}
|
||||
@ -165,10 +175,10 @@ namespace osu.Game.Rulesets.Objects
|
||||
/// Returns the end time of this object.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This returns the <see cref="IHasEndTime.EndTime"/> where available, falling back to <see cref="HitObject.StartTime"/> otherwise.
|
||||
/// This returns the <see cref="IHasDuration.EndTime"/> where available, falling back to <see cref="HitObject.StartTime"/> otherwise.
|
||||
/// </remarks>
|
||||
/// <param name="hitObject">The object.</param>
|
||||
/// <returns>The end time of this object.</returns>
|
||||
public static double GetEndTime(this HitObject hitObject) => (hitObject as IHasEndTime)?.EndTime ?? hitObject.StartTime;
|
||||
public static double GetEndTime(this HitObject hitObject) => (hitObject as IHasDuration)?.EndTime ?? hitObject.StartTime;
|
||||
}
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch
|
||||
};
|
||||
}
|
||||
|
||||
protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double endTime)
|
||||
protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration)
|
||||
{
|
||||
// Convert spinners don't create the new combo themselves, but force the next non-spinner hitobject to create a new combo
|
||||
// Their combo offset is still added to that next hitobject's combo index
|
||||
@ -65,11 +65,11 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch
|
||||
|
||||
return new ConvertSpinner
|
||||
{
|
||||
EndTime = endTime
|
||||
Duration = duration
|
||||
};
|
||||
}
|
||||
|
||||
protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double endTime)
|
||||
protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
@ -8,11 +8,11 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch
|
||||
/// <summary>
|
||||
/// Legacy osu!catch Spinner-type, used for parsing Beatmaps.
|
||||
/// </summary>
|
||||
internal sealed class ConvertSpinner : ConvertHitObject, IHasEndTime, IHasXPosition, IHasCombo
|
||||
internal sealed class ConvertSpinner : ConvertHitObject, IHasDuration, IHasXPosition, IHasCombo
|
||||
{
|
||||
public double EndTime { get; set; }
|
||||
public double EndTime => StartTime + Duration;
|
||||
|
||||
public double Duration => EndTime - StartTime;
|
||||
public double Duration { get; set; }
|
||||
|
||||
public float X => 256; // Required for CatchBeatmapConverter
|
||||
|
||||
|
@ -189,9 +189,9 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
}
|
||||
else if (type.HasFlag(LegacyHitObjectType.Spinner))
|
||||
{
|
||||
double endTime = Math.Max(startTime, Parsing.ParseDouble(split[5]) + Offset);
|
||||
double duration = Math.Max(0, Parsing.ParseDouble(split[5]) + Offset - startTime);
|
||||
|
||||
result = CreateSpinner(new Vector2(512, 384) / 2, combo, comboOffset, endTime);
|
||||
result = CreateSpinner(new Vector2(512, 384) / 2, combo, comboOffset, duration);
|
||||
|
||||
if (split.Length > 6)
|
||||
readCustomSampleBanks(split[6], bankInfo);
|
||||
@ -209,7 +209,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
readCustomSampleBanks(string.Join(":", ss.Skip(1)), bankInfo);
|
||||
}
|
||||
|
||||
result = CreateHold(pos, combo, comboOffset, endTime + Offset);
|
||||
result = CreateHold(pos, combo, comboOffset, endTime + Offset - startTime);
|
||||
}
|
||||
|
||||
if (result == null)
|
||||
@ -321,9 +321,9 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
/// <param name="position">The position of the hit object.</param>
|
||||
/// <param name="newCombo">Whether the hit object creates a new combo.</param>
|
||||
/// <param name="comboOffset">When starting a new combo, the offset of the new combo relative to the current one.</param>
|
||||
/// <param name="endTime">The spinner end time.</param>
|
||||
/// <param name="duration">The spinner duration.</param>
|
||||
/// <returns>The hit object.</returns>
|
||||
protected abstract HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double endTime);
|
||||
protected abstract HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a legacy Hold-type hit object.
|
||||
@ -331,8 +331,8 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
/// <param name="position">The position of the hit object.</param>
|
||||
/// <param name="newCombo">Whether the hit object creates a new combo.</param>
|
||||
/// <param name="comboOffset">When starting a new combo, the offset of the new combo relative to the current one.</param>
|
||||
/// <param name="endTime">The hold end time.</param>
|
||||
protected abstract HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double endTime);
|
||||
/// <param name="duration">The hold duration.</param>
|
||||
protected abstract HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration);
|
||||
|
||||
private List<HitSampleInfo> convertSoundType(LegacyHitSoundType type, SampleBankInfo bankInfo)
|
||||
{
|
||||
|
@ -9,7 +9,10 @@ using osu.Game.Beatmaps.ControlPoints;
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Legacy
|
||||
{
|
||||
internal abstract class ConvertSlider : ConvertHitObject, IHasCurve, IHasLegacyLastTickOffset
|
||||
internal abstract class ConvertSlider : ConvertHitObject, IHasPathWithRepeats, IHasLegacyLastTickOffset,
|
||||
#pragma warning disable 618
|
||||
IHasCurve
|
||||
#pragma warning restore 618
|
||||
{
|
||||
/// <summary>
|
||||
/// Scoring distance with a speed-adjusted beat length of 1 second.
|
||||
@ -26,13 +29,13 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
public List<IList<HitSampleInfo>> NodeSamples { get; set; }
|
||||
public int RepeatCount { get; set; }
|
||||
|
||||
public double EndTime
|
||||
public double Duration
|
||||
{
|
||||
get => StartTime + this.SpanCount() * Distance / Velocity;
|
||||
get => this.SpanCount() * Distance / Velocity;
|
||||
set => throw new System.NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed.
|
||||
}
|
||||
|
||||
public double Duration => EndTime - StartTime;
|
||||
public double EndTime => StartTime + Duration;
|
||||
|
||||
public double Velocity = 1;
|
||||
|
||||
|
@ -37,21 +37,21 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
|
||||
};
|
||||
}
|
||||
|
||||
protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double endTime)
|
||||
protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration)
|
||||
{
|
||||
return new ConvertSpinner
|
||||
{
|
||||
X = position.X,
|
||||
EndTime = endTime
|
||||
Duration = duration
|
||||
};
|
||||
}
|
||||
|
||||
protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double endTime)
|
||||
protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration)
|
||||
{
|
||||
return new ConvertHold
|
||||
{
|
||||
X = position.X,
|
||||
EndTime = endTime
|
||||
Duration = duration
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -5,12 +5,12 @@ using osu.Game.Rulesets.Objects.Types;
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Legacy.Mania
|
||||
{
|
||||
internal sealed class ConvertHold : ConvertHitObject, IHasXPosition, IHasEndTime
|
||||
internal sealed class ConvertHold : ConvertHitObject, IHasXPosition, IHasDuration
|
||||
{
|
||||
public float X { get; set; }
|
||||
|
||||
public double EndTime { get; set; }
|
||||
public double Duration { get; set; }
|
||||
|
||||
public double Duration => EndTime - StartTime;
|
||||
public double EndTime => StartTime + Duration;
|
||||
}
|
||||
}
|
||||
|
@ -8,11 +8,11 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
|
||||
/// <summary>
|
||||
/// Legacy osu!mania Spinner-type, used for parsing Beatmaps.
|
||||
/// </summary>
|
||||
internal sealed class ConvertSpinner : ConvertHitObject, IHasEndTime, IHasXPosition
|
||||
internal sealed class ConvertSpinner : ConvertHitObject, IHasDuration, IHasXPosition
|
||||
{
|
||||
public double EndTime { get; set; }
|
||||
public double Duration { get; set; }
|
||||
|
||||
public double Duration => EndTime - StartTime;
|
||||
public double EndTime => StartTime + Duration;
|
||||
|
||||
public float X { get; set; }
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
|
||||
};
|
||||
}
|
||||
|
||||
protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double endTime)
|
||||
protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration)
|
||||
{
|
||||
// Convert spinners don't create the new combo themselves, but force the next non-spinner hitobject to create a new combo
|
||||
// Their combo offset is still added to that next hitobject's combo index
|
||||
@ -66,11 +66,11 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
|
||||
return new ConvertSpinner
|
||||
{
|
||||
Position = position,
|
||||
EndTime = endTime
|
||||
Duration = duration
|
||||
};
|
||||
}
|
||||
|
||||
protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double endTime)
|
||||
protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
@ -9,11 +9,11 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
|
||||
/// <summary>
|
||||
/// Legacy osu! Spinner-type, used for parsing Beatmaps.
|
||||
/// </summary>
|
||||
internal sealed class ConvertSpinner : ConvertHitObject, IHasEndTime, IHasPosition, IHasCombo
|
||||
internal sealed class ConvertSpinner : ConvertHitObject, IHasDuration, IHasPosition, IHasCombo
|
||||
{
|
||||
public double EndTime { get; set; }
|
||||
public double Duration { get; set; }
|
||||
|
||||
public double Duration => EndTime - StartTime;
|
||||
public double EndTime => StartTime + Duration;
|
||||
|
||||
public Vector2 Position { get; set; }
|
||||
|
||||
|
@ -33,15 +33,15 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko
|
||||
};
|
||||
}
|
||||
|
||||
protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double endTime)
|
||||
protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration)
|
||||
{
|
||||
return new ConvertSpinner
|
||||
{
|
||||
EndTime = endTime
|
||||
Duration = duration
|
||||
};
|
||||
}
|
||||
|
||||
protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double endTime)
|
||||
protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
@ -8,10 +8,10 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko
|
||||
/// <summary>
|
||||
/// Legacy osu!taiko Spinner-type, used for parsing Beatmaps.
|
||||
/// </summary>
|
||||
internal sealed class ConvertSpinner : ConvertHitObject, IHasEndTime
|
||||
internal sealed class ConvertSpinner : ConvertHitObject, IHasDuration
|
||||
{
|
||||
public double EndTime { get; set; }
|
||||
public double Duration { get; set; }
|
||||
|
||||
public double Duration => EndTime - StartTime;
|
||||
public double EndTime => StartTime + Duration;
|
||||
}
|
||||
}
|
||||
|
@ -4,13 +4,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
namespace osu.Game.Rulesets.Objects
|
||||
{
|
||||
public static class SliderEventGenerator
|
||||
{
|
||||
[Obsolete("Use the overload with cancellation support instead.")] // can be removed 20201115
|
||||
public static IEnumerable<SliderEventDescriptor> Generate(double startTime, double spanDuration, double velocity, double tickDistance, double totalDistance, int spanCount,
|
||||
double? legacyLastTickOffset)
|
||||
{
|
||||
return Generate(startTime, spanDuration, velocity, tickDistance, totalDistance, spanCount, legacyLastTickOffset, default);
|
||||
}
|
||||
|
||||
// ReSharper disable once MethodOverloadWithOptionalParameter
|
||||
public static IEnumerable<SliderEventDescriptor> Generate(double startTime, double spanDuration, double velocity, double tickDistance, double totalDistance, int spanCount,
|
||||
double? legacyLastTickOffset, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// A very lenient maximum length of a slider for ticks to be generated.
|
||||
// This exists for edge cases such as /b/1573664 where the beatmap has been edited by the user, and should never be reached in normal usage.
|
||||
@ -37,7 +46,7 @@ namespace osu.Game.Rulesets.Objects
|
||||
var spanStartTime = startTime + span * spanDuration;
|
||||
var reversed = span % 2 == 1;
|
||||
|
||||
var ticks = generateTicks(span, spanStartTime, spanDuration, reversed, length, tickDistance, minDistanceFromEnd);
|
||||
var ticks = generateTicks(span, spanStartTime, spanDuration, reversed, length, tickDistance, minDistanceFromEnd, cancellationToken);
|
||||
|
||||
if (reversed)
|
||||
{
|
||||
@ -108,12 +117,15 @@ namespace osu.Game.Rulesets.Objects
|
||||
/// <param name="length">The length of the path.</param>
|
||||
/// <param name="tickDistance">The distance between each tick.</param>
|
||||
/// <param name="minDistanceFromEnd">The distance from the end of the path at which ticks are not allowed to be added.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>A <see cref="SliderEventDescriptor"/> for each tick. If <paramref name="reversed"/> is true, the ticks will be returned in reverse-StartTime order.</returns>
|
||||
private static IEnumerable<SliderEventDescriptor> generateTicks(int spanIndex, double spanStartTime, double spanDuration, bool reversed, double length, double tickDistance,
|
||||
double minDistanceFromEnd)
|
||||
double minDistanceFromEnd, CancellationToken cancellationToken = default)
|
||||
{
|
||||
for (var d = tickDistance; d <= length; d += tickDistance)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (d >= length - minDistanceFromEnd)
|
||||
break;
|
||||
|
||||
|
@ -1,13 +1,12 @@
|
||||
// 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.
|
||||
|
||||
using System;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Types
|
||||
{
|
||||
/// <summary>
|
||||
/// A HitObject that has a curve.
|
||||
/// </summary>
|
||||
[Obsolete("Use IHasPathWithRepeats instead.")] // can be removed 20201126
|
||||
public interface IHasCurve : IHasDistance, IHasRepeats
|
||||
{
|
||||
/// <summary>
|
||||
@ -16,6 +15,8 @@ namespace osu.Game.Rulesets.Objects.Types
|
||||
SliderPath Path { get; }
|
||||
}
|
||||
|
||||
#pragma warning disable 618
|
||||
[Obsolete("Use IHasPathWithRepeats instead.")] // can be removed 20201126
|
||||
public static class HasCurveExtensions
|
||||
{
|
||||
/// <summary>
|
||||
@ -50,4 +51,5 @@ namespace osu.Game.Rulesets.Objects.Types
|
||||
public static int SpanAt(this IHasCurve obj, double progress)
|
||||
=> (int)(progress * obj.SpanCount());
|
||||
}
|
||||
#pragma warning restore 618
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ namespace osu.Game.Rulesets.Objects.Types
|
||||
/// <summary>
|
||||
/// A HitObject that has a positional length.
|
||||
/// </summary>
|
||||
public interface IHasDistance : IHasEndTime
|
||||
public interface IHasDistance : IHasDuration
|
||||
{
|
||||
/// <summary>
|
||||
/// The positional length of the HitObject.
|
||||
|
34
osu.Game/Rulesets/Objects/Types/IHasDuration.cs
Normal file
34
osu.Game/Rulesets/Objects/Types/IHasDuration.cs
Normal file
@ -0,0 +1,34 @@
|
||||
// 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 Newtonsoft.Json;
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Types
|
||||
{
|
||||
/// <summary>
|
||||
/// A HitObject that ends at a different time than its start time.
|
||||
/// </summary>
|
||||
#pragma warning disable 618
|
||||
public interface IHasDuration : IHasEndTime
|
||||
#pragma warning restore 618
|
||||
{
|
||||
double IHasEndTime.EndTime
|
||||
{
|
||||
get => EndTime;
|
||||
set => Duration = (Duration - EndTime) + value;
|
||||
}
|
||||
|
||||
double IHasEndTime.Duration => Duration;
|
||||
|
||||
/// <summary>
|
||||
/// The time at which the HitObject ends.
|
||||
/// </summary>
|
||||
new double EndTime { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The duration of the HitObject.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
new double Duration { get; set; }
|
||||
}
|
||||
}
|
@ -1,6 +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.
|
||||
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Types
|
||||
@ -8,6 +9,7 @@ namespace osu.Game.Rulesets.Objects.Types
|
||||
/// <summary>
|
||||
/// A HitObject that ends at a different time than its start time.
|
||||
/// </summary>
|
||||
[Obsolete("Use IHasDuration instead.")] // can be removed 20201126
|
||||
public interface IHasEndTime
|
||||
{
|
||||
/// <summary>
|
||||
|
13
osu.Game/Rulesets/Objects/Types/IHasPath.cs
Normal file
13
osu.Game/Rulesets/Objects/Types/IHasPath.cs
Normal file
@ -0,0 +1,13 @@
|
||||
// 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.
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Types
|
||||
{
|
||||
public interface IHasPath : IHasDistance
|
||||
{
|
||||
/// <summary>
|
||||
/// The curve.
|
||||
/// </summary>
|
||||
SliderPath Path { get; }
|
||||
}
|
||||
}
|
50
osu.Game/Rulesets/Objects/Types/IHasPathWithRepeats.cs
Normal file
50
osu.Game/Rulesets/Objects/Types/IHasPathWithRepeats.cs
Normal file
@ -0,0 +1,50 @@
|
||||
// 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 osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Types
|
||||
{
|
||||
/// <summary>
|
||||
/// A HitObject that has a curve.
|
||||
/// </summary>
|
||||
// ReSharper disable once RedundantExtendsListEntry
|
||||
public interface IHasPathWithRepeats : IHasPath, IHasRepeats
|
||||
{
|
||||
}
|
||||
|
||||
public static class HasPathWithRepeatsExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Computes the position on the curve relative to how much of the <see cref="HitObject"/> has been completed.
|
||||
/// </summary>
|
||||
/// <param name="obj">The curve.</param>
|
||||
/// <param name="progress">[0, 1] where 0 is the start time of the <see cref="HitObject"/> and 1 is the end time of the <see cref="HitObject"/>.</param>
|
||||
/// <returns>The position on the curve.</returns>
|
||||
public static Vector2 CurvePositionAt(this IHasPathWithRepeats obj, double progress)
|
||||
=> obj.Path.PositionAt(obj.ProgressAt(progress));
|
||||
|
||||
/// <summary>
|
||||
/// Computes the progress along the curve relative to how much of the <see cref="HitObject"/> has been completed.
|
||||
/// </summary>
|
||||
/// <param name="obj">The curve.</param>
|
||||
/// <param name="progress">[0, 1] where 0 is the start time of the <see cref="HitObject"/> and 1 is the end time of the <see cref="HitObject"/>.</param>
|
||||
/// <returns>[0, 1] where 0 is the beginning of the curve and 1 is the end of the curve.</returns>
|
||||
public static double ProgressAt(this IHasPathWithRepeats obj, double progress)
|
||||
{
|
||||
double p = progress * obj.SpanCount() % 1;
|
||||
if (obj.SpanAt(progress) % 2 == 1)
|
||||
p = 1 - p;
|
||||
return p;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines which span of the curve the progress point is on.
|
||||
/// </summary>
|
||||
/// <param name="obj">The curve.</param>
|
||||
/// <param name="progress">[0, 1] where 0 is the beginning of the curve and 1 is the end of the curve.</param>
|
||||
/// <returns>[0, SpanCount) where 0 is the first run.</returns>
|
||||
public static int SpanAt(this IHasPathWithRepeats obj, double progress)
|
||||
=> (int)(progress * obj.SpanCount());
|
||||
}
|
||||
}
|
@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Objects.Types
|
||||
/// <summary>
|
||||
/// A HitObject that spans some length.
|
||||
/// </summary>
|
||||
public interface IHasRepeats : IHasEndTime
|
||||
public interface IHasRepeats : IHasDuration
|
||||
{
|
||||
/// <summary>
|
||||
/// The amount of times the HitObject repeats.
|
||||
|
@ -3,9 +3,11 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Rulesets.Scoring
|
||||
{
|
||||
@ -47,6 +49,8 @@ namespace osu.Game.Rulesets.Scoring
|
||||
private double targetMinimumHealth;
|
||||
private double drainRate = 1;
|
||||
|
||||
private PeriodTracker noDrainPeriodTracker;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="DrainingHealthProcessor"/>.
|
||||
/// </summary>
|
||||
@ -60,14 +64,14 @@ namespace osu.Game.Rulesets.Scoring
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (!IsBreakTime.Value)
|
||||
{
|
||||
// When jumping in and out of gameplay time within a single frame, health should only be drained for the period within the gameplay time
|
||||
double lastGameplayTime = Math.Clamp(Time.Current - Time.Elapsed, drainStartTime, gameplayEndTime);
|
||||
double currentGameplayTime = Math.Clamp(Time.Current, drainStartTime, gameplayEndTime);
|
||||
if (noDrainPeriodTracker?.IsInAny(Time.Current) == true)
|
||||
return;
|
||||
|
||||
Health.Value -= drainRate * (currentGameplayTime - lastGameplayTime);
|
||||
}
|
||||
// When jumping in and out of gameplay time within a single frame, health should only be drained for the period within the gameplay time
|
||||
double lastGameplayTime = Math.Clamp(Time.Current - Time.Elapsed, drainStartTime, gameplayEndTime);
|
||||
double currentGameplayTime = Math.Clamp(Time.Current, drainStartTime, gameplayEndTime);
|
||||
|
||||
Health.Value -= drainRate * (currentGameplayTime - lastGameplayTime);
|
||||
}
|
||||
|
||||
public override void ApplyBeatmap(IBeatmap beatmap)
|
||||
@ -77,6 +81,19 @@ namespace osu.Game.Rulesets.Scoring
|
||||
if (beatmap.HitObjects.Count > 0)
|
||||
gameplayEndTime = beatmap.HitObjects[^1].GetEndTime();
|
||||
|
||||
noDrainPeriodTracker = new PeriodTracker(beatmap.Breaks.Select(breakPeriod => new Period(
|
||||
beatmap.HitObjects
|
||||
.Select(hitObject => hitObject.GetEndTime())
|
||||
.Where(endTime => endTime <= breakPeriod.StartTime)
|
||||
.DefaultIfEmpty(double.MinValue)
|
||||
.Last(),
|
||||
beatmap.HitObjects
|
||||
.Select(hitObject => hitObject.StartTime)
|
||||
.Where(startTime => startTime >= breakPeriod.EndTime)
|
||||
.DefaultIfEmpty(double.MaxValue)
|
||||
.First()
|
||||
)));
|
||||
|
||||
targetMinimumHealth = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, min_health_target, mid_health_target, max_health_target);
|
||||
|
||||
base.ApplyBeatmap(beatmap);
|
||||
|
@ -26,11 +26,6 @@ namespace osu.Game.Rulesets.Scoring
|
||||
/// </summary>
|
||||
public readonly BindableDouble Health = new BindableDouble(1) { MinValue = 0, MaxValue = 1 };
|
||||
|
||||
/// <summary>
|
||||
/// Whether gameplay is currently in a break.
|
||||
/// </summary>
|
||||
public readonly IBindable<bool> IsBreakTime = new Bindable<bool>();
|
||||
|
||||
/// <summary>
|
||||
/// Whether this ScoreProcessor has already triggered the failed state.
|
||||
/// </summary>
|
||||
|
@ -9,6 +9,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Layout;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.UI.Scrolling
|
||||
{
|
||||
@ -78,6 +79,98 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
||||
hitObjectInitialStateCache.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a position in screen space, return the time within this column.
|
||||
/// </summary>
|
||||
public double TimeAtScreenSpacePosition(Vector2 screenSpacePosition)
|
||||
{
|
||||
// convert to local space of column so we can snap and fetch correct location.
|
||||
Vector2 localPosition = ToLocalSpace(screenSpacePosition);
|
||||
|
||||
float position = 0;
|
||||
|
||||
switch (scrollingInfo.Direction.Value)
|
||||
{
|
||||
case ScrollingDirection.Up:
|
||||
case ScrollingDirection.Down:
|
||||
position = localPosition.Y;
|
||||
break;
|
||||
|
||||
case ScrollingDirection.Right:
|
||||
case ScrollingDirection.Left:
|
||||
position = localPosition.X;
|
||||
break;
|
||||
}
|
||||
|
||||
flipPositionIfRequired(ref position);
|
||||
|
||||
return scrollingInfo.Algorithm.TimeAt(position, Time.Current, scrollingInfo.TimeRange.Value, getLength());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a time, return the screen space position within this column.
|
||||
/// </summary>
|
||||
public Vector2 ScreenSpacePositionAtTime(double time)
|
||||
{
|
||||
var pos = scrollingInfo.Algorithm.PositionAt(time, Time.Current, scrollingInfo.TimeRange.Value, getLength());
|
||||
|
||||
flipPositionIfRequired(ref pos);
|
||||
|
||||
switch (scrollingInfo.Direction.Value)
|
||||
{
|
||||
case ScrollingDirection.Up:
|
||||
case ScrollingDirection.Down:
|
||||
return ToScreenSpace(new Vector2(getBreadth() / 2, pos));
|
||||
|
||||
default:
|
||||
return ToScreenSpace(new Vector2(pos, getBreadth() / 2));
|
||||
}
|
||||
}
|
||||
|
||||
private float getLength()
|
||||
{
|
||||
switch (scrollingInfo.Direction.Value)
|
||||
{
|
||||
case ScrollingDirection.Left:
|
||||
case ScrollingDirection.Right:
|
||||
return DrawWidth;
|
||||
|
||||
default:
|
||||
return DrawHeight;
|
||||
}
|
||||
}
|
||||
|
||||
private float getBreadth()
|
||||
{
|
||||
switch (scrollingInfo.Direction.Value)
|
||||
{
|
||||
case ScrollingDirection.Up:
|
||||
case ScrollingDirection.Down:
|
||||
return DrawWidth;
|
||||
|
||||
default:
|
||||
return DrawHeight;
|
||||
}
|
||||
}
|
||||
|
||||
private void flipPositionIfRequired(ref float position)
|
||||
{
|
||||
// We're dealing with screen coordinates in which the position decreases towards the centre of the screen resulting in an increase in start time.
|
||||
// The scrolling algorithm instead assumes a top anchor meaning an increase in time corresponds to an increase in position,
|
||||
// so when scrolling downwards the coordinates need to be flipped.
|
||||
|
||||
switch (scrollingInfo.Direction.Value)
|
||||
{
|
||||
case ScrollingDirection.Down:
|
||||
position = DrawHeight - position;
|
||||
break;
|
||||
|
||||
case ScrollingDirection.Right:
|
||||
position = DrawWidth - position;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void onDefaultsApplied(DrawableHitObject drawableObject)
|
||||
{
|
||||
// The cache may not exist if the hitobject state hasn't been computed yet (e.g. if the hitobject was added + defaults applied in the same frame).
|
||||
@ -177,7 +270,7 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
||||
// Cant use AddOnce() since the delegate is re-constructed every invocation
|
||||
private void computeInitialStateRecursive(DrawableHitObject hitObject) => hitObject.Schedule(() =>
|
||||
{
|
||||
if (hitObject.HitObject is IHasEndTime e)
|
||||
if (hitObject.HitObject is IHasDuration e)
|
||||
{
|
||||
switch (direction.Value)
|
||||
{
|
||||
|
@ -4,6 +4,7 @@
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.UI.Scrolling
|
||||
{
|
||||
@ -23,6 +24,18 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
||||
Direction.BindTo(ScrollingInfo.Direction);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a position in screen space, return the time within this column.
|
||||
/// </summary>
|
||||
public virtual double TimeAtScreenSpacePosition(Vector2 screenSpacePosition) =>
|
||||
((ScrollingHitObjectContainer)HitObjectContainer).TimeAtScreenSpacePosition(screenSpacePosition);
|
||||
|
||||
/// <summary>
|
||||
/// Given a time, return the screen space position within this column.
|
||||
/// </summary>
|
||||
public virtual Vector2 ScreenSpacePositionAtTime(double time)
|
||||
=> ((ScrollingHitObjectContainer)HitObjectContainer).ScreenSpacePositionAtTime(time);
|
||||
|
||||
protected sealed override HitObjectContainer CreateHitObjectContainer() => new ScrollingHitObjectContainer();
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,6 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
@ -26,7 +25,7 @@ namespace osu.Game.Screens.Edit.Components
|
||||
private IconButton playButton;
|
||||
|
||||
[Resolved]
|
||||
private IAdjustableClock adjustableClock { get; set; }
|
||||
private EditorClock editorClock { get; set; }
|
||||
|
||||
private readonly BindableNumber<double> tempo = new BindableDouble(1);
|
||||
|
||||
@ -87,17 +86,17 @@ namespace osu.Game.Screens.Edit.Components
|
||||
|
||||
private void togglePause()
|
||||
{
|
||||
if (adjustableClock.IsRunning)
|
||||
adjustableClock.Stop();
|
||||
if (editorClock.IsRunning)
|
||||
editorClock.Stop();
|
||||
else
|
||||
adjustableClock.Start();
|
||||
editorClock.Start();
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
playButton.Icon = adjustableClock.IsRunning ? FontAwesome.Regular.PauseCircle : FontAwesome.Regular.PlayCircle;
|
||||
playButton.Icon = editorClock.IsRunning ? FontAwesome.Regular.PauseCircle : FontAwesome.Regular.PlayCircle;
|
||||
}
|
||||
|
||||
private class PlaybackTabControl : OsuTabControl<double>
|
||||
|
@ -5,7 +5,6 @@ using osu.Framework.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Components
|
||||
@ -15,7 +14,7 @@ namespace osu.Game.Screens.Edit.Components
|
||||
private readonly OsuSpriteText trackTimer;
|
||||
|
||||
[Resolved]
|
||||
private IAdjustableClock adjustableClock { get; set; }
|
||||
private EditorClock editorClock { get; set; }
|
||||
|
||||
public TimeInfoContainer()
|
||||
{
|
||||
@ -35,7 +34,7 @@ namespace osu.Game.Screens.Edit.Components
|
||||
{
|
||||
base.Update();
|
||||
|
||||
trackTimer.Text = TimeSpan.FromMilliseconds(adjustableClock.CurrentTime).ToString(@"mm\:ss\:fff");
|
||||
trackTimer.Text = TimeSpan.FromMilliseconds(editorClock.CurrentTime).ToString(@"mm\:ss\:fff");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user