mirror of
https://github.com/ppy/osu.git
synced 2025-01-15 16:03:01 +08:00
Merge branch 'master' into mania-judgements
This commit is contained in:
commit
476526714d
@ -1 +1 @@
|
|||||||
Subproject commit aa3e296968873208ca4460b00c0b82fe3aa8ff5c
|
Subproject commit 0f3db5da09d0e7c4d2ef3057030e018f34ba536e
|
39
osu.Desktop.VisualTests/Tests/TestCaseBreadcrumbs.cs
Normal file
39
osu.Desktop.VisualTests/Tests/TestCaseBreadcrumbs.cs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Desktop.VisualTests.Tests
|
||||||
|
{
|
||||||
|
internal class TestCaseBreadcrumbs : TestCase
|
||||||
|
{
|
||||||
|
public override string Description => @"breadcrumb > control";
|
||||||
|
|
||||||
|
public override void Reset()
|
||||||
|
{
|
||||||
|
base.Reset();
|
||||||
|
|
||||||
|
BreadcrumbControl<BreadcrumbTab> c;
|
||||||
|
Add(c = new BreadcrumbControl<BreadcrumbTab>
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Width = 0.5f,
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep(@"first", () => c.Current.Value = BreadcrumbTab.Click);
|
||||||
|
AddStep(@"second", () => c.Current.Value = BreadcrumbTab.The);
|
||||||
|
AddStep(@"third", () => c.Current.Value = BreadcrumbTab.Circles);
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum BreadcrumbTab
|
||||||
|
{
|
||||||
|
Click,
|
||||||
|
The,
|
||||||
|
Circles,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -223,6 +223,7 @@
|
|||||||
<Compile Include="Tests\TestCaseDrawableRoom.cs" />
|
<Compile Include="Tests\TestCaseDrawableRoom.cs" />
|
||||||
<Compile Include="Tests\TestCaseUserPanel.cs" />
|
<Compile Include="Tests\TestCaseUserPanel.cs" />
|
||||||
<Compile Include="Tests\TestCaseDirect.cs" />
|
<Compile Include="Tests\TestCaseDirect.cs" />
|
||||||
|
<Compile Include="Tests\TestCaseBreadcrumbs.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup />
|
<ItemGroup />
|
||||||
<ItemGroup />
|
<ItemGroup />
|
||||||
|
@ -13,6 +13,7 @@ using osu.Game.Rulesets.Mania.MathUtils;
|
|||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy;
|
using osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy;
|
||||||
using OpenTK;
|
using OpenTK;
|
||||||
|
using osu.Game.Audio;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Beatmaps
|
namespace osu.Game.Rulesets.Mania.Beatmaps
|
||||||
{
|
{
|
||||||
@ -161,9 +162,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
|||||||
pattern.Add(new HoldNote
|
pattern.Add(new HoldNote
|
||||||
{
|
{
|
||||||
StartTime = HitObject.StartTime,
|
StartTime = HitObject.StartTime,
|
||||||
Samples = HitObject.Samples,
|
|
||||||
Duration = endTimeData.Duration,
|
Duration = endTimeData.Duration,
|
||||||
Column = column,
|
Column = column,
|
||||||
|
Head = { Samples = sampleInfoListAt(HitObject.StartTime) },
|
||||||
|
Tail = { Samples = sampleInfoListAt(endTimeData.EndTime) },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else if (positionData != null)
|
else if (positionData != null)
|
||||||
@ -178,6 +180,24 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
|||||||
|
|
||||||
return pattern;
|
return pattern;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the sample info list at a point in time.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="time">The time to retrieve the sample info list from.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private SampleInfoList sampleInfoListAt(double time)
|
||||||
|
{
|
||||||
|
var curveData = HitObject as IHasCurve;
|
||||||
|
|
||||||
|
if (curveData == null)
|
||||||
|
return HitObject.Samples;
|
||||||
|
|
||||||
|
double segmentTime = (curveData.EndTime - HitObject.StartTime) / curveData.RepeatCount;
|
||||||
|
|
||||||
|
int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime);
|
||||||
|
return curveData.RepeatSamples[index];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
21
osu.Game.Rulesets.Mania/Objects/BarLine.cs
Normal file
21
osu.Game.Rulesets.Mania/Objects/BarLine.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Objects
|
||||||
|
{
|
||||||
|
public class BarLine : ManiaHitObject
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The control point which this bar line is part of.
|
||||||
|
/// </summary>
|
||||||
|
public TimingControlPoint ControlPoint;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The index of the beat which this bar line represents within the control point.
|
||||||
|
/// This is a "major" bar line if <see cref="BeatIndex"/> % <see cref="TimingControlPoint.TimeSignature"/> == 0.
|
||||||
|
/// </summary>
|
||||||
|
public int BeatIndex;
|
||||||
|
}
|
||||||
|
}
|
74
osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs
Normal file
74
osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using OpenTK;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Visualises a <see cref="BarLine"/>. Although this derives DrawableManiaHitObject,
|
||||||
|
/// this does not handle input/sound like a normal hit object.
|
||||||
|
/// </summary>
|
||||||
|
public class DrawableBarLine : DrawableManiaHitObject<BarLine>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Height of major bar line triangles.
|
||||||
|
/// </summary>
|
||||||
|
private const float triangle_height = 12;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Offset of the major bar line triangles from the sides of the bar line.
|
||||||
|
/// </summary>
|
||||||
|
private const float triangle_offset = 9;
|
||||||
|
|
||||||
|
public DrawableBarLine(BarLine barLine)
|
||||||
|
: base(barLine)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
Height = 1;
|
||||||
|
|
||||||
|
Add(new Box
|
||||||
|
{
|
||||||
|
Name = "Bar line",
|
||||||
|
Anchor = Anchor.BottomCentre,
|
||||||
|
Origin = Anchor.BottomCentre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
});
|
||||||
|
|
||||||
|
bool isMajor = barLine.BeatIndex % (int)barLine.ControlPoint.TimeSignature == 0;
|
||||||
|
|
||||||
|
if (isMajor)
|
||||||
|
{
|
||||||
|
Add(new EquilateralTriangle
|
||||||
|
{
|
||||||
|
Name = "Left triangle",
|
||||||
|
Anchor = Anchor.BottomLeft,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Size = new Vector2(triangle_height),
|
||||||
|
X = -triangle_offset,
|
||||||
|
Rotation = 90
|
||||||
|
});
|
||||||
|
|
||||||
|
Add(new EquilateralTriangle
|
||||||
|
{
|
||||||
|
Name = "Right triangle",
|
||||||
|
Anchor = Anchor.BottomRight,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Size = new Vector2(triangle_height),
|
||||||
|
X = triangle_offset,
|
||||||
|
Rotation = -90
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isMajor && barLine.BeatIndex % 2 == 1)
|
||||||
|
Alpha = 0.2f;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateState(ArmedState state)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -58,6 +58,9 @@ namespace osu.Game.Rulesets.Mania.Objects
|
|||||||
|
|
||||||
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
|
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
|
||||||
tickSpacing = timingPoint.BeatLength / difficulty.SliderTickRate;
|
tickSpacing = timingPoint.BeatLength / difficulty.SliderTickRate;
|
||||||
|
|
||||||
|
Head.ApplyDefaults(controlPointInfo, difficulty);
|
||||||
|
Tail.ApplyDefaults(controlPointInfo, difficulty);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -6,9 +6,11 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using OpenTK;
|
using OpenTK;
|
||||||
using OpenTK.Input;
|
using OpenTK.Input;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Configuration;
|
using osu.Framework.Configuration;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Lists;
|
using osu.Framework.Lists;
|
||||||
|
using osu.Framework.MathUtils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Rulesets.Beatmaps;
|
using osu.Game.Rulesets.Beatmaps;
|
||||||
@ -85,6 +87,34 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
var maniaPlayfield = (ManiaPlayfield)Playfield;
|
||||||
|
|
||||||
|
double lastObjectTime = (Objects.LastOrDefault() as IHasEndTime)?.EndTime ?? Objects.LastOrDefault()?.StartTime ?? double.MaxValue;
|
||||||
|
|
||||||
|
SortedList<TimingControlPoint> timingPoints = Beatmap.ControlPointInfo.TimingPoints;
|
||||||
|
for (int i = 0; i < timingPoints.Count; i++)
|
||||||
|
{
|
||||||
|
TimingControlPoint point = timingPoints[i];
|
||||||
|
|
||||||
|
// Stop on the beat before the next timing point, or if there is no next timing point stop slightly past the last object
|
||||||
|
double endTime = i < timingPoints.Count - 1 ? timingPoints[i + 1].Time - point.BeatLength : lastObjectTime + point.BeatLength * (int)point.TimeSignature;
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
for (double t = timingPoints[i].Time; Precision.DefinitelyBigger(endTime, t); t += point.BeatLength, index++)
|
||||||
|
{
|
||||||
|
maniaPlayfield.Add(new DrawableBarLine(new BarLine
|
||||||
|
{
|
||||||
|
StartTime = t,
|
||||||
|
ControlPoint = point,
|
||||||
|
BeatIndex = index
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(this);
|
public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(this);
|
||||||
|
|
||||||
protected override BeatmapConverter<ManiaHitObject> CreateBeatmapConverter() => new ManiaBeatmapConverter();
|
protected override BeatmapConverter<ManiaHitObject> CreateBeatmapConverter() => new ManiaBeatmapConverter();
|
||||||
|
@ -21,6 +21,7 @@ using osu.Game.Rulesets.Mania.Timing;
|
|||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Graphics.Transforms;
|
using osu.Framework.Graphics.Transforms;
|
||||||
using osu.Framework.MathUtils;
|
using osu.Framework.MathUtils;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.UI
|
namespace osu.Game.Rulesets.Mania.UI
|
||||||
{
|
{
|
||||||
@ -57,7 +58,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
private readonly FlowContainer<Column> columns;
|
private readonly FlowContainer<Column> columns;
|
||||||
public IEnumerable<Column> Columns => columns.Children;
|
public IEnumerable<Column> Columns => columns.Children;
|
||||||
|
|
||||||
private readonly ControlPointContainer barlineContainer;
|
private readonly ControlPointContainer barLineContainer;
|
||||||
|
|
||||||
private List<Color4> normalColumnColours = new List<Color4>();
|
private List<Color4> normalColumnColours = new List<Color4>();
|
||||||
private Color4 specialColumnColour;
|
private Color4 specialColumnColour;
|
||||||
@ -75,6 +76,15 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
{
|
{
|
||||||
new Container
|
new Container
|
||||||
{
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
Name = "Masked elements",
|
||||||
Anchor = Anchor.TopCentre,
|
Anchor = Anchor.TopCentre,
|
||||||
Origin = Anchor.TopCentre,
|
Origin = Anchor.TopCentre,
|
||||||
RelativeSizeAxes = Axes.Y,
|
RelativeSizeAxes = Axes.Y,
|
||||||
@ -95,17 +105,24 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
Direction = FillDirection.Horizontal,
|
Direction = FillDirection.Horizontal,
|
||||||
Padding = new MarginPadding { Left = 1, Right = 1 },
|
Padding = new MarginPadding { Left = 1, Right = 1 },
|
||||||
Spacing = new Vector2(1, 0)
|
Spacing = new Vector2(1, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
new Container
|
new Container
|
||||||
{
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Padding = new MarginPadding { Top = HIT_TARGET_POSITION },
|
Padding = new MarginPadding { Top = HIT_TARGET_POSITION },
|
||||||
Children = new[]
|
Children = new[]
|
||||||
{
|
{
|
||||||
barlineContainer = new ControlPointContainer(timingChanges)
|
barLineContainer = new ControlPointContainer(timingChanges)
|
||||||
{
|
{
|
||||||
Name = "Bar lines",
|
Name = "Bar lines",
|
||||||
RelativeSizeAxes = Axes.Both,
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
RelativeSizeAxes = Axes.Y
|
||||||
|
// Width is set in the Update method
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -190,6 +207,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
}
|
}
|
||||||
|
|
||||||
public override void Add(DrawableHitObject<ManiaHitObject, ManiaJudgement> h) => Columns.ElementAt(h.HitObject.Column).Add(h);
|
public override void Add(DrawableHitObject<ManiaHitObject, ManiaJudgement> h) => Columns.ElementAt(h.HitObject.Column).Add(h);
|
||||||
|
public void Add(DrawableBarLine barline) => barLineContainer.Add(barline);
|
||||||
|
|
||||||
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
|
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
|
||||||
{
|
{
|
||||||
@ -224,7 +242,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
|
|
||||||
timeSpan = MathHelper.Clamp(timeSpan, time_span_min, time_span_max);
|
timeSpan = MathHelper.Clamp(timeSpan, time_span_min, time_span_max);
|
||||||
|
|
||||||
barlineContainer.TimeSpan = value;
|
barLineContainer.TimeSpan = value;
|
||||||
Columns.ForEach(c => c.ControlPointContainer.TimeSpan = value);
|
Columns.ForEach(c => c.ControlPointContainer.TimeSpan = value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -234,6 +252,13 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
TransformTo(() => TimeSpan, newTimeSpan, duration, easing, new TransformTimeSpan());
|
TransformTo(() => TimeSpan, newTimeSpan, duration, easing, new TransformTimeSpan());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
// Due to masking differences, it is not possible to get the width of the columns container automatically
|
||||||
|
// While masking on effectively only the Y-axis, so we need to set the width of the bar line container manually
|
||||||
|
barLineContainer.Width = columns.Width;
|
||||||
|
}
|
||||||
|
|
||||||
private class TransformTimeSpan : Transform<double>
|
private class TransformTimeSpan : Transform<double>
|
||||||
{
|
{
|
||||||
public override double CurrentValue
|
public override double CurrentValue
|
||||||
|
@ -62,6 +62,7 @@
|
|||||||
<Compile Include="Judgements\ManiaHitResult.cs" />
|
<Compile Include="Judgements\ManiaHitResult.cs" />
|
||||||
<Compile Include="Judgements\ManiaJudgement.cs" />
|
<Compile Include="Judgements\ManiaJudgement.cs" />
|
||||||
<Compile Include="ManiaDifficultyCalculator.cs" />
|
<Compile Include="ManiaDifficultyCalculator.cs" />
|
||||||
|
<Compile Include="Objects\Drawables\DrawableBarLine.cs" />
|
||||||
<Compile Include="Objects\Drawables\DrawableHoldNote.cs" />
|
<Compile Include="Objects\Drawables\DrawableHoldNote.cs" />
|
||||||
<Compile Include="Objects\Drawables\DrawableHoldNoteTick.cs" />
|
<Compile Include="Objects\Drawables\DrawableHoldNoteTick.cs" />
|
||||||
<Compile Include="Objects\Drawables\DrawableManiaHitObject.cs" />
|
<Compile Include="Objects\Drawables\DrawableManiaHitObject.cs" />
|
||||||
@ -70,6 +71,7 @@
|
|||||||
<Compile Include="Objects\Drawables\Pieces\NotePiece.cs" />
|
<Compile Include="Objects\Drawables\Pieces\NotePiece.cs" />
|
||||||
<Compile Include="Objects\Types\IHasColumn.cs" />
|
<Compile Include="Objects\Types\IHasColumn.cs" />
|
||||||
<Compile Include="Scoring\ManiaScoreProcessor.cs" />
|
<Compile Include="Scoring\ManiaScoreProcessor.cs" />
|
||||||
|
<Compile Include="Objects\BarLine.cs" />
|
||||||
<Compile Include="Objects\HoldNote.cs" />
|
<Compile Include="Objects\HoldNote.cs" />
|
||||||
<Compile Include="Objects\HoldNoteTick.cs" />
|
<Compile Include="Objects\HoldNoteTick.cs" />
|
||||||
<Compile Include="Objects\ManiaHitObject.cs" />
|
<Compile Include="Objects\ManiaHitObject.cs" />
|
||||||
|
@ -428,6 +428,9 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (var hitObject in beatmap.HitObjects)
|
||||||
|
hitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.Difficulty);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal enum LegacySampleBank
|
internal enum LegacySampleBank
|
||||||
|
84
osu.Game/Graphics/UserInterface/BreadcrumbControl.cs
Normal file
84
osu.Game/Graphics/UserInterface/BreadcrumbControl.cs
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using OpenTK;
|
||||||
|
using osu.Framework;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace osu.Game.Graphics.UserInterface
|
||||||
|
{
|
||||||
|
public class BreadcrumbControl<T> : OsuTabControl<T>
|
||||||
|
{
|
||||||
|
private const float padding = 10;
|
||||||
|
|
||||||
|
protected override TabItem<T> CreateTabItem(T value) => new BreadcrumbTabItem(value);
|
||||||
|
|
||||||
|
public BreadcrumbControl()
|
||||||
|
{
|
||||||
|
Height = 26;
|
||||||
|
TabContainer.Spacing = new Vector2(padding, 0f);
|
||||||
|
Current.ValueChanged += tab =>
|
||||||
|
{
|
||||||
|
foreach (var t in TabContainer.Children.OfType<BreadcrumbTabItem>())
|
||||||
|
{
|
||||||
|
var tIndex = TabContainer.IndexOf(t);
|
||||||
|
var tabIndex = TabContainer.IndexOf(TabMap[tab]);
|
||||||
|
|
||||||
|
t.State = tIndex < tabIndex ? Visibility.Hidden : Visibility.Visible;
|
||||||
|
t.Chevron.FadeTo(tIndex <= tabIndex ? 0f : 1f, 500, EasingTypes.OutQuint);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private class BreadcrumbTabItem : OsuTabItem, IStateful<Visibility>
|
||||||
|
{
|
||||||
|
public readonly TextAwesome Chevron;
|
||||||
|
|
||||||
|
//don't allow clicking between transitions and don't make the chevron clickable
|
||||||
|
protected override bool InternalContains(Vector2 screenSpacePos) => Alpha == 1f && Text.Contains(screenSpacePos);
|
||||||
|
public override bool HandleInput => State == Visibility.Visible;
|
||||||
|
|
||||||
|
private Visibility state;
|
||||||
|
public Visibility State
|
||||||
|
{
|
||||||
|
get { return state; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == state) return;
|
||||||
|
state = value;
|
||||||
|
|
||||||
|
const float transition_duration = 500;
|
||||||
|
|
||||||
|
if (State == Visibility.Visible)
|
||||||
|
{
|
||||||
|
FadeIn(transition_duration, EasingTypes.OutQuint);
|
||||||
|
ScaleTo(new Vector2(1f), transition_duration, EasingTypes.OutQuint);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FadeOut(transition_duration, EasingTypes.OutQuint);
|
||||||
|
ScaleTo(new Vector2(0.8f, 1f), transition_duration, EasingTypes.OutQuint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public BreadcrumbTabItem(T value) : base(value)
|
||||||
|
{
|
||||||
|
Text.TextSize = 16;
|
||||||
|
Padding = new MarginPadding { Right = padding + 8 }; //padding + chevron width
|
||||||
|
Add(Chevron = new TextAwesome
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreRight,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
TextSize = 12,
|
||||||
|
Icon = FontAwesome.fa_chevron_right,
|
||||||
|
Margin = new MarginPadding { Left = padding },
|
||||||
|
Alpha = 0f,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,18 +5,21 @@ using osu.Framework.Audio.Sample;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Graphics.Transforms;
|
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using OpenTK;
|
using OpenTK;
|
||||||
using OpenTK.Graphics;
|
using OpenTK.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Framework.Audio.Track;
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace osu.Game.Graphics.UserInterface
|
namespace osu.Game.Graphics.UserInterface
|
||||||
{
|
{
|
||||||
public class TwoLayerButton : ClickableContainer
|
public class TwoLayerButton : ClickableContainer
|
||||||
{
|
{
|
||||||
private readonly TextAwesome icon;
|
private readonly BouncingIcon bouncingIcon;
|
||||||
|
|
||||||
public Box IconLayer;
|
public Box IconLayer;
|
||||||
public Box TextLayer;
|
public Box TextLayer;
|
||||||
@ -95,11 +98,10 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
icon = new TextAwesome
|
bouncingIcon = new BouncingIcon
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
TextSize = 25,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -146,7 +148,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
{
|
{
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
icon.Icon = value;
|
bouncingIcon.Icon = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,58 +164,20 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
|
|
||||||
protected override bool OnHover(InputState state)
|
protected override bool OnHover(InputState state)
|
||||||
{
|
{
|
||||||
icon.ClearTransforms();
|
|
||||||
|
|
||||||
ResizeTo(SIZE_EXTENDED, transform_time, EasingTypes.OutElastic);
|
ResizeTo(SIZE_EXTENDED, transform_time, EasingTypes.OutElastic);
|
||||||
|
|
||||||
int duration = 0; //(int)(Game.Audio.BeatLength / 2);
|
|
||||||
if (duration == 0) duration = pulse_length;
|
|
||||||
|
|
||||||
IconLayer.FadeColour(HoverColour, transform_time, EasingTypes.OutElastic);
|
IconLayer.FadeColour(HoverColour, transform_time, EasingTypes.OutElastic);
|
||||||
|
|
||||||
const double offset = 0; //(1 - Game.Audio.SyncBeatProgress) * duration;
|
bouncingIcon.ScaleTo(1.1f, transform_time, EasingTypes.OutElastic);
|
||||||
double startTime = Time.Current + offset;
|
|
||||||
|
|
||||||
// basic pulse
|
|
||||||
icon.Transforms.Add(new TransformScale
|
|
||||||
{
|
|
||||||
StartValue = new Vector2(1.1f),
|
|
||||||
EndValue = Vector2.One,
|
|
||||||
StartTime = startTime,
|
|
||||||
EndTime = startTime + duration,
|
|
||||||
Easing = EasingTypes.Out,
|
|
||||||
LoopCount = -1,
|
|
||||||
LoopDelay = duration
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnHoverLost(InputState state)
|
protected override void OnHoverLost(InputState state)
|
||||||
{
|
{
|
||||||
icon.ClearTransforms();
|
|
||||||
|
|
||||||
ResizeTo(SIZE_RETRACTED, transform_time, EasingTypes.OutElastic);
|
ResizeTo(SIZE_RETRACTED, transform_time, EasingTypes.OutElastic);
|
||||||
|
|
||||||
IconLayer.FadeColour(TextLayer.Colour, transform_time, EasingTypes.OutElastic);
|
IconLayer.FadeColour(TextLayer.Colour, transform_time, EasingTypes.OutElastic);
|
||||||
|
|
||||||
int duration = 0; //(int)(Game.Audio.BeatLength);
|
bouncingIcon.ScaleTo(1, transform_time, EasingTypes.OutElastic);
|
||||||
if (duration == 0) duration = pulse_length * 2;
|
|
||||||
|
|
||||||
const double offset = 0; //(1 - Game.Audio.SyncBeatProgress) * duration;
|
|
||||||
double startTime = Time.Current + offset;
|
|
||||||
|
|
||||||
// slow pulse
|
|
||||||
icon.Transforms.Add(new TransformScale
|
|
||||||
{
|
|
||||||
StartValue = new Vector2(1.1f),
|
|
||||||
EndValue = Vector2.One,
|
|
||||||
StartTime = startTime,
|
|
||||||
EndTime = startTime + duration,
|
|
||||||
Easing = EasingTypes.Out,
|
|
||||||
LoopCount = -1,
|
|
||||||
LoopDelay = duration
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
|
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
|
||||||
@ -239,5 +203,45 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
|
|
||||||
return base.OnClick(state);
|
return base.OnClick(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class BouncingIcon : BeatSyncedContainer
|
||||||
|
{
|
||||||
|
private const double beat_in_time = 60;
|
||||||
|
|
||||||
|
private readonly TextAwesome icon;
|
||||||
|
|
||||||
|
public FontAwesome Icon { set { icon.Icon = value; } }
|
||||||
|
|
||||||
|
public BouncingIcon()
|
||||||
|
{
|
||||||
|
EarlyActivationMilliseconds = beat_in_time;
|
||||||
|
AutoSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
icon = new TextAwesome
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
TextSize = 25
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes)
|
||||||
|
{
|
||||||
|
base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes);
|
||||||
|
|
||||||
|
var beatLength = timingPoint.BeatLength;
|
||||||
|
|
||||||
|
float amplitudeAdjust = Math.Min(1, 0.4f + amplitudes.Maximum);
|
||||||
|
|
||||||
|
if (beatIndex < 0) return;
|
||||||
|
|
||||||
|
icon.ScaleTo(1 - 0.1f * amplitudeAdjust, beat_in_time, EasingTypes.Out);
|
||||||
|
using (icon.BeginDelayedSequence(beat_in_time))
|
||||||
|
icon.ScaleTo(1, beatLength * 2, EasingTypes.OutQuint);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -27,6 +27,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
public class ModButton : ModButtonEmpty, IHasTooltip
|
public class ModButton : ModButtonEmpty, IHasTooltip
|
||||||
{
|
{
|
||||||
private ModIcon foregroundIcon;
|
private ModIcon foregroundIcon;
|
||||||
|
private ModIcon backgroundIcon;
|
||||||
private readonly SpriteText text;
|
private readonly SpriteText text;
|
||||||
private readonly Container<ModIcon> iconsContainer;
|
private readonly Container<ModIcon> iconsContainer;
|
||||||
private SampleChannel sampleOn, sampleOff;
|
private SampleChannel sampleOn, sampleOff;
|
||||||
@ -35,38 +36,67 @@ namespace osu.Game.Overlays.Mods
|
|||||||
|
|
||||||
public string TooltipText => (SelectedMod?.Description ?? Mods.FirstOrDefault()?.Description) ?? string.Empty;
|
public string TooltipText => (SelectedMod?.Description ?? Mods.FirstOrDefault()?.Description) ?? string.Empty;
|
||||||
|
|
||||||
private int _selectedIndex = -1;
|
private const EasingTypes mod_switch_easing = EasingTypes.InOutSine;
|
||||||
private int selectedIndex
|
private const double mod_switch_duration = 120;
|
||||||
|
|
||||||
|
// A selected index of -1 means not selected.
|
||||||
|
private int selectedIndex = -1;
|
||||||
|
|
||||||
|
protected int SelectedIndex
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return _selectedIndex;
|
return selectedIndex;
|
||||||
}
|
}
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (value == _selectedIndex) return;
|
if (value == selectedIndex) return;
|
||||||
_selectedIndex = value;
|
|
||||||
|
int direction = value < selectedIndex ? -1 : 1;
|
||||||
|
bool beforeSelected = Selected;
|
||||||
|
|
||||||
|
Mod modBefore = SelectedMod ?? Mods[0];
|
||||||
|
|
||||||
if (value >= Mods.Length)
|
if (value >= Mods.Length)
|
||||||
{
|
selectedIndex = -1;
|
||||||
_selectedIndex = -1;
|
else if (value < -1)
|
||||||
}
|
selectedIndex = Mods.Length - 1;
|
||||||
else if (value <= -2)
|
else
|
||||||
{
|
selectedIndex = value;
|
||||||
_selectedIndex = Mods.Length - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
Mod modAfter = SelectedMod ?? Mods[0];
|
||||||
|
|
||||||
|
if (beforeSelected != Selected)
|
||||||
|
{
|
||||||
iconsContainer.RotateTo(Selected ? 5f : 0f, 300, EasingTypes.OutElastic);
|
iconsContainer.RotateTo(Selected ? 5f : 0f, 300, EasingTypes.OutElastic);
|
||||||
iconsContainer.ScaleTo(Selected ? 1.1f : 1f, 300, EasingTypes.OutElastic);
|
iconsContainer.ScaleTo(Selected ? 1.1f : 1f, 300, EasingTypes.OutElastic);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modBefore != modAfter)
|
||||||
|
{
|
||||||
|
const float rotate_angle = 16;
|
||||||
|
|
||||||
|
foregroundIcon.RotateTo(rotate_angle * direction, mod_switch_duration, mod_switch_easing);
|
||||||
|
backgroundIcon.RotateTo(-rotate_angle * direction, mod_switch_duration, mod_switch_easing);
|
||||||
|
|
||||||
|
backgroundIcon.Icon = modAfter.Icon;
|
||||||
|
using (iconsContainer.BeginDelayedSequence(mod_switch_duration, true))
|
||||||
|
{
|
||||||
|
foregroundIcon.RotateTo(-rotate_angle * direction);
|
||||||
|
foregroundIcon.RotateTo(0f, mod_switch_duration, mod_switch_easing);
|
||||||
|
|
||||||
|
backgroundIcon.RotateTo(rotate_angle * direction);
|
||||||
|
backgroundIcon.RotateTo(0f, mod_switch_duration, mod_switch_easing);
|
||||||
|
|
||||||
|
iconsContainer.Schedule(() => displayMod(modAfter));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foregroundIcon.Highlighted = Selected;
|
foregroundIcon.Highlighted = Selected;
|
||||||
|
|
||||||
if (mod != null)
|
|
||||||
displayMod(SelectedMod ?? Mods[0]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Selected => selectedIndex != -1;
|
public bool Selected => SelectedIndex != -1;
|
||||||
|
|
||||||
|
|
||||||
private Color4 selectedColour;
|
private Color4 selectedColour;
|
||||||
public Color4 SelectedColour
|
public Color4 SelectedColour
|
||||||
@ -117,7 +147,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
|
|
||||||
// the mods from Mod, only multiple if Mod is a MultiMod
|
// the mods from Mod, only multiple if Mod is a MultiMod
|
||||||
|
|
||||||
public override Mod SelectedMod => Mods.ElementAtOrDefault(selectedIndex);
|
public override Mod SelectedMod => Mods.ElementAtOrDefault(SelectedIndex);
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(AudioManager audio)
|
private void load(AudioManager audio)
|
||||||
@ -142,23 +172,25 @@ namespace osu.Game.Overlays.Mods
|
|||||||
|
|
||||||
public void SelectNext()
|
public void SelectNext()
|
||||||
{
|
{
|
||||||
(++selectedIndex == -1 ? sampleOff : sampleOn).Play();
|
(++SelectedIndex == -1 ? sampleOff : sampleOn).Play();
|
||||||
Action?.Invoke(SelectedMod);
|
Action?.Invoke(SelectedMod);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SelectPrevious()
|
public void SelectPrevious()
|
||||||
{
|
{
|
||||||
(--selectedIndex == -1 ? sampleOff : sampleOn).Play();
|
(--SelectedIndex == -1 ? sampleOff : sampleOn).Play();
|
||||||
Action?.Invoke(SelectedMod);
|
Action?.Invoke(SelectedMod);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Deselect()
|
public void Deselect()
|
||||||
{
|
{
|
||||||
selectedIndex = -1;
|
SelectedIndex = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void displayMod(Mod mod)
|
private void displayMod(Mod mod)
|
||||||
{
|
{
|
||||||
|
if (backgroundIcon != null)
|
||||||
|
backgroundIcon.Icon = foregroundIcon.Icon;
|
||||||
foregroundIcon.Icon = mod.Icon;
|
foregroundIcon.Icon = mod.Icon;
|
||||||
text.Text = mod.Name;
|
text.Text = mod.Name;
|
||||||
}
|
}
|
||||||
@ -170,17 +202,17 @@ namespace osu.Game.Overlays.Mods
|
|||||||
{
|
{
|
||||||
iconsContainer.Add(new[]
|
iconsContainer.Add(new[]
|
||||||
{
|
{
|
||||||
new ModIcon(Mods[0])
|
backgroundIcon = new ModIcon(Mods[1])
|
||||||
{
|
{
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.BottomRight,
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.BottomRight,
|
||||||
AutoSizeAxes = Axes.Both,
|
AutoSizeAxes = Axes.Both,
|
||||||
Position = new Vector2(1.5f),
|
Position = new Vector2(1.5f),
|
||||||
},
|
},
|
||||||
foregroundIcon = new ModIcon(Mods[0])
|
foregroundIcon = new ModIcon(Mods[0])
|
||||||
{
|
{
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.BottomRight,
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.BottomRight,
|
||||||
AutoSizeAxes = Axes.Both,
|
AutoSizeAxes = Axes.Both,
|
||||||
Position = new Vector2(-1.5f),
|
Position = new Vector2(-1.5f),
|
||||||
},
|
},
|
||||||
|
@ -6,20 +6,30 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using OpenTK;
|
using OpenTK;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Database;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Objects.Legacy
|
namespace osu.Game.Rulesets.Objects.Legacy
|
||||||
{
|
{
|
||||||
internal abstract class ConvertSlider : HitObject, IHasCurve
|
internal abstract class ConvertSlider : HitObject, IHasCurve
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Scoring distance with a speed-adjusted beat length of 1 second.
|
||||||
|
/// </summary>
|
||||||
|
private const float base_scoring_distance = 100;
|
||||||
|
|
||||||
public List<Vector2> ControlPoints { get; set; }
|
public List<Vector2> ControlPoints { get; set; }
|
||||||
public CurveType CurveType { get; set; }
|
public CurveType CurveType { get; set; }
|
||||||
|
|
||||||
public double Distance { get; set; }
|
public double Distance { get; set; }
|
||||||
|
|
||||||
public List<SampleInfoList> RepeatSamples { get; set; }
|
public List<SampleInfoList> RepeatSamples { get; set; }
|
||||||
public int RepeatCount { get; set; } = 1;
|
public int RepeatCount { get; set; } = 1;
|
||||||
|
|
||||||
public double EndTime { get; set; }
|
public double EndTime => StartTime + RepeatCount * Distance / Velocity;
|
||||||
public double Duration { get; set; }
|
public double Duration => EndTime - StartTime;
|
||||||
|
|
||||||
|
public double Velocity = 1;
|
||||||
|
|
||||||
public Vector2 PositionAt(double progress)
|
public Vector2 PositionAt(double progress)
|
||||||
{
|
{
|
||||||
@ -35,5 +45,17 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
|||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
|
||||||
|
{
|
||||||
|
base.ApplyDefaults(controlPointInfo, difficulty);
|
||||||
|
|
||||||
|
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
|
||||||
|
DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime);
|
||||||
|
|
||||||
|
double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier / difficultyPoint.SpeedMultiplier;
|
||||||
|
|
||||||
|
Velocity = scoringDistance / timingPoint.BeatLength;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -451,6 +451,7 @@
|
|||||||
<Compile Include="Overlays\Direct\SlimEnumDropdown.cs" />
|
<Compile Include="Overlays\Direct\SlimEnumDropdown.cs" />
|
||||||
<Compile Include="Graphics\Containers\ReverseDepthFillFlowContainer.cs" />
|
<Compile Include="Graphics\Containers\ReverseDepthFillFlowContainer.cs" />
|
||||||
<Compile Include="Database\RankStatus.cs" />
|
<Compile Include="Database\RankStatus.cs" />
|
||||||
|
<Compile Include="Graphics\UserInterface\BreadcrumbControl.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\osu-framework\osu.Framework\osu.Framework.csproj">
|
<ProjectReference Include="..\osu-framework\osu.Framework\osu.Framework.csproj">
|
||||||
|
Loading…
Reference in New Issue
Block a user