1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-15 01:12:55 +08:00

Compare commits

...

240 Commits

119 changed files with 2809 additions and 773 deletions
+1 -1
View File
@@ -5,6 +5,6 @@
"version": "3.1.100"
},
"msbuild-sdks": {
"Microsoft.Build.Traversal": "2.0.24"
"Microsoft.Build.Traversal": "2.0.32"
}
}
+2 -2
View File
@@ -51,7 +51,7 @@
<Reference Include="Java.Interop" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.315.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.327.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.331.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.401.0" />
</ItemGroup>
</Project>
@@ -72,10 +72,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
protected override Skill[] CreateSkills(IBeatmap beatmap)
{
using (var catcher = new Catcher(beatmap.BeatmapInfo.BaseDifficulty))
{
halfCatcherWidth = catcher.CatchWidth * 0.5f;
halfCatcherWidth *= 0.8f; // We're only using 80% of the catcher's width to simulate imperfect gameplay.
}
return new Skill[]
{
@@ -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.Linq;
using osu.Framework.Bindables;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
@@ -30,6 +31,22 @@ namespace osu.Game.Rulesets.Catch.Mods
Value = 5,
};
public override string SettingDescription
{
get
{
string circleSize = CircleSize.IsDefault ? string.Empty : $"CS {CircleSize.Value:N1}";
string approachRate = ApproachRate.IsDefault ? string.Empty : $"AR {ApproachRate.Value:N1}";
return string.Join(", ", new[]
{
circleSize,
base.SettingDescription,
approachRate
}.Where(s => !string.IsNullOrEmpty(s)));
}
}
protected override void TransferSettings(BeatmapDifficulty difficulty)
{
base.TransferSettings(difficulty);
+6 -1
View File
@@ -37,10 +37,15 @@ namespace osu.Game.Rulesets.Catch.UI
public CatcherAnimationState CurrentState { get; private set; }
/// <summary>
/// The width of the catcher which can receive fruit. Equivalent to "catchMargin" in osu-stable.
/// </summary>
private const float allowed_catch_range = 0.8f;
/// <summary>
/// Width of the area that can be used to attempt catches during gameplay.
/// </summary>
internal float CatchWidth => CatcherArea.CATCHER_SIZE * Math.Abs(Scale.X);
internal float CatchWidth => CatcherArea.CATCHER_SIZE * Math.Abs(Scale.X) * allowed_catch_range;
protected bool Dashing
{
+1 -1
View File
@@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Catch.UI
public CatcherSprite(CatcherAnimationState state)
: base(new CatchSkinComponent(componentFromState(state)), _ =>
new DefaultCatcherSprite(state), confineMode: ConfineMode.ScaleDownToFit)
new DefaultCatcherSprite(state), confineMode: ConfineMode.ScaleToFit)
{
RelativeSizeAxes = Axes.None;
Size = new Vector2(CatcherArea.CATCHER_SIZE);
@@ -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 System.Collections.Generic;
using osu.Game.Rulesets.Mania.Beatmaps;
using NUnit.Framework;
namespace osu.Game.Rulesets.Mania.Tests
{
[TestFixture]
public class ManiaColumnTypeTest
{
[TestCase(new[]
{
ColumnType.Special
}, 1)]
[TestCase(new[]
{
ColumnType.Odd,
ColumnType.Even,
ColumnType.Even,
ColumnType.Odd
}, 4)]
[TestCase(new[]
{
ColumnType.Odd,
ColumnType.Even,
ColumnType.Odd,
ColumnType.Special,
ColumnType.Odd,
ColumnType.Even,
ColumnType.Odd
}, 7)]
public void Test(IEnumerable<ColumnType> expected, int columns)
{
var definition = new StageDefinition
{
Columns = columns
};
var results = getResults(definition);
Assert.AreEqual(expected, results);
}
private IEnumerable<ColumnType> getResults(StageDefinition definition)
{
for (var i = 0; i < definition.Columns; i++)
yield return definition.GetTypeOfColumn(i);
}
}
}
@@ -0,0 +1,40 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.UI;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.Tests.Skinning
{
/// <summary>
/// A container to be used in a <see cref="ManiaSkinnableTestScene"/> to provide a resolvable <see cref="Column"/> dependency.
/// </summary>
public class ColumnTestContainer : Container
{
protected override Container<Drawable> Content => content;
private readonly Container content;
[Cached]
private readonly Column column;
public ColumnTestContainer(int column, ManiaAction action)
{
this.column = new Column(column)
{
Action = { Value = action },
AccentColour = Color4.Orange,
ColumnType = column % 2 == 0 ? ColumnType.Even : ColumnType.Odd
};
InternalChild = content = new ManiaInputManager(new ManiaRuleset().RulesetInfo, 4)
{
RelativeSizeAxes = Axes.Both
};
}
}
}
@@ -0,0 +1,67 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Timing;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.Tests.Skinning
{
/// <summary>
/// A test scene for a mania hitobject.
/// </summary>
public abstract class ManiaHitObjectTestScene : ManiaSkinnableTestScene
{
[BackgroundDependencyLoader]
private void load()
{
SetContents(() => new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Height = 0.7f,
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
new ColumnTestContainer(0, ManiaAction.Key1)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Y,
Width = 80,
Child = new ScrollingHitObjectContainer
{
RelativeSizeAxes = Axes.Both,
Clock = new FramedClock(new StopwatchClock()),
}.With(c =>
{
c.Add(CreateHitObject().With(h => h.AccentColour.Value = Color4.Orange));
})
},
new ColumnTestContainer(1, ManiaAction.Key2)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Y,
Width = 80,
Child = new ScrollingHitObjectContainer
{
RelativeSizeAxes = Axes.Both,
Clock = new FramedClock(new StopwatchClock()),
}.With(c =>
{
c.Add(CreateHitObject().With(h => h.AccentColour.Value = Color4.Orange));
})
},
}
});
}
protected abstract DrawableManiaHitObject CreateHitObject();
}
}
@@ -0,0 +1,58 @@
// 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.Shapes;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Rulesets.UI.Scrolling.Algorithms;
using osu.Game.Tests.Visual;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.Tests.Skinning
{
/// <summary>
/// A test scene for skinnable mania components.
/// </summary>
public abstract class ManiaSkinnableTestScene : SkinnableTestScene
{
[Cached(Type = typeof(IScrollingInfo))]
private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo();
protected ManiaSkinnableTestScene()
{
scrollingInfo.Direction.Value = ScrollingDirection.Down;
Add(new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.SlateGray.Opacity(0.2f),
Depth = 1
});
}
[Test]
public void TestScrollingDown()
{
AddStep("change direction to down", () => scrollingInfo.Direction.Value = ScrollingDirection.Down);
}
[Test]
public void TestScrollingUp()
{
AddStep("change direction to up", () => scrollingInfo.Direction.Value = ScrollingDirection.Up);
}
private class TestScrollingInfo : IScrollingInfo
{
public readonly Bindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>();
IBindable<ScrollingDirection> IScrollingInfo.Direction => Direction;
IBindable<double> IScrollingInfo.TimeRange { get; } = new Bindable<double>(1000);
IScrollAlgorithm IScrollingInfo.Algorithm { get; } = new ConstantScrollAlgorithm();
}
}
}
@@ -0,0 +1,49 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Mania.UI.Components;
using osu.Game.Skinning;
using osuTK;
namespace osu.Game.Rulesets.Mania.Tests.Skinning
{
public class TestSceneColumnBackground : ManiaSkinnableTestScene
{
[BackgroundDependencyLoader]
private void load()
{
SetContents(() => new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.8f),
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
new ColumnTestContainer(0, ManiaAction.Key1)
{
RelativeSizeAxes = Axes.Both,
Width = 0.5f,
Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground), _ => new DefaultColumnBackground())
{
RelativeSizeAxes = Axes.Both
}
},
new ColumnTestContainer(1, ManiaAction.Key2)
{
RelativeSizeAxes = Axes.Both,
Width = 0.5f,
Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground), _ => new DefaultColumnBackground())
{
RelativeSizeAxes = Axes.Both
}
}
}
});
}
}
}
@@ -0,0 +1,49 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Mania.UI.Components;
using osu.Game.Rulesets.UI;
using osuTK;
namespace osu.Game.Rulesets.Mania.Tests.Skinning
{
public class TestSceneColumnHitObjectArea : ManiaSkinnableTestScene
{
[BackgroundDependencyLoader]
private void load()
{
SetContents(() => new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.8f),
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
new ColumnTestContainer(0, ManiaAction.Key1)
{
RelativeSizeAxes = Axes.Both,
Width = 0.5f,
Child = new ColumnHitObjectArea(new HitObjectContainer())
{
RelativeSizeAxes = Axes.Both
}
},
new ColumnTestContainer(1, ManiaAction.Key2)
{
RelativeSizeAxes = Axes.Both,
Width = 0.5f,
Child = new ColumnHitObjectArea(new HitObjectContainer())
{
RelativeSizeAxes = Axes.Both
}
}
}
});
}
}
}
@@ -6,13 +6,13 @@ using System.Collections.Generic;
using System.Linq;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Game.Tests.Visual;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Mania.Tests
namespace osu.Game.Rulesets.Mania.Tests.Skinning
{
public class TestSceneDrawableJudgement : SkinnableTestScene
{
@@ -0,0 +1,24 @@
// 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.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
namespace osu.Game.Rulesets.Mania.Tests.Skinning
{
public class TestSceneHoldNote : ManiaHitObjectTestScene
{
protected override DrawableManiaHitObject CreateHitObject()
{
var note = new HoldNote { Duration = 1000 };
note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
return new DrawableHoldNote(note)
{
Height = 200,
};
}
}
}
@@ -0,0 +1,58 @@
// 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 osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Mania.Skinning;
using osu.Game.Rulesets.Mania.UI.Components;
using osu.Game.Skinning;
using osuTK;
namespace osu.Game.Rulesets.Mania.Tests.Skinning
{
public class TestSceneKeyArea : ManiaSkinnableTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(DefaultKeyArea),
typeof(LegacyKeyArea)
};
[BackgroundDependencyLoader]
private void load()
{
SetContents(() => new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.8f),
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
new ColumnTestContainer(0, ManiaAction.Key1)
{
RelativeSizeAxes = Axes.Both,
Width = 0.5f,
Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea), _ => new DefaultKeyArea())
{
RelativeSizeAxes = Axes.Both
},
},
new ColumnTestContainer(1, ManiaAction.Key2)
{
RelativeSizeAxes = Axes.Both,
Width = 0.5f,
Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea), _ => new DefaultKeyArea())
{
RelativeSizeAxes = Axes.Both
},
},
}
});
}
}
}
@@ -0,0 +1,21 @@
// 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.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
namespace osu.Game.Rulesets.Mania.Tests.Skinning
{
public class TestSceneNote : ManiaHitObjectTestScene
{
protected override DrawableManiaHitObject CreateHitObject()
{
var note = new Note();
note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
return new DrawableNote(note);
}
}
}
@@ -0,0 +1,27 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.UI;
namespace osu.Game.Rulesets.Mania.Tests.Skinning
{
public class TestSceneStage : ManiaSkinnableTestScene
{
[BackgroundDependencyLoader]
private void load()
{
SetContents(() =>
{
ManiaAction normalAction = ManiaAction.Key1;
ManiaAction specialAction = ManiaAction.Special1;
return new ManiaInputManager(new ManiaRuleset().RulesetInfo, 4)
{
Child = new ManiaStage(0, new StageDefinition { Columns = 4 }, ref normalAction, ref specialAction)
};
});
}
}
}
@@ -28,7 +28,6 @@ namespace osu.Game.Rulesets.Mania.Tests
{
typeof(Column),
typeof(ColumnBackground),
typeof(ColumnKeyArea),
typeof(ColumnHitObjectArea)
};
@@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Mania.Tests
Origin = Anchor.Centre,
RelativePositionAxes = Axes.Y,
Y = -0.25f,
Size = new Vector2(Column.COLUMN_WIDTH, NotePiece.NOTE_HEIGHT),
Size = new Vector2(Column.COLUMN_WIDTH, DefaultNotePiece.NOTE_HEIGHT),
};
int runcount = 0;
@@ -1,9 +1,12 @@
// 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.Mania
namespace osu.Game.Rulesets.Mania.Beatmaps
{
public enum ManiaSkinComponents
public enum ColumnType
{
Even,
Odd,
Special
}
}
@@ -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;
using osu.Game.Rulesets.Mania.UI;
namespace osu.Game.Rulesets.Mania.Beatmaps
@@ -21,5 +22,19 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
/// <param name="column">The 0-based column index.</param>
/// <returns>Whether the column is a special column.</returns>
public bool IsSpecialColumn(int column) => Columns % 2 == 1 && column == Columns / 2;
/// <summary>
/// Get the type of column given a column index.
/// </summary>
/// <param name="column">The 0-based column index.</param>
/// <returns>The type of the column.</returns>
public ColumnType GetTypeOfColumn(int column)
{
if (IsSpecialColumn(column))
return ColumnType.Special;
int distanceToEdge = Math.Min(column, (Columns - 1) - column);
return distanceToEdge % 2 == 0 ? ColumnType.Odd : ColumnType.Even;
}
}
}
@@ -7,12 +7,12 @@ using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components
{
public class EditBodyPiece : BodyPiece
public class EditBodyPiece : DefaultBodyPiece
{
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
AccentColour = colours.Yellow;
AccentColour.Value = colours.Yellow;
Background.Alpha = 0.5f;
Foreground.Alpha = 0;
@@ -12,12 +12,12 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components
{
public EditNotePiece()
{
Height = NotePiece.NOTE_HEIGHT;
Height = DefaultNotePiece.NOTE_HEIGHT;
CornerRadius = 5;
Masking = true;
InternalChild = new NotePiece();
InternalChild = new DefaultNotePiece();
}
[BackgroundDependencyLoader]
@@ -4,13 +4,13 @@
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{
@@ -42,11 +42,18 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{
new HoldNoteNoteSelectionBlueprint(DrawableObject, HoldNotePosition.Start),
new HoldNoteNoteSelectionBlueprint(DrawableObject, HoldNotePosition.End),
new BodyPiece
new Container
{
AccentColour = Color4.Transparent,
BorderColour = colours.Yellow
},
RelativeSizeAxes = Axes.Both,
BorderThickness = 1,
BorderColour = colours.Yellow,
Child = new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
AlwaysPresent = true,
}
}
};
}
@@ -122,11 +122,11 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
switch (scrollingInfo.Direction.Value)
{
case ScrollingDirection.Up:
mousePosition.Y -= NotePiece.NOTE_HEIGHT / 2;
mousePosition.Y -= DefaultNotePiece.NOTE_HEIGHT / 2;
break;
case ScrollingDirection.Down:
mousePosition.Y += NotePiece.NOTE_HEIGHT / 2;
mousePosition.Y += DefaultNotePiece.NOTE_HEIGHT / 2;
break;
}
@@ -143,11 +143,11 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
switch (scrollingInfo.Direction.Value)
{
case ScrollingDirection.Up:
hitObjectPosition.Y += NotePiece.NOTE_HEIGHT / 2;
hitObjectPosition.Y += DefaultNotePiece.NOTE_HEIGHT / 2;
break;
case ScrollingDirection.Down:
hitObjectPosition.Y -= NotePiece.NOTE_HEIGHT / 2;
hitObjectPosition.Y -= DefaultNotePiece.NOTE_HEIGHT / 2;
break;
}
@@ -16,4 +16,15 @@ namespace osu.Game.Rulesets.Mania
protected override string ComponentName => Component.ToString().ToLower();
}
public enum ManiaSkinComponents
{
ColumnBackground,
HitTarget,
KeyArea,
Note,
HoldNoteHead,
HoldNoteTail,
HoldNoteBody,
}
}
@@ -10,6 +10,7 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
@@ -20,6 +21,10 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
public override bool DisplayResult => false;
public IBindable<bool> IsHitting => isHitting;
private readonly Bindable<bool> isHitting = new Bindable<bool>();
public DrawableHoldNoteHead Head => headContainer.Child;
public DrawableHoldNoteTail Tail => tailContainer.Child;
@@ -27,7 +32,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
private readonly Container<DrawableHoldNoteTail> tailContainer;
private readonly Container<DrawableHoldNoteTick> tickContainer;
private readonly BodyPiece bodyPiece;
private readonly Drawable bodyPiece;
/// <summary>
/// Time at which the user started holding this hold note. Null if the user is not holding this hold note.
@@ -44,18 +49,16 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
RelativeSizeAxes = Axes.X;
AddRangeInternal(new Drawable[]
AddRangeInternal(new[]
{
bodyPiece = new BodyPiece { RelativeSizeAxes = Axes.X },
bodyPiece = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HoldNoteBody), _ => new DefaultBodyPiece())
{
RelativeSizeAxes = Axes.X
},
tickContainer = new Container<DrawableHoldNoteTick> { RelativeSizeAxes = Axes.Both },
headContainer = new Container<DrawableHoldNoteHead> { RelativeSizeAxes = Axes.Both },
tailContainer = new Container<DrawableHoldNoteTail> { RelativeSizeAxes = Axes.Both },
});
AccentColour.BindValueChanged(colour =>
{
bodyPiece.AccentColour = colour.NewValue;
}, true);
}
protected override void AddNestedHitObject(DrawableHitObject hitObject)
@@ -168,7 +171,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
return;
HoldStartTime = Time.Current;
bodyPiece.Hitting = true;
isHitting.Value = true;
}
public void OnReleased(ManiaAction action)
@@ -194,7 +197,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
private void endHold()
{
HoldStartTime = null;
bodyPiece.Hitting = false;
isHitting.Value = false;
}
}
}
@@ -8,6 +8,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
/// </summary>
public class DrawableHoldNoteHead : DrawableNote
{
protected override ManiaSkinComponents Component => ManiaSkinComponents.HoldNoteHead;
public DrawableHoldNoteHead(DrawableHoldNote holdNote)
: base(holdNote.HitObject.Head)
{
@@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
/// </summary>
private const double release_window_lenience = 1.5;
protected override ManiaSkinComponents Component => ManiaSkinComponents.HoldNoteTail;
private readonly DrawableHoldNote holdNote;
public DrawableHoldNoteTail(DrawableHoldNote holdNote)
@@ -3,13 +3,12 @@
using System.Diagnostics;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Effects;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
@@ -18,7 +17,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
/// </summary>
public class DrawableNote : DrawableManiaHitObject<Note>, IKeyBindingHandler<ManiaAction>
{
private readonly NotePiece headPiece;
protected virtual ManiaSkinComponents Component => ManiaSkinComponents.Note;
private readonly Drawable headPiece;
public DrawableNote(Note hitObject)
: base(hitObject)
@@ -26,22 +27,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
CornerRadius = 5;
Masking = true;
AddInternal(headPiece = new NotePiece());
AccentColour.BindValueChanged(colour =>
AddInternal(headPiece = new SkinnableDrawable(new ManiaSkinComponent(Component), _ => new DefaultNotePiece())
{
headPiece.AccentColour = colour.NewValue;
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Colour = colour.NewValue.Lighten(1f).Opacity(0.2f),
Radius = 10,
};
}, true);
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y
});
}
protected override void OnDirectionChanged(ValueChangedEvent<ScrollingDirection> e)
@@ -2,6 +2,9 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osuTK.Graphics;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
@@ -9,26 +12,38 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Layout;
using osu.Game.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
{
/// <summary>
/// Represents length-wise portion of a hold note.
/// </summary>
public class BodyPiece : Container, IHasAccentColour
public class DefaultBodyPiece : CompositeDrawable
{
private readonly Container subtractionLayer;
protected readonly Bindable<Color4> AccentColour = new Bindable<Color4>();
protected readonly Drawable Background;
protected readonly BufferedContainer Foreground;
private readonly BufferedContainer subtractionContainer;
private readonly LayoutValue subtractionCache = new LayoutValue(Invalidation.DrawSize);
private readonly IBindable<bool> isHitting = new Bindable<bool>();
public BodyPiece()
protected Drawable Background { get; private set; }
protected BufferedContainer Foreground { get; private set; }
private BufferedContainer subtractionContainer;
private Container subtractionLayer;
public DefaultBodyPiece()
{
RelativeSizeAxes = Axes.Both;
Blending = BlendingParameters.Additive;
Children = new[]
AddLayout(subtractionCache);
}
[BackgroundDependencyLoader(true)]
private void load([CanBeNull] DrawableHitObject drawableObject)
{
InternalChildren = new[]
{
Background = new Box { RelativeSizeAxes = Axes.Both },
Foreground = new BufferedContainer
@@ -66,43 +81,37 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
}
};
AddLayout(subtractionCache);
}
protected override void LoadComplete()
{
base.LoadComplete();
updateAccentColour();
}
private Color4 accentColour;
public Color4 AccentColour
{
get => accentColour;
set
if (drawableObject != null)
{
if (accentColour == value)
return;
var holdNote = (DrawableHoldNote)drawableObject;
accentColour = value;
updateAccentColour();
AccentColour.BindTo(drawableObject.AccentColour);
isHitting.BindTo(holdNote.IsHitting);
}
AccentColour.BindValueChanged(onAccentChanged, true);
isHitting.BindValueChanged(_ => onAccentChanged(new ValueChangedEvent<Color4>(AccentColour.Value, AccentColour.Value)), true);
}
public bool Hitting
private void onAccentChanged(ValueChangedEvent<Color4> accent)
{
get => hitting;
set
{
hitting = value;
updateAccentColour();
}
}
Foreground.Colour = accent.NewValue.Opacity(0.5f);
Background.Colour = accent.NewValue.Opacity(0.7f);
private readonly LayoutValue subtractionCache = new LayoutValue(Invalidation.DrawSize);
const float animation_length = 50;
Foreground.ClearTransforms(false, nameof(Foreground.Colour));
if (isHitting.Value)
{
// wait for the next sync point
double synchronisedOffset = animation_length * 2 - Time.Current % (animation_length * 2);
using (Foreground.BeginDelayedSequence(synchronisedOffset))
Foreground.FadeColour(accent.NewValue.Lighten(0.2f), animation_length).Then().FadeColour(Foreground.Colour, animation_length).Loop();
}
subtractionCache.Invalidate();
}
protected override void Update()
{
@@ -125,30 +134,5 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
subtractionCache.Validate();
}
}
private bool hitting;
private void updateAccentColour()
{
if (!IsLoaded)
return;
Foreground.Colour = AccentColour.Opacity(0.5f);
Background.Colour = AccentColour.Opacity(0.7f);
const float animation_length = 50;
Foreground.ClearTransforms(false, nameof(Foreground.Colour));
if (hitting)
{
// wait for the next sync point
double synchronisedOffset = animation_length * 2 - Time.Current % (animation_length * 2);
using (Foreground.BeginDelayedSequence(synchronisedOffset))
Foreground.FadeColour(AccentColour.Lighten(0.2f), animation_length).Then().FadeColour(Foreground.Colour, animation_length).Loop();
}
subtractionCache.Invalidate();
}
}
}
@@ -0,0 +1,85 @@
// 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 JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osuTK.Graphics;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
{
/// <summary>
/// Represents the static hit markers of notes.
/// </summary>
internal class DefaultNotePiece : CompositeDrawable
{
public const float NOTE_HEIGHT = 12;
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
private readonly IBindable<Color4> accentColour = new Bindable<Color4>();
private readonly Box colouredBox;
public DefaultNotePiece()
{
RelativeSizeAxes = Axes.X;
Height = NOTE_HEIGHT;
CornerRadius = 5;
Masking = true;
InternalChildren = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both
},
colouredBox = new Box
{
RelativeSizeAxes = Axes.X,
Height = NOTE_HEIGHT / 2,
Alpha = 0.1f
}
};
}
[BackgroundDependencyLoader(true)]
private void load([NotNull] IScrollingInfo scrollingInfo, [CanBeNull] DrawableHitObject drawableObject)
{
direction.BindTo(scrollingInfo.Direction);
direction.BindValueChanged(onDirectionChanged, true);
if (drawableObject != null)
{
accentColour.BindTo(drawableObject.AccentColour);
accentColour.BindValueChanged(onAccentChanged, true);
}
}
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
{
colouredBox.Anchor = colouredBox.Origin = direction.NewValue == ScrollingDirection.Up
? Anchor.TopCentre
: Anchor.BottomCentre;
}
private void onAccentChanged(ValueChangedEvent<Color4> accent)
{
colouredBox.Colour = accent.NewValue.Lighten(0.9f);
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Colour = accent.NewValue.Lighten(1f).Opacity(0.2f),
Radius = 10,
};
}
}
}
@@ -1,73 +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.Framework.Allocation;
using osu.Framework.Bindables;
using osuTK.Graphics;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
{
/// <summary>
/// Represents the static hit markers of notes.
/// </summary>
internal class NotePiece : Container, IHasAccentColour
{
public const float NOTE_HEIGHT = 12;
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
private readonly Box colouredBox;
public NotePiece()
{
RelativeSizeAxes = Axes.X;
Height = NOTE_HEIGHT;
Children = new[]
{
new Box
{
RelativeSizeAxes = Axes.Both
},
colouredBox = new Box
{
RelativeSizeAxes = Axes.X,
Height = NOTE_HEIGHT / 2,
Alpha = 0.1f
}
};
}
[BackgroundDependencyLoader]
private void load(IScrollingInfo scrollingInfo)
{
direction.BindTo(scrollingInfo.Direction);
direction.BindValueChanged(dir =>
{
colouredBox.Anchor = colouredBox.Origin = dir.NewValue == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
}, true);
}
private Color4 accentColour;
public Color4 AccentColour
{
get => accentColour;
set
{
if (accentColour == value)
return;
accentColour = value;
colouredBox.Colour = AccentColour.Lighten(0.9f);
}
}
}
}
@@ -0,0 +1,92 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Animations;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Skinning;
using osuTK;
namespace osu.Game.Rulesets.Mania.Skinning
{
public class LegacyBodyPiece : LegacyManiaColumnElement
{
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
private readonly IBindable<bool> isHitting = new Bindable<bool>();
private Drawable sprite;
[Resolved(CanBeNull = true)]
private ManiaStage stage { get; set; }
[Resolved]
private Column column { get; set; }
public LegacyBodyPiece()
{
RelativeSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load(ISkinSource skin, IScrollingInfo scrollingInfo, DrawableHitObject drawableObject)
{
string imageName = GetManiaSkinConfig<string>(skin, LegacyManiaSkinConfigurationLookups.HoldNoteBodyImage)?.Value
?? $"mania-note{FallbackColumnIndex}L";
sprite = skin.GetAnimation(imageName, true, true).With(d =>
{
if (d == null)
return;
if (d is TextureAnimation animation)
animation.IsPlaying = false;
d.Anchor = Anchor.TopCentre;
d.RelativeSizeAxes = Axes.Both;
d.Size = Vector2.One;
d.FillMode = FillMode.Stretch;
// Todo: Wrap
});
if (sprite != null)
InternalChild = sprite;
direction.BindTo(scrollingInfo.Direction);
direction.BindValueChanged(onDirectionChanged, true);
var holdNote = (DrawableHoldNote)drawableObject;
isHitting.BindTo(holdNote.IsHitting);
isHitting.BindValueChanged(onIsHittingChanged, true);
}
private void onIsHittingChanged(ValueChangedEvent<bool> isHitting)
{
if (!(sprite is TextureAnimation animation))
return;
animation.IsPlaying = isHitting.NewValue;
}
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
{
if (sprite == null)
return;
if (direction.NewValue == ScrollingDirection.Up)
{
sprite.Origin = Anchor.BottomCentre;
sprite.Scale = new Vector2(1, -1);
}
else
{
sprite.Origin = Anchor.TopCentre;
sprite.Scale = Vector2.One;
}
}
}
}
@@ -0,0 +1,128 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Skinning;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.Skinning
{
public class LegacyColumnBackground : LegacyManiaColumnElement, IKeyBindingHandler<ManiaAction>
{
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
private Container lightContainer;
private Sprite light;
public LegacyColumnBackground()
{
RelativeSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load(ISkinSource skin, IScrollingInfo scrollingInfo)
{
string lightImage = GetManiaSkinConfig<string>(skin, LegacyManiaSkinConfigurationLookups.LightImage, 0)?.Value
?? "mania-stage-light";
float leftLineWidth = GetManiaSkinConfig<float>(skin, LegacyManiaSkinConfigurationLookups.LeftLineWidth)
?.Value ?? 1;
float rightLineWidth = GetManiaSkinConfig<float>(skin, LegacyManiaSkinConfigurationLookups.RightLineWidth)
?.Value ?? 1;
bool hasLeftLine = leftLineWidth > 0;
bool hasRightLine = rightLineWidth > 0 && skin.GetConfig<LegacySkinConfiguration.LegacySetting, decimal>(LegacySkinConfiguration.LegacySetting.Version)?.Value >= 2.4m
|| Stage == null || Column.Index == Stage.Columns.Count - 1;
float lightPosition = skin.GetConfig<LegacyManiaSkinConfigurationLookup, float>(
new LegacyManiaSkinConfigurationLookup(Stage?.Columns.Count ?? 4, LegacyManiaSkinConfigurationLookups.LightPosition))?.Value
?? 0;
InternalChildren = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black
},
new Box
{
RelativeSizeAxes = Axes.Y,
Width = leftLineWidth,
Alpha = hasLeftLine ? 1 : 0
},
new Box
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
RelativeSizeAxes = Axes.Y,
Width = rightLineWidth,
Alpha = hasRightLine ? 1 : 0
},
lightContainer = new Container
{
Origin = Anchor.BottomCentre,
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Bottom = lightPosition },
Child = light = new Sprite
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
Texture = skin.GetTexture(lightImage),
RelativeSizeAxes = Axes.X,
Width = 1,
Alpha = 0
}
}
};
direction.BindTo(scrollingInfo.Direction);
direction.BindValueChanged(onDirectionChanged, true);
}
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
{
if (direction.NewValue == ScrollingDirection.Up)
{
lightContainer.Anchor = Anchor.TopCentre;
lightContainer.Scale = new Vector2(1, -1);
}
else
{
lightContainer.Anchor = Anchor.BottomCentre;
lightContainer.Scale = Vector2.One;
}
}
public bool OnPressed(ManiaAction action)
{
if (action == Column.Action.Value)
{
light.FadeIn();
light.ScaleTo(Vector2.One);
}
return false;
}
public void OnReleased(ManiaAction action)
{
// Todo: Should be 400 * 100 / CurrentBPM
const double animation_length = 250;
if (action == Column.Action.Value)
{
light.FadeTo(0, animation_length);
light.ScaleTo(new Vector2(1, 0), animation_length);
}
}
}
}
@@ -0,0 +1,78 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Skinning;
using osuTK;
namespace osu.Game.Rulesets.Mania.Skinning
{
public class LegacyHitTarget : LegacyManiaElement
{
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
private Container directionContainer;
public LegacyHitTarget()
{
RelativeSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load(ISkinSource skin, IScrollingInfo scrollingInfo)
{
string targetImage = GetManiaSkinConfig<string>(skin, LegacyManiaSkinConfigurationLookups.HitTargetImage)?.Value
?? "mania-stage-hint";
bool showJudgementLine = GetManiaSkinConfig<bool>(skin, LegacyManiaSkinConfigurationLookups.ShowJudgementLine)?.Value
?? true;
InternalChild = directionContainer = new Container
{
Origin = Anchor.CentreLeft,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
{
new Sprite
{
Texture = skin.GetTexture(targetImage),
Scale = new Vector2(1, 0.9f * 1.6025f),
RelativeSizeAxes = Axes.X,
Width = 1
},
new Box
{
Anchor = Anchor.CentreLeft,
RelativeSizeAxes = Axes.X,
Height = 1,
Alpha = showJudgementLine ? 0.9f : 0
}
}
};
direction.BindTo(scrollingInfo.Direction);
direction.BindValueChanged(onDirectionChanged, true);
}
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
{
if (direction.NewValue == ScrollingDirection.Up)
{
directionContainer.Anchor = Anchor.TopLeft;
directionContainer.Scale = new Vector2(1, -1);
}
else
{
directionContainer.Anchor = Anchor.BottomLeft;
directionContainer.Scale = Vector2.One;
}
}
}
}
@@ -0,0 +1,17 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Textures;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Mania.Skinning
{
public class LegacyHoldNoteHeadPiece : LegacyNotePiece
{
protected override Texture GetTexture(ISkinSource skin)
{
return GetTextureFromLookup(skin, LegacyManiaSkinConfigurationLookups.HoldNoteHeadImage)
?? GetTextureFromLookup(skin, LegacyManiaSkinConfigurationLookups.NoteImage);
}
}
}
@@ -0,0 +1,27 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Bindables;
using osu.Framework.Graphics.Textures;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Mania.Skinning
{
public class LegacyHoldNoteTailPiece : LegacyNotePiece
{
protected override void OnDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
{
// Invert the direction
base.OnDirectionChanged(direction.NewValue == ScrollingDirection.Up
? new ValueChangedEvent<ScrollingDirection>(ScrollingDirection.Down, ScrollingDirection.Down)
: new ValueChangedEvent<ScrollingDirection>(ScrollingDirection.Up, ScrollingDirection.Up));
}
protected override Texture GetTexture(ISkinSource skin)
{
return GetTextureFromLookup(skin, LegacyManiaSkinConfigurationLookups.HoldNoteTailImage)
?? GetTextureFromLookup(skin, LegacyManiaSkinConfigurationLookups.HoldNoteHeadImage);
}
}
}
@@ -0,0 +1,109 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Skinning;
using osuTK;
namespace osu.Game.Rulesets.Mania.Skinning
{
public class LegacyKeyArea : LegacyManiaColumnElement, IKeyBindingHandler<ManiaAction>
{
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
private Container directionContainer;
private Sprite upSprite;
private Sprite downSprite;
[Resolved(CanBeNull = true)]
private ManiaStage stage { get; set; }
[Resolved]
private Column column { get; set; }
public LegacyKeyArea()
{
RelativeSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load(ISkinSource skin, IScrollingInfo scrollingInfo)
{
string upImage = GetManiaSkinConfig<string>(skin, LegacyManiaSkinConfigurationLookups.KeyImage)?.Value
?? $"mania-key{FallbackColumnIndex}";
string downImage = GetManiaSkinConfig<string>(skin, LegacyManiaSkinConfigurationLookups.KeyImageDown)?.Value
?? $"mania-key{FallbackColumnIndex}D";
InternalChild = directionContainer = new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
{
upSprite = new Sprite
{
Origin = Anchor.BottomCentre,
Texture = skin.GetTexture(upImage),
RelativeSizeAxes = Axes.X,
Width = 1
},
downSprite = new Sprite
{
Origin = Anchor.BottomCentre,
Texture = skin.GetTexture(downImage),
RelativeSizeAxes = Axes.X,
Width = 1,
Alpha = 0
}
}
};
direction.BindTo(scrollingInfo.Direction);
direction.BindValueChanged(onDirectionChanged, true);
}
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
{
if (direction.NewValue == ScrollingDirection.Up)
{
directionContainer.Anchor = directionContainer.Origin = Anchor.TopCentre;
upSprite.Anchor = downSprite.Anchor = Anchor.TopCentre;
upSprite.Scale = downSprite.Scale = new Vector2(1, -1);
}
else
{
directionContainer.Anchor = directionContainer.Origin = Anchor.BottomCentre;
upSprite.Anchor = downSprite.Anchor = Anchor.BottomCentre;
upSprite.Scale = downSprite.Scale = Vector2.One;
}
}
public bool OnPressed(ManiaAction action)
{
if (action == column.Action.Value)
{
upSprite.FadeTo(0);
downSprite.FadeTo(1);
}
return false;
}
public void OnReleased(ManiaAction action)
{
if (action == column.Action.Value)
{
upSprite.FadeTo(1);
downSprite.FadeTo(0);
}
}
}
}
@@ -0,0 +1,48 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Mania.Skinning
{
/// <summary>
/// A <see cref="CompositeDrawable"/> which is placed somewhere within a <see cref="Column"/>.
/// </summary>
public class LegacyManiaColumnElement : LegacyManiaElement
{
[Resolved]
protected Column Column { get; private set; }
/// <summary>
/// The column type identifier to use for texture lookups, in the case of no user-provided configuration.
/// </summary>
protected string FallbackColumnIndex { get; private set; }
[BackgroundDependencyLoader]
private void load()
{
switch (Column.ColumnType)
{
case ColumnType.Special:
FallbackColumnIndex = "S";
break;
case ColumnType.Odd:
FallbackColumnIndex = "1";
break;
case ColumnType.Even:
FallbackColumnIndex = "2";
break;
}
}
protected override IBindable<T> GetManiaSkinConfig<T>(ISkin skin, LegacyManiaSkinConfigurationLookups lookup, int? index = null)
=> base.GetManiaSkinConfig<T>(skin, lookup, index ?? Column.Index);
}
}
@@ -0,0 +1,32 @@
// 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 JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Mania.Skinning
{
/// <summary>
/// A mania legacy skin element.
/// </summary>
public class LegacyManiaElement : CompositeDrawable
{
[Resolved(CanBeNull = true)]
[CanBeNull]
protected ManiaStage Stage { get; private set; }
/// <summary>
/// Retrieve a per-column-count skin configuration.
/// </summary>
/// <param name="skin">The skin from which configuration is retrieved.</param>
/// <param name="lookup">The value to retrieve.</param>
/// <param name="index">If not null, denotes the index of the column to which the entry applies.</param>
protected virtual IBindable<T> GetManiaSkinConfig<T>(ISkin skin, LegacyManiaSkinConfigurationLookups lookup, int? index = null)
=> skin.GetConfig<LegacyManiaSkinConfigurationLookup, T>(
new LegacyManiaSkinConfigurationLookup(Stage?.Columns.Count ?? 4, lookup, index));
}
}
@@ -0,0 +1,92 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Skinning;
using osuTK;
namespace osu.Game.Rulesets.Mania.Skinning
{
public class LegacyNotePiece : LegacyManiaColumnElement
{
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
private Container directionContainer;
private Sprite noteSprite;
public LegacyNotePiece()
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
}
[BackgroundDependencyLoader]
private void load(ISkinSource skin, IScrollingInfo scrollingInfo)
{
InternalChild = directionContainer = new Container
{
Origin = Anchor.BottomCentre,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Child = noteSprite = new Sprite { Texture = GetTexture(skin) }
};
direction.BindTo(scrollingInfo.Direction);
direction.BindValueChanged(OnDirectionChanged, true);
}
protected override void Update()
{
base.Update();
if (noteSprite.Texture != null)
{
var scale = DrawWidth / noteSprite.Texture.DisplayWidth;
noteSprite.Scale = new Vector2(scale);
}
}
protected virtual void OnDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
{
if (direction.NewValue == ScrollingDirection.Up)
{
directionContainer.Anchor = Anchor.TopCentre;
directionContainer.Scale = new Vector2(1, -1);
}
else
{
directionContainer.Anchor = Anchor.BottomCentre;
directionContainer.Scale = Vector2.One;
}
}
protected virtual Texture GetTexture(ISkinSource skin) => GetTextureFromLookup(skin, LegacyManiaSkinConfigurationLookups.NoteImage);
protected Texture GetTextureFromLookup(ISkin skin, LegacyManiaSkinConfigurationLookups lookup)
{
string suffix = string.Empty;
switch (lookup)
{
case LegacyManiaSkinConfigurationLookups.HoldNoteHeadImage:
suffix = "H";
break;
case LegacyManiaSkinConfigurationLookups.HoldNoteTailImage:
suffix = "T";
break;
}
string noteImage = GetManiaSkinConfig<string>(skin, lookup)?.Value
?? $"mania-note{FallbackColumnIndex}{suffix}";
return skin.GetTexture(noteImage);
}
}
}
@@ -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;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Textures;
using osu.Framework.Audio.Sample;
@@ -15,9 +16,29 @@ namespace osu.Game.Rulesets.Mania.Skinning
{
private readonly ISkin source;
public ManiaLegacySkinTransformer(ISkin source)
private Lazy<bool> isLegacySkin;
/// <summary>
/// Whether texture for the keys exists.
/// Used to determine if the mania ruleset is skinned.
/// </summary>
private Lazy<bool> hasKeyTexture;
public ManiaLegacySkinTransformer(ISkinSource source)
{
this.source = source;
source.SourceChanged += sourceChanged;
sourceChanged();
}
private void sourceChanged()
{
isLegacySkin = new Lazy<bool>(() => source.GetConfig<LegacySkinConfiguration.LegacySetting, decimal>(LegacySkinConfiguration.LegacySetting.Version) != null);
hasKeyTexture = new Lazy<bool>(() => source.GetAnimation(
source.GetConfig<LegacyManiaSkinConfigurationLookup, string>(
new LegacyManiaSkinConfigurationLookup(4, LegacyManiaSkinConfigurationLookups.KeyImage, 0))?.Value
?? "mania-key1", true, true) != null);
}
public Drawable GetDrawableComponent(ISkinComponent component)
@@ -26,6 +47,36 @@ namespace osu.Game.Rulesets.Mania.Skinning
{
case GameplaySkinComponent<HitResult> resultComponent:
return getResult(resultComponent);
case ManiaSkinComponent maniaComponent:
if (!isLegacySkin.Value || !hasKeyTexture.Value)
return null;
switch (maniaComponent.Component)
{
case ManiaSkinComponents.ColumnBackground:
return new LegacyColumnBackground();
case ManiaSkinComponents.HitTarget:
return new LegacyHitTarget();
case ManiaSkinComponents.KeyArea:
return new LegacyKeyArea();
case ManiaSkinComponents.Note:
return new LegacyNotePiece();
case ManiaSkinComponents.HoldNoteHead:
return new LegacyHoldNoteHeadPiece();
case ManiaSkinComponents.HoldNoteTail:
return new LegacyHoldNoteTailPiece();
case ManiaSkinComponents.HoldNoteBody:
return new LegacyBodyPiece();
}
break;
}
return null;
+16 -79
View File
@@ -12,17 +12,19 @@ using osu.Framework.Bindables;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using osu.Game.Rulesets.Mania.UI.Components;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Skinning;
using osuTK;
using osu.Game.Rulesets.Mania.Beatmaps;
namespace osu.Game.Rulesets.Mania.UI
{
[Cached]
public class Column : ScrollingPlayfield, IKeyBindingHandler<ManiaAction>, IHasAccentColour
{
public const float COLUMN_WIDTH = 80;
private const float special_column_width = 70;
public const float SPECIAL_COLUMN_WIDTH = 70;
/// <summary>
/// The index of this column as part of the whole playfield.
@@ -31,109 +33,44 @@ namespace osu.Game.Rulesets.Mania.UI
public readonly Bindable<ManiaAction> Action = new Bindable<ManiaAction>();
private readonly ColumnBackground background;
private readonly ColumnKeyArea keyArea;
private readonly ColumnHitObjectArea hitObjectArea;
internal readonly Container TopLevelContainer;
private readonly Container explosionContainer;
public Column(int index)
{
Index = index;
RelativeSizeAxes = Axes.Y;
Width = COLUMN_WIDTH;
background = new ColumnBackground { RelativeSizeAxes = Axes.Both };
Container hitTargetContainer;
Drawable background = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground), _ => new DefaultColumnBackground())
{
RelativeSizeAxes = Axes.Both
};
InternalChildren = new[]
{
// For input purposes, the background is added at the highest depth, but is then proxied back below all other elements
background.CreateProxy(),
hitTargetContainer = new Container
hitObjectArea = new ColumnHitObjectArea(HitObjectContainer) { RelativeSizeAxes = Axes.Both },
new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea), _ => new DefaultKeyArea())
{
Name = "Hit target + hit objects",
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
hitObjectArea = new ColumnHitObjectArea(HitObjectContainer)
{
RelativeSizeAxes = Axes.Both,
},
explosionContainer = new Container
{
Name = "Hit explosions",
RelativeSizeAxes = Axes.Both,
}
}
},
keyArea = new ColumnKeyArea
{
RelativeSizeAxes = Axes.X,
Height = ManiaStage.HIT_TARGET_POSITION,
RelativeSizeAxes = Axes.Both
},
background,
TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both }
};
TopLevelContainer.Add(explosionContainer.CreateProxy());
Direction.BindValueChanged(dir =>
{
hitTargetContainer.Padding = new MarginPadding
{
Top = dir.NewValue == ScrollingDirection.Up ? ManiaStage.HIT_TARGET_POSITION : 0,
Bottom = dir.NewValue == ScrollingDirection.Down ? ManiaStage.HIT_TARGET_POSITION : 0,
};
explosionContainer.Padding = new MarginPadding
{
Top = dir.NewValue == ScrollingDirection.Up ? NotePiece.NOTE_HEIGHT / 2 : 0,
Bottom = dir.NewValue == ScrollingDirection.Down ? NotePiece.NOTE_HEIGHT / 2 : 0
};
keyArea.Anchor = keyArea.Origin = dir.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
}, true);
TopLevelContainer.Add(hitObjectArea.Explosions.CreateProxy());
}
public override Axes RelativeSizeAxes => Axes.Y;
private bool isSpecial;
public ColumnType ColumnType { get; set; }
public bool IsSpecial
{
get => isSpecial;
set
{
if (isSpecial == value)
return;
public bool IsSpecial => ColumnType == ColumnType.Special;
isSpecial = value;
Width = isSpecial ? special_column_width : COLUMN_WIDTH;
}
}
private Color4 accentColour;
public Color4 AccentColour
{
get => accentColour;
set
{
if (accentColour == value)
return;
accentColour = value;
background.AccentColour = value;
keyArea.AccentColour = value;
hitObjectArea.AccentColour = value;
}
}
public Color4 AccentColour { get; set; }
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
@@ -168,7 +105,7 @@ namespace osu.Game.Rulesets.Mania.UI
if (!result.IsHit || !judgedObject.DisplayResult || !DisplayJudgements.Value)
return;
explosionContainer.Add(new HitExplosion(judgedObject.AccentColour.Value, judgedObject is DrawableHoldNoteTick)
hitObjectArea.Explosions.Add(new HitExplosion(judgedObject.AccentColour.Value, judgedObject is DrawableHoldNoteTick)
{
Anchor = Direction.Value == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre,
Origin = Anchor.Centre
@@ -1,144 +1,51 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK.Graphics;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Mania.UI.Components
{
public class ColumnHitObjectArea : CompositeDrawable, IHasAccentColour
public class ColumnHitObjectArea : HitObjectArea
{
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
public readonly Container<HitExplosion> Explosions;
private readonly Drawable hitTarget;
public ColumnHitObjectArea(HitObjectContainer hitObjectContainer)
: base(hitObjectContainer)
{
InternalChildren = new[]
AddRangeInternal(new[]
{
hitTarget = new DefaultHitTarget
hitTarget = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitTarget), _ => new DefaultHitTarget())
{
RelativeSizeAxes = Axes.X,
Depth = 1
},
hitObjectContainer
};
}
[BackgroundDependencyLoader]
private void load(IScrollingInfo scrollingInfo)
{
direction.BindTo(scrollingInfo.Direction);
direction.BindValueChanged(dir =>
{
Anchor anchor = dir.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
hitTarget.Anchor = hitTarget.Origin = anchor;
}, true);
}
private Color4 accentColour;
public Color4 AccentColour
{
get => accentColour;
set
{
if (accentColour == value)
return;
accentColour = value;
if (hitTarget is IHasAccentColour colouredHitTarget)
colouredHitTarget.AccentColour = accentColour;
}
}
private class DefaultHitTarget : CompositeDrawable, IHasAccentColour
{
private const float hit_target_bar_height = 2;
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
private readonly Container hitTargetLine;
private readonly Drawable hitTargetBar;
public DefaultHitTarget()
{
InternalChildren = new[]
Explosions = new Container<HitExplosion>
{
hitTargetBar = new Box
{
RelativeSizeAxes = Axes.X,
Height = NotePiece.NOTE_HEIGHT,
Alpha = 0.6f,
Colour = Color4.Black
},
hitTargetLine = new Container
{
RelativeSizeAxes = Axes.X,
Height = hit_target_bar_height,
Masking = true,
Child = new Box { RelativeSizeAxes = Axes.Both }
},
};
}
[BackgroundDependencyLoader]
private void load(IScrollingInfo scrollingInfo)
{
direction.BindTo(scrollingInfo.Direction);
direction.BindValueChanged(dir =>
{
Anchor anchor = dir.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
hitTargetBar.Anchor = hitTargetBar.Origin = anchor;
hitTargetLine.Anchor = hitTargetLine.Origin = anchor;
}, true);
}
protected override void LoadComplete()
{
base.LoadComplete();
updateColours();
}
private Color4 accentColour;
public Color4 AccentColour
{
get => accentColour;
set
{
if (accentColour == value)
return;
accentColour = value;
updateColours();
RelativeSizeAxes = Axes.Both,
Depth = -1,
}
}
});
}
private void updateColours()
protected override void UpdateHitPosition()
{
base.UpdateHitPosition();
if (Direction.Value == ScrollingDirection.Up)
{
if (!IsLoaded)
return;
hitTargetLine.EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Radius = 5,
Colour = accentColour.Opacity(0.5f),
};
hitTarget.Anchor = hitTarget.Origin = Anchor.TopLeft;
Explosions.Padding = new MarginPadding { Top = DefaultNotePiece.NOTE_HEIGHT / 2 };
}
else
{
hitTarget.Anchor = hitTarget.Origin = Anchor.BottomLeft;
Explosions.Padding = new MarginPadding { Bottom = DefaultNotePiece.NOTE_HEIGHT / 2 };
}
}
}
@@ -1,124 +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.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Bindings;
using osu.Game.Graphics;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.UI.Components
{
public class ColumnKeyArea : CompositeDrawable, IKeyBindingHandler<ManiaAction>, IHasAccentColour
{
private const float key_icon_size = 10;
private const float key_icon_corner_radius = 3;
private readonly IBindable<ManiaAction> action = new Bindable<ManiaAction>();
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
private Container keyIcon;
[BackgroundDependencyLoader]
private void load(IBindable<ManiaAction> action, IScrollingInfo scrollingInfo)
{
this.action.BindTo(action);
Drawable gradient;
InternalChildren = new[]
{
gradient = new Box
{
Name = "Key gradient",
RelativeSizeAxes = Axes.Both,
Alpha = 0.5f
},
keyIcon = new Container
{
Name = "Key icon",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(key_icon_size),
Masking = true,
CornerRadius = key_icon_corner_radius,
BorderThickness = 2,
BorderColour = Color4.White, // Not true
Children = new[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
AlwaysPresent = true
}
}
}
};
direction.BindTo(scrollingInfo.Direction);
direction.BindValueChanged(dir =>
{
gradient.Colour = ColourInfo.GradientVertical(
dir.NewValue == ScrollingDirection.Up ? Color4.Black : Color4.Black.Opacity(0),
dir.NewValue == ScrollingDirection.Up ? Color4.Black.Opacity(0) : Color4.Black);
}, true);
}
protected override void LoadComplete()
{
base.LoadComplete();
updateColours();
}
private Color4 accentColour;
public Color4 AccentColour
{
get => accentColour;
set
{
if (accentColour == value)
return;
accentColour = value;
updateColours();
}
}
private void updateColours()
{
if (!IsLoaded)
return;
keyIcon.EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Radius = 5,
Colour = accentColour.Opacity(0.5f),
};
}
public bool OnPressed(ManiaAction action)
{
if (action == this.action.Value)
keyIcon.ScaleTo(1.4f, 50, Easing.OutQuint).Then().ScaleTo(1.3f, 250, Easing.OutQuint);
return false;
}
public void OnReleased(ManiaAction action)
{
if (action == this.action.Value)
keyIcon.ScaleTo(1f, 125, Easing.OutQuint);
}
}
}
@@ -0,0 +1,90 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.UI.Components
{
public class DefaultColumnBackground : CompositeDrawable, IKeyBindingHandler<ManiaAction>
{
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
private Color4 brightColour;
private Color4 dimColour;
private Box background;
private Box backgroundOverlay;
[Resolved]
private Column column { get; set; }
public DefaultColumnBackground()
{
RelativeSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load(IScrollingInfo scrollingInfo)
{
InternalChildren = new[]
{
background = new Box
{
Name = "Background",
RelativeSizeAxes = Axes.Both,
},
backgroundOverlay = new Box
{
Name = "Background Gradient Overlay",
RelativeSizeAxes = Axes.Both,
Height = 0.5f,
Blending = BlendingParameters.Additive,
Alpha = 0
}
};
background.Colour = column.AccentColour.Darken(5);
brightColour = column.AccentColour.Opacity(0.6f);
dimColour = column.AccentColour.Opacity(0);
direction.BindTo(scrollingInfo.Direction);
direction.BindValueChanged(onDirectionChanged, true);
}
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
{
if (direction.NewValue == ScrollingDirection.Up)
{
backgroundOverlay.Anchor = backgroundOverlay.Origin = Anchor.TopLeft;
backgroundOverlay.Colour = ColourInfo.GradientVertical(brightColour, dimColour);
}
else
{
backgroundOverlay.Anchor = backgroundOverlay.Origin = Anchor.BottomLeft;
backgroundOverlay.Colour = ColourInfo.GradientVertical(dimColour, brightColour);
}
}
public bool OnPressed(ManiaAction action)
{
if (action == column.Action.Value)
backgroundOverlay.FadeTo(1, 50, Easing.OutQuint).Then().FadeTo(0.5f, 250, Easing.OutQuint);
return false;
}
public void OnReleased(ManiaAction action)
{
if (action == column.Action.Value)
backgroundOverlay.FadeTo(0, 250, Easing.OutQuint);
}
}
}
@@ -0,0 +1,80 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.UI.Components
{
public class DefaultHitTarget : CompositeDrawable
{
private const float hit_target_bar_height = 2;
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
private Container hitTargetLine;
private Drawable hitTargetBar;
[Resolved]
private Column column { get; set; }
public DefaultHitTarget()
{
RelativeSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load(IScrollingInfo scrollingInfo)
{
InternalChildren = new[]
{
hitTargetBar = new Box
{
RelativeSizeAxes = Axes.X,
Height = DefaultNotePiece.NOTE_HEIGHT,
Alpha = 0.6f,
Colour = Color4.Black
},
hitTargetLine = new Container
{
RelativeSizeAxes = Axes.X,
Height = hit_target_bar_height,
Masking = true,
Child = new Box { RelativeSizeAxes = Axes.Both }
},
};
hitTargetLine.EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Radius = 5,
Colour = column.AccentColour.Opacity(0.5f),
};
direction.BindTo(scrollingInfo.Direction);
direction.BindValueChanged(onDirectionChanged, true);
}
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
{
if (direction.NewValue == ScrollingDirection.Up)
{
hitTargetBar.Anchor = hitTargetBar.Origin = Anchor.TopLeft;
hitTargetLine.Anchor = hitTargetLine.Origin = Anchor.TopLeft;
}
else
{
hitTargetBar.Anchor = hitTargetBar.Origin = Anchor.BottomLeft;
hitTargetLine.Anchor = hitTargetLine.Origin = Anchor.BottomLeft;
}
}
}
}
@@ -0,0 +1,114 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.UI.Components
{
public class DefaultKeyArea : CompositeDrawable, IKeyBindingHandler<ManiaAction>
{
private const float key_icon_size = 10;
private const float key_icon_corner_radius = 3;
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
private Container directionContainer;
private Container keyIcon;
private Drawable gradient;
[Resolved]
private Column column { get; set; }
public DefaultKeyArea()
{
RelativeSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load(IScrollingInfo scrollingInfo)
{
InternalChild = directionContainer = new Container
{
RelativeSizeAxes = Axes.X,
Height = ManiaStage.HIT_TARGET_POSITION,
Children = new[]
{
gradient = new Box
{
Name = "Key gradient",
RelativeSizeAxes = Axes.Both,
Alpha = 0.5f
},
keyIcon = new Container
{
Name = "Key icon",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(key_icon_size),
Masking = true,
CornerRadius = key_icon_corner_radius,
BorderThickness = 2,
BorderColour = Color4.White, // Not true
Children = new[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
AlwaysPresent = true
}
}
}
}
};
keyIcon.EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Radius = 5,
Colour = column.AccentColour.Opacity(0.5f),
};
direction.BindTo(scrollingInfo.Direction);
direction.BindValueChanged(onDirectionChanged, true);
}
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
{
if (direction.NewValue == ScrollingDirection.Up)
{
directionContainer.Anchor = directionContainer.Origin = Anchor.TopLeft;
gradient.Colour = ColourInfo.GradientVertical(Color4.Black, Color4.Black.Opacity(0));
}
else
{
directionContainer.Anchor = directionContainer.Origin = Anchor.BottomLeft;
gradient.Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0), Color4.Black);
}
}
public bool OnPressed(ManiaAction action)
{
if (action == column.Action.Value)
keyIcon.ScaleTo(1.4f, 50, Easing.OutQuint).Then().ScaleTo(1.3f, 250, Easing.OutQuint);
return false;
}
public void OnReleased(ManiaAction action)
{
if (action == column.Action.Value)
keyIcon.ScaleTo(1f, 125, Easing.OutQuint);
}
}
}
@@ -0,0 +1,57 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Mania.UI.Components
{
public class HitObjectArea : SkinReloadableDrawable
{
protected readonly IBindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>();
[Resolved(CanBeNull = true)]
private ManiaStage stage { get; set; }
public HitObjectArea(HitObjectContainer hitObjectContainer)
{
InternalChildren = new[]
{
hitObjectContainer,
};
}
[BackgroundDependencyLoader]
private void load(IScrollingInfo scrollingInfo)
{
Direction.BindTo(scrollingInfo.Direction);
Direction.BindValueChanged(onDirectionChanged, true);
}
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
{
base.SkinChanged(skin, allowFallback);
UpdateHitPosition();
}
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
{
UpdateHitPosition();
}
protected virtual void UpdateHitPosition()
{
float hitPosition = CurrentSkin.GetConfig<LegacyManiaSkinConfigurationLookup, float>(
new LegacyManiaSkinConfigurationLookup(stage?.Columns.Count ?? 4, LegacyManiaSkinConfigurationLookups.HitPosition))?.Value
?? ManiaStage.HIT_TARGET_POSITION;
Padding = Direction.Value == ScrollingDirection.Up
? new MarginPadding { Top = hitPosition }
: new MarginPadding { Bottom = hitPosition };
}
}
}
+2 -2
View File
@@ -12,7 +12,7 @@ using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.UI
{
internal class HitExplosion : CompositeDrawable
public class HitExplosion : CompositeDrawable
{
public override bool RemoveWhenNotAlive => true;
@@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Mania.UI
public HitExplosion(Color4 objectColour, bool isSmall = false)
{
RelativeSizeAxes = Axes.X;
Height = NotePiece.NOTE_HEIGHT;
Height = DefaultNotePiece.NOTE_HEIGHT;
// scale roughly in-line with visual appearance of notes
Scale = new Vector2(1f, 0.6f);
+48 -49
View File
@@ -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.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
@@ -12,9 +11,11 @@ using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.UI.Components;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Skinning;
using osuTK;
using osuTK.Graphics;
@@ -23,6 +24,7 @@ namespace osu.Game.Rulesets.Mania.UI
/// <summary>
/// A collection of <see cref="Column"/>s.
/// </summary>
[Cached]
public class ManiaStage : ScrollingPlayfield
{
public const float COLUMN_SPACING = 1;
@@ -32,15 +34,18 @@ namespace osu.Game.Rulesets.Mania.UI
public IReadOnlyList<Column> Columns => columnFlow.Children;
private readonly FillFlowContainer<Column> columnFlow;
private readonly Container barLineContainer;
public Container<DrawableManiaJudgement> Judgements => judgements;
private readonly JudgementContainer<DrawableManiaJudgement> judgements;
private readonly Drawable barLineContainer;
private readonly Container topLevelContainer;
private List<Color4> normalColumnColours = new List<Color4>();
private Color4 specialColumnColour;
private readonly Dictionary<ColumnType, Color4> columnColours = new Dictionary<ColumnType, Color4>
{
{ ColumnType.Even, new Color4(6, 84, 0, 255) },
{ ColumnType.Odd, new Color4(94, 0, 57, 255) },
{ ColumnType.Special, new Color4(0, 48, 63, 255) }
};
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Columns.Any(c => c.ReceivePositionalInputAt(screenSpacePos));
@@ -89,7 +94,6 @@ namespace osu.Game.Rulesets.Mania.UI
AutoSizeAxes = Axes.X,
Direction = FillDirection.Horizontal,
Padding = new MarginPadding { Left = COLUMN_SPACING, Right = COLUMN_SPACING },
Spacing = new Vector2(COLUMN_SPACING, 0)
},
}
},
@@ -102,13 +106,12 @@ namespace osu.Game.Rulesets.Mania.UI
Width = 1366, // Bar lines should only be masked on the vertical axis
BypassAutoSizeAxes = Axes.Both,
Masking = true,
Child = barLineContainer = new Container
Child = barLineContainer = new HitObjectArea(HitObjectContainer)
{
Name = "Bar lines",
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.Y,
Child = HitObjectContainer
}
},
judgements = new JudgementContainer<DrawableManiaJudgement>
@@ -125,24 +128,52 @@ namespace osu.Game.Rulesets.Mania.UI
for (int i = 0; i < definition.Columns; i++)
{
var isSpecial = definition.IsSpecialColumn(i);
var columnType = definition.GetTypeOfColumn(i);
var column = new Column(firstColumnIndex + i)
{
IsSpecial = isSpecial,
Action = { Value = isSpecial ? specialColumnStartAction++ : normalColumnStartAction++ }
ColumnType = columnType,
AccentColour = columnColours[columnType],
Action = { Value = columnType == ColumnType.Special ? specialColumnStartAction++ : normalColumnStartAction++ }
};
AddColumn(column);
}
}
Direction.BindValueChanged(dir =>
private ISkin currentSkin;
[BackgroundDependencyLoader]
private void load(ISkinSource skin)
{
currentSkin = skin;
skin.SourceChanged += onSkinChanged;
onSkinChanged();
}
private void onSkinChanged()
{
foreach (var col in columnFlow)
{
barLineContainer.Padding = new MarginPadding
if (col.Index > 0)
{
Top = dir.NewValue == ScrollingDirection.Up ? HIT_TARGET_POSITION : 0,
Bottom = dir.NewValue == ScrollingDirection.Down ? HIT_TARGET_POSITION : 0,
};
}, true);
float spacing = currentSkin.GetConfig<LegacyManiaSkinConfigurationLookup, float>(
new LegacyManiaSkinConfigurationLookup(Columns.Count, LegacyManiaSkinConfigurationLookups.ColumnSpacing, col.Index - 1))
?.Value ?? COLUMN_SPACING;
col.Margin = new MarginPadding { Left = spacing };
}
float? width = currentSkin.GetConfig<LegacyManiaSkinConfigurationLookup, float>(
new LegacyManiaSkinConfigurationLookup(Columns.Count, LegacyManiaSkinConfigurationLookups.ColumnWidth, col.Index))
?.Value;
if (width == null)
// only used by default skin (legacy skins get defaults set in LegacyManiaSkinConfiguration)
col.Width = col.IsSpecial ? Column.SPECIAL_COLUMN_WIDTH : Column.COLUMN_WIDTH;
else
col.Width = width.Value;
}
}
public void AddColumn(Column c)
@@ -195,38 +226,6 @@ namespace osu.Game.Rulesets.Mania.UI
});
}
[BackgroundDependencyLoader]
private void load()
{
normalColumnColours = new List<Color4>
{
new Color4(94, 0, 57, 255),
new Color4(6, 84, 0, 255)
};
specialColumnColour = new Color4(0, 48, 63, 255);
// Set the special column + colour + key
foreach (var column in Columns)
{
if (!column.IsSpecial)
continue;
column.AccentColour = specialColumnColour;
}
var nonSpecialColumns = Columns.Where(c => !c.IsSpecial).ToList();
// We'll set the colours of the non-special columns in a separate loop, because the non-special
// column colours are mirrored across their centre and special styles mess with this
for (int i = 0; i < Math.Ceiling(nonSpecialColumns.Count / 2f); i++)
{
Color4 colour = normalColumnColours[i % normalColumnColours.Count];
nonSpecialColumns[i].AccentColour = colour;
nonSpecialColumns[nonSpecialColumns.Count - 1 - i].AccentColour = colour;
}
}
protected override void Update()
{
// Due to masking differences, it is not possible to get the width of the columns container automatically
Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

@@ -0,0 +1,70 @@
// 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.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests
{
[TestFixture]
public class TestSceneSpinnerSpunOut : OsuTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(SpinnerDisc),
typeof(DrawableSpinner),
typeof(DrawableOsuHitObject),
typeof(OsuModSpunOut)
};
[SetUp]
public void SetUp() => Schedule(() =>
{
SelectedMods.Value = new[] { new OsuModSpunOut() };
});
[Test]
public void TestSpunOut()
{
DrawableSpinner spinner = null;
AddStep("create spinner", () => spinner = createSpinner());
AddUntilStep("wait for end", () => Time.Current > spinner.LifetimeEnd);
AddAssert("spinner is completed", () => spinner.Progress >= 1);
}
private DrawableSpinner createSpinner()
{
var spinner = new Spinner
{
StartTime = Time.Current + 500,
EndTime = Time.Current + 2500
};
spinner.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
var drawableSpinner = new DrawableSpinner(spinner)
{
Anchor = Anchor.Centre
};
foreach (var mod in SelectedMods.Value.OfType<IApplicableToDrawableHitObjects>())
mod.ApplyToDrawableHitObjects(new[] { drawableSpinner });
Add(drawableSpinner);
return drawableSpinner;
}
}
}
@@ -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.Linq;
using osu.Framework.Bindables;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
@@ -30,6 +31,22 @@ namespace osu.Game.Rulesets.Osu.Mods
Value = 5,
};
public override string SettingDescription
{
get
{
string circleSize = CircleSize.IsDefault ? string.Empty : $"CS {CircleSize.Value:N1}";
string approachRate = ApproachRate.IsDefault ? string.Empty : $"AR {ApproachRate.Value:N1}";
return string.Join(", ", new[]
{
circleSize,
base.SettingDescription,
approachRate
}.Where(s => !string.IsNullOrEmpty(s)));
}
}
protected override void TransferSettings(BeatmapDifficulty difficulty)
{
base.TransferSettings(difficulty);
+1 -1
View File
@@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Osu.Mods
void handleHitCircle(DrawableHitCircle circle)
{
if (!circle.IsHovered)
if (!circle.HitArea.IsHovered)
return;
Debug.Assert(circle.HitObject.HitWindows != null);
+27 -2
View File
@@ -2,21 +2,46 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Utils;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables;
namespace osu.Game.Rulesets.Osu.Mods
{
public class OsuModSpunOut : Mod
public class OsuModSpunOut : Mod, IApplicableToDrawableHitObjects
{
public override string Name => "Spun Out";
public override string Acronym => "SO";
public override IconUsage? Icon => OsuIcon.ModSpunout;
public override ModType Type => ModType.DifficultyReduction;
public override ModType Type => ModType.Automation;
public override string Description => @"Spinners will be automatically completed.";
public override double ScoreMultiplier => 0.9;
public override bool Ranked => true;
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(OsuModAutopilot) };
public void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
{
foreach (var hitObject in drawables)
{
if (hitObject is DrawableSpinner spinner)
{
spinner.HandleUserInput = false;
spinner.OnUpdate += onSpinnerUpdate;
}
}
}
private void onSpinnerUpdate(Drawable drawable)
{
var spinner = (DrawableSpinner)drawable;
spinner.Disc.Tracking = true;
spinner.Disc.Rotate(MathUtils.RadiansToDegrees((float)spinner.Clock.ElapsedFrameTime * 0.03f));
}
}
}
@@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
/// <summary>
/// A single follow point positioned between two adjacent <see cref="DrawableOsuHitObject"/>s.
/// </summary>
public class FollowPoint : Container
public class FollowPoint : Container, IAnimationTimeReference
{
private const float width = 8;
@@ -45,5 +45,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
}
}, confineMode: ConfineMode.NoScaling);
}
public double AnimationStartTime { get; set; }
}
}
@@ -116,6 +116,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
int point = 0;
ClearInternal();
for (int d = (int)(spacing * 1.5); d < distance - spacing; d += spacing)
{
float fraction = (float)d / distance;
@@ -126,13 +128,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
FollowPoint fp;
if (InternalChildren.Count > point)
{
fp = (FollowPoint)InternalChildren[point];
fp.ClearTransforms();
}
else
AddInternal(fp = new FollowPoint());
AddInternal(fp = new FollowPoint());
fp.Position = pointStartPosition;
fp.Rotation = rotation;
@@ -142,6 +138,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
if (firstTransformStartTime == null)
firstTransformStartTime = fadeInTime;
fp.AnimationStartTime = fadeInTime;
using (fp.BeginAbsoluteSequence(fadeInTime))
{
fp.FadeIn(osuEnd.TimeFadeIn);
@@ -30,6 +30,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public readonly SkinnableDrawable CirclePiece;
private readonly Container scaleContainer;
protected virtual OsuSkinComponents CirclePieceComponent => OsuSkinComponents.HitCircle;
public DrawableHitCircle(HitCircle h)
: base(h)
{
@@ -57,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
return true;
},
},
CirclePiece = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.HitCircle), _ => new MainCirclePiece()),
CirclePiece = new SkinnableDrawable(new OsuSkinComponent(CirclePieceComponent), _ => new MainCirclePiece()),
ApproachCircle = new ApproachCircle
{
Alpha = 0,
@@ -14,6 +14,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private readonly IBindable<Vector2> positionBindable = new Bindable<Vector2>();
private readonly IBindable<int> pathVersion = new Bindable<int>();
protected override OsuSkinComponents CirclePieceComponent => OsuSkinComponents.SliderHeadHitCircle;
private readonly Slider slider;
public DrawableSliderHead(Slider slider, HitCircle h)
@@ -87,6 +87,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public void UpdateSnakingPosition(Vector2 start, Vector2 end)
{
// When the repeat is hit, the arrow should fade out on spot rather than following the slider
if (IsHit) return;
bool isRepeatAtEnd = sliderRepeat.RepeatIndex % 2 == 0;
List<Vector2> curve = ((PlaySliderBody)drawableSlider.Body.Drawable).CurrentCurve;
@@ -176,17 +176,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected override void Update()
{
Disc.Tracking = OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false;
if (!SpmCounter.IsPresent && Disc.Tracking)
SpmCounter.FadeIn(HitObject.TimeFadeIn);
base.Update();
if (HandleUserInput)
Disc.Tracking = OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false;
}
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
if (!SpmCounter.IsPresent && Disc.Tracking)
SpmCounter.FadeIn(HitObject.TimeFadeIn);
circle.Rotation = Disc.Rotation;
Ticks.Rotation = Disc.Rotation;
SpmCounter.SetRotation(Disc.RotationAbsolute);
@@ -73,6 +73,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
}
}
/// <summary>
/// Whether currently in the correct time range to allow spinning.
/// </summary>
private bool isSpinnableTime => spinner.StartTime <= Time.Current && spinner.EndTime > Time.Current;
protected override bool OnMouseMove(MouseMoveEvent e)
{
mousePosition = Parent.ToLocalSpace(e.ScreenSpaceMousePosition);
@@ -93,27 +98,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
protected override void Update()
{
base.Update();
var thisAngle = -MathUtils.RadiansToDegrees(MathF.Atan2(mousePosition.X - DrawSize.X / 2, mousePosition.Y - DrawSize.Y / 2));
bool validAndTracking = tracking && spinner.StartTime <= Time.Current && spinner.EndTime > Time.Current;
var delta = thisAngle - lastAngle;
if (validAndTracking)
{
if (!rotationTransferred)
{
currentRotation = Rotation * 2;
rotationTransferred = true;
}
if (thisAngle - lastAngle > 180)
lastAngle += 360;
else if (lastAngle - thisAngle > 180)
lastAngle -= 360;
currentRotation += thisAngle - lastAngle;
RotationAbsolute += Math.Abs(thisAngle - lastAngle) * Math.Sign(Clock.ElapsedFrameTime);
}
if (tracking)
Rotate(delta);
lastAngle = thisAngle;
@@ -128,5 +118,38 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Rotation = (float)Interpolation.Lerp(Rotation, currentRotation / 2, Math.Clamp(Math.Abs(Time.Elapsed) / 40, 0, 1));
}
/// <summary>
/// Rotate the disc by the provided angle (in addition to any existing rotation).
/// </summary>
/// <remarks>
/// Will be a no-op if not a valid time to spin.
/// </remarks>
/// <param name="angle">The delta angle.</param>
public void Rotate(float angle)
{
if (!isSpinnableTime)
return;
if (!rotationTransferred)
{
currentRotation = Rotation * 2;
rotationTransferred = true;
}
if (angle > 180)
{
lastAngle += 360;
angle -= 360;
}
else if (-angle > 180)
{
lastAngle -= 360;
angle += 360;
}
currentRotation += angle;
RotationAbsolute += Math.Abs(angle) * Math.Sign(Clock.ElapsedFrameTime);
}
}
}
+1 -1
View File
@@ -113,7 +113,6 @@ namespace osu.Game.Rulesets.Osu
new OsuModEasy(),
new OsuModNoFail(),
new MultiMod(new OsuModHalfTime(), new OsuModDaycore()),
new OsuModSpunOut(),
};
case ModType.DifficultyIncrease:
@@ -139,6 +138,7 @@ namespace osu.Game.Rulesets.Osu
new MultiMod(new OsuModAutoplay(), new OsuModCinema()),
new OsuModRelax(),
new OsuModAutopilot(),
new OsuModSpunOut(),
};
case ModType.Fun:
@@ -13,6 +13,7 @@ namespace osu.Game.Rulesets.Osu
ApproachCircle,
ReverseArrow,
HitCircleText,
SliderHeadHitCircle,
SliderFollowCircle,
SliderBall,
SliderBody,
@@ -6,6 +6,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets.Objects.Drawables;
@@ -18,8 +19,12 @@ namespace osu.Game.Rulesets.Osu.Skinning
{
public class LegacyMainCirclePiece : CompositeDrawable
{
public LegacyMainCirclePiece()
private readonly string priorityLookup;
public LegacyMainCirclePiece(string priorityLookup = null)
{
this.priorityLookup = priorityLookup;
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
}
@@ -39,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
{
hitCircleSprite = new Sprite
{
Texture = skin.GetTexture("hitcircle"),
Texture = getTextureWithFallback(string.Empty),
Colour = drawableObject.AccentColour.Value,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -51,12 +56,17 @@ namespace osu.Game.Rulesets.Osu.Skinning
}, confineMode: ConfineMode.NoScaling),
new Sprite
{
Texture = skin.GetTexture("hitcircleoverlay"),
Texture = getTextureWithFallback("overlay"),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
}
};
bool overlayAboveNumber = skin.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.HitCircleOverlayAboveNumber)?.Value ?? true;
if (!overlayAboveNumber)
ChangeInternalChildDepth(hitCircleText, -float.MaxValue);
state.BindTo(drawableObject.State);
state.BindValueChanged(updateState, true);
@@ -65,6 +75,16 @@ namespace osu.Game.Rulesets.Osu.Skinning
indexInCurrentCombo.BindTo(osuObject.IndexInCurrentComboBindable);
indexInCurrentCombo.BindValueChanged(index => hitCircleText.Text = (index.NewValue + 1).ToString(), true);
Texture getTextureWithFallback(string name)
{
Texture tex = null;
if (!string.IsNullOrEmpty(priorityLookup))
tex = skin.GetTexture($"{priorityLookup}{name}");
return tex ?? skin.GetTexture($"hitcircle{name}");
}
}
private void updateState(ValueChangedEvent<ArmedState> state)
@@ -82,6 +82,12 @@ namespace osu.Game.Rulesets.Osu.Skinning
return null;
case OsuSkinComponents.SliderHeadHitCircle:
if (hasHitCircle.Value)
return new LegacyMainCirclePiece("sliderstartcircle");
return null;
case OsuSkinComponents.HitCircle:
if (hasHitCircle.Value)
return new LegacyMainCirclePiece();
@@ -11,6 +11,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
SliderPathRadius,
AllowSliderBallTint,
CursorExpand,
CursorRotate
CursorRotate,
HitCircleOverlayAboveNumber
}
}
@@ -0,0 +1,9 @@
[Mania]
Keys: 4
ColumnWidth: 10,10,10,10
HitPosition: 470
[Mania]
Keys: 4
ColumnWidth: 20,20,20,20
HitPosition: 460
@@ -0,0 +1,4 @@
[Mania]
Keys: 4
ColumnWidth: 10,10,10,10,10,10,10
HitPosition: 470
@@ -0,0 +1,9 @@
[Mania]
Keys: 4
ColumnWidth: 10,10,10,10
HitPosition: 470
[Mania]
Keys: 2
ColumnWidth: 20,20
HitPosition: 460
@@ -0,0 +1,4 @@
[Mania]
Keys: 4
ColumnWidth: 10,10,10,10
HitPosition: 470
@@ -0,0 +1,87 @@
// 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.Game.IO;
using osu.Game.Skinning;
using osu.Game.Tests.Resources;
namespace osu.Game.Tests.Skins
{
[TestFixture]
public class LegacyManiaSkinDecoderTest
{
[Test]
public void TestParseSingleConfig()
{
var decoder = new LegacyManiaSkinDecoder();
using (var resStream = TestResources.OpenResource("mania-skin-single.ini"))
using (var stream = new LineBufferedReader(resStream))
{
var configs = decoder.Decode(stream);
Assert.That(configs.Count, Is.EqualTo(1));
Assert.That(configs[0].Keys, Is.EqualTo(4));
Assert.That(configs[0].ColumnWidth, Is.EquivalentTo(new float[] { 16, 16, 16, 16 }));
Assert.That(configs[0].HitPosition, Is.EqualTo(16));
}
}
[Test]
public void TestParseMultipleConfig()
{
var decoder = new LegacyManiaSkinDecoder();
using (var resStream = TestResources.OpenResource("mania-skin-multiple.ini"))
using (var stream = new LineBufferedReader(resStream))
{
var configs = decoder.Decode(stream);
Assert.That(configs.Count, Is.EqualTo(2));
Assert.That(configs[0].Keys, Is.EqualTo(4));
Assert.That(configs[0].ColumnWidth, Is.EquivalentTo(new float[] { 16, 16, 16, 16 }));
Assert.That(configs[0].HitPosition, Is.EqualTo(16));
Assert.That(configs[1].Keys, Is.EqualTo(2));
Assert.That(configs[1].ColumnWidth, Is.EquivalentTo(new float[] { 32, 32 }));
Assert.That(configs[1].HitPosition, Is.EqualTo(32));
}
}
[Test]
public void TestParseDuplicateConfig()
{
var decoder = new LegacyManiaSkinDecoder();
using (var resStream = TestResources.OpenResource("mania-skin-single.ini"))
using (var stream = new LineBufferedReader(resStream))
{
var configs = decoder.Decode(stream);
Assert.That(configs.Count, Is.EqualTo(1));
Assert.That(configs[0].Keys, Is.EqualTo(4));
Assert.That(configs[0].ColumnWidth, Is.EquivalentTo(new float[] { 16, 16, 16, 16 }));
Assert.That(configs[0].HitPosition, Is.EqualTo(16));
}
}
[Test]
public void TestParseWithUnnecessaryExtraData()
{
var decoder = new LegacyManiaSkinDecoder();
using (var resStream = TestResources.OpenResource("mania-skin-extra-data.ini"))
using (var stream = new LineBufferedReader(resStream))
{
var configs = decoder.Decode(stream);
Assert.That(configs.Count, Is.EqualTo(1));
Assert.That(configs[0].Keys, Is.EqualTo(4));
Assert.That(configs[0].ColumnWidth, Is.EquivalentTo(new float[] { 16, 16, 16, 16 }));
Assert.That(configs[0].HitPosition, Is.EqualTo(16));
}
}
}
}
@@ -4,7 +4,6 @@
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Screens;
using osu.Game.Configuration;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Catch;
@@ -74,9 +73,6 @@ namespace osu.Game.Tests.Visual.Gameplay
Beatmap.Value = working;
SelectedMods.Value = new[] { ruleset.GetAllMods().First(m => m is ModNoFail) };
Player?.Exit();
Player = null;
Player = CreatePlayer(ruleset);
LoadScreen(Player);
@@ -4,8 +4,13 @@
using System.ComponentModel;
using System.Linq;
using osu.Framework.Testing;
using osu.Game.Beatmaps.Timing;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play;
using osu.Game.Screens.Play.Break;
using osu.Game.Screens.Ranking;
namespace osu.Game.Tests.Visual.Gameplay
{
@@ -16,20 +21,38 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override Player CreatePlayer(Ruleset ruleset)
{
SelectedMods.Value = SelectedMods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray();
return new TestPlayer(false, false);
SelectedMods.Value = new[] { ruleset.GetAutoplayMod() };
return new TestPlayer(false);
}
protected override void AddCheckSteps()
{
AddUntilStep("score above zero", () => Player.ScoreProcessor.TotalScore.Value > 0);
AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 2));
AddStep("seek to break time", () => Player.GameplayClockContainer.Seek(Player.ChildrenOfType<BreakTracker>().First().Breaks.First().StartTime));
AddUntilStep("wait for seek to complete", () =>
Player.HUDOverlay.Progress.ReferenceClock.CurrentTime >= Player.BreakOverlay.Breaks.First().StartTime);
AddAssert("test keys not counting", () => !Player.HUDOverlay.KeyCounter.IsCounting);
seekToBreak(0);
AddAssert("keys not counting", () => !Player.HUDOverlay.KeyCounter.IsCounting);
AddAssert("overlay displays 100% accuracy", () => Player.BreakOverlay.ChildrenOfType<BreakInfo>().Single().AccuracyDisplay.Current.Value == 1);
AddStep("rewind", () => Player.GameplayClockContainer.Seek(-80000));
AddUntilStep("key counter reset", () => Player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0));
seekToBreak(0);
seekToBreak(1);
AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime()));
AddUntilStep("results displayed", () => getResultsScreen() != null);
AddAssert("score has combo", () => getResultsScreen().Score.Combo > 100);
AddAssert("score has no misses", () => getResultsScreen().Score.Statistics[HitResult.Miss] == 0);
ResultsScreen getResultsScreen() => Stack.CurrentScreen as ResultsScreen;
}
private void seekToBreak(int breakIndex)
{
AddStep($"seek to break {breakIndex}", () => Player.GameplayClockContainer.Seek(destBreak().StartTime));
AddUntilStep("wait for seek to complete", () => Player.HUDOverlay.Progress.ReferenceClock.CurrentTime >= destBreak().StartTime);
BreakPeriod destBreak() => Player.ChildrenOfType<BreakTracker>().First().Breaks.ElementAt(breakIndex);
}
}
}
@@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddRange(new Drawable[]
{
breakTracker = new TestBreakTracker(),
breakOverlay = new BreakOverlay(true)
breakOverlay = new BreakOverlay(true, null)
{
ProcessCustomClock = false,
}
@@ -17,13 +17,12 @@ using osu.Game.Replays;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.UI;
using osu.Game.Tests.Visual;
using osu.Game.Tests.Visual.UserInterface;
using osuTK;
using osuTK.Graphics;
using osuTK.Input;
namespace osu.Game.Tests.Gameplay
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneReplayRecorder : OsuManualInputManagerTestScene
{
@@ -13,12 +13,11 @@ using osu.Game.Replays;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.UI;
using osu.Game.Tests.Visual;
using osu.Game.Tests.Visual.UserInterface;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Tests.Gameplay
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneReplayRecording : OsuTestScene
{
@@ -43,16 +43,15 @@ namespace osu.Game.Tests.Visual.Gameplay
{
new ExposedSkinnableDrawable("default", _ => new DefaultBox(), _ => true),
new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true),
new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true, ConfineMode.ScaleToFit),
new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true, ConfineMode.NoScaling)
}
},
};
});
AddAssert("check sizes", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 30, 30, 30, 50 }));
AddAssert("check sizes", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 30, 30, 50 }));
AddStep("adjust scale", () => fill.Scale = new Vector2(2));
AddAssert("check sizes unchanged by scale", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 30, 30, 30, 50 }));
AddAssert("check sizes unchanged by scale", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 30, 30, 50 }));
}
[Test]
@@ -74,7 +73,6 @@ namespace osu.Game.Tests.Visual.Gameplay
Children = new[]
{
new ExposedSkinnableDrawable("default", _ => new DefaultBox(), _ => true),
new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true),
new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true, ConfineMode.ScaleToFit),
new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true, ConfineMode.NoScaling)
}
@@ -82,9 +80,9 @@ namespace osu.Game.Tests.Visual.Gameplay
};
});
AddAssert("check sizes", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 50, 30, 50, 30 }));
AddAssert("check sizes", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 50, 50, 30 }));
AddStep("adjust scale", () => fill.Scale = new Vector2(2));
AddAssert("check sizes unchanged by scale", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 50, 30, 50, 30 }));
AddAssert("check sizes unchanged by scale", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 50, 50, 30 }));
}
[Test]
@@ -182,7 +180,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public new Drawable Drawable => base.Drawable;
public ExposedSkinnableDrawable(string name, Func<ISkinComponent, Drawable> defaultImplementation, Func<ISkinSource, bool> allowFallback = null,
ConfineMode confineMode = ConfineMode.ScaleDownToFit)
ConfineMode confineMode = ConfineMode.ScaleToFit)
: base(new TestSkinComponent(name), defaultImplementation, allowFallback, confineMode)
{
}
@@ -36,8 +36,6 @@ namespace osu.Game.Tests.Visual.Menus
[Test]
public void TestInstantLoad()
{
// visual only, very impossible to test this using asserts.
AddStep("load immediately", () =>
{
loader = new TestLoader();
@@ -46,12 +44,17 @@ namespace osu.Game.Tests.Visual.Menus
LoadScreen(loader);
});
AddAssert("spinner did not display", () => loader.LoadingSpinner?.Alpha == 0);
spinnerNotPresentOrHidden();
AddUntilStep("loaded", () => loader.ScreenLoaded);
AddUntilStep("not current", () => !loader.IsCurrentScreen());
spinnerNotPresentOrHidden();
}
private void spinnerNotPresentOrHidden() =>
AddAssert("spinner did not display", () => loader.LoadingSpinner == null || loader.LoadingSpinner.Alpha == 0);
[Test]
public void TestDelayedLoad()
{
@@ -83,6 +83,82 @@ namespace osu.Game.Tests.Visual.SongSelect
waitForSelection(set_count, 3);
}
[TestCase(true)]
[TestCase(false)]
public void TestTraversalBeyondVisible(bool forwards)
{
var sets = new List<BeatmapSetInfo>();
const int total_set_count = 200;
for (int i = 0; i < total_set_count; i++)
sets.Add(createTestBeatmapSet(i + 1));
loadBeatmaps(sets);
for (int i = 1; i < total_set_count; i += i)
selectNextAndAssert(i);
void selectNextAndAssert(int amount)
{
setSelected(forwards ? 1 : total_set_count, 1);
AddStep($"{(forwards ? "Next" : "Previous")} beatmap {amount} times", () =>
{
for (int i = 0; i < amount; i++)
{
carousel.SelectNext(forwards ? 1 : -1);
}
});
waitForSelection(forwards ? amount + 1 : total_set_count - amount);
}
}
[Test]
public void TestTraversalBeyondVisibleDifficulties()
{
var sets = new List<BeatmapSetInfo>();
const int total_set_count = 20;
for (int i = 0; i < total_set_count; i++)
sets.Add(createTestBeatmapSet(i + 1));
loadBeatmaps(sets);
// Selects next set once, difficulty index doesn't change
selectNextAndAssert(3, true, 2, 1);
// Selects next set 16 times (50 \ 3 == 16), difficulty index changes twice (50 % 3 == 2)
selectNextAndAssert(50, true, 17, 3);
// Travels around the carousel thrice (200 \ 60 == 3)
// continues to select 20 times (200 \ 60 == 20)
// selects next set 6 times (20 \ 3 == 6)
// difficulty index changes twice (20 % 3 == 2)
selectNextAndAssert(200, true, 7, 3);
// All same but in reverse
selectNextAndAssert(3, false, 19, 3);
selectNextAndAssert(50, false, 4, 1);
selectNextAndAssert(200, false, 14, 1);
void selectNextAndAssert(int amount, bool forwards, int expectedSet, int expectedDiff)
{
// Select very first or very last difficulty
setSelected(forwards ? 1 : 20, forwards ? 1 : 3);
AddStep($"{(forwards ? "Next" : "Previous")} difficulty {amount} times", () =>
{
for (int i = 0; i < amount; i++)
carousel.SelectNext(forwards ? 1 : -1, false);
});
waitForSelection(expectedSet, expectedDiff);
}
}
/// <summary>
/// Test filtering
/// </summary>
@@ -91,13 +91,14 @@ namespace osu.Game.Tests.Visual.UserInterface
var easierMods = osu.GetModsFor(ModType.DifficultyReduction);
var harderMods = osu.GetModsFor(ModType.DifficultyIncrease);
var conversionMods = osu.GetModsFor(ModType.Conversion);
var noFailMod = osu.GetModsFor(ModType.DifficultyReduction).FirstOrDefault(m => m is OsuModNoFail);
var hiddenMod = harderMods.FirstOrDefault(m => m is OsuModHidden);
var doubleTimeMod = harderMods.OfType<MultiMod>().FirstOrDefault(m => m.Mods.Any(a => a is OsuModDoubleTime));
var spunOutMod = easierMods.FirstOrDefault(m => m is OsuModSpunOut);
var targetMod = conversionMods.FirstOrDefault(m => m is OsuModTarget);
var easy = easierMods.FirstOrDefault(m => m is OsuModEasy);
var hardRock = harderMods.FirstOrDefault(m => m is OsuModHardRock);
@@ -109,7 +110,7 @@ namespace osu.Game.Tests.Visual.UserInterface
testMultiplierTextColour(noFailMod, () => modSelect.LowMultiplierColour);
testMultiplierTextColour(hiddenMod, () => modSelect.HighMultiplierColour);
testUnimplementedMod(spunOutMod);
testUnimplementedMod(targetMod);
}
[Test]
+22 -7
View File
@@ -87,7 +87,7 @@ namespace osu.Game.Beatmaps
protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path)?.ToLowerInvariant() == ".osz";
protected override Task Populate(BeatmapSetInfo beatmapSet, ArchiveReader archive, CancellationToken cancellationToken = default)
protected override async Task Populate(BeatmapSetInfo beatmapSet, ArchiveReader archive, CancellationToken cancellationToken = default)
{
if (archive != null)
beatmapSet.Beatmaps = createBeatmapDifficulties(beatmapSet.Files);
@@ -103,7 +103,19 @@ namespace osu.Game.Beatmaps
validateOnlineIds(beatmapSet);
return updateQueue.UpdateAsync(beatmapSet, cancellationToken);
bool hadOnlineBeatmapIDs = beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID > 0);
await updateQueue.UpdateAsync(beatmapSet, cancellationToken);
// ensure at least one beatmap was able to retrieve or keep an online ID, else drop the set ID.
if (hadOnlineBeatmapIDs && !beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID > 0))
{
if (beatmapSet.OnlineBeatmapSetID != null)
{
beatmapSet.OnlineBeatmapSetID = null;
LogForModel(beatmapSet, "Disassociating beatmap set ID due to loss of all beatmap IDs");
}
}
}
protected override void PreImport(BeatmapSetInfo beatmapSet)
@@ -447,12 +459,15 @@ namespace osu.Game.Beatmaps
var res = req.Result;
beatmap.Status = res.Status;
beatmap.BeatmapSet.Status = res.BeatmapSet.Status;
beatmap.BeatmapSet.OnlineBeatmapSetID = res.OnlineBeatmapSetID;
beatmap.OnlineBeatmapID = res.OnlineBeatmapID;
if (res != null)
{
beatmap.Status = res.Status;
beatmap.BeatmapSet.Status = res.BeatmapSet.Status;
beatmap.BeatmapSet.OnlineBeatmapSetID = res.OnlineBeatmapSetID;
beatmap.OnlineBeatmapID = res.OnlineBeatmapID;
LogForModel(set, $"Online retrieval mapped {beatmap} to {res.OnlineBeatmapSetID} / {res.OnlineBeatmapID}.");
LogForModel(set, $"Online retrieval mapped {beatmap} to {res.OnlineBeatmapSetID} / {res.OnlineBeatmapID}.");
}
}
catch (Exception e)
{
+11 -1
View File
@@ -41,6 +41,7 @@ namespace osu.Game.Beatmaps.Formats
section = Section.None;
}
OnBeginNewSection(section);
continue;
}
@@ -57,6 +58,14 @@ namespace osu.Game.Beatmaps.Formats
protected virtual bool ShouldSkipLine(string line) => string.IsNullOrWhiteSpace(line) || line.AsSpan().TrimStart().StartsWith("//".AsSpan(), StringComparison.Ordinal);
/// <summary>
/// Invoked when a new <see cref="Section"/> has been entered.
/// </summary>
/// <param name="section">The entered <see cref="Section"/>.</param>
protected virtual void OnBeginNewSection(Section section)
{
}
protected virtual void ParseLine(T output, Section section, string line)
{
line = StripComments(line);
@@ -139,7 +148,8 @@ namespace osu.Game.Beatmaps.Formats
Colours,
HitObjects,
Variables,
Fonts
Fonts,
Mania
}
internal class LegacyDifficultyControlPoint : DifficultyControlPoint
+2 -2
View File
@@ -17,9 +17,9 @@ namespace osu.Game.IO
private readonly StreamReader streamReader;
private readonly Queue<string> lineBuffer;
public LineBufferedReader(Stream stream)
public LineBufferedReader(Stream stream, bool leaveOpen = false)
{
streamReader = new StreamReader(stream);
streamReader = new StreamReader(stream, Encoding.UTF8, true, 1024, leaveOpen);
lineBuffer = new Queue<string>();
}
+2 -2
View File
@@ -12,11 +12,11 @@ namespace osu.Game.Online.API
/// An API request with a well-defined response type.
/// </summary>
/// <typeparam name="T">Type of the response (used for deserialisation).</typeparam>
public abstract class APIRequest<T> : APIRequest
public abstract class APIRequest<T> : APIRequest where T : class
{
protected override WebRequest CreateWebRequest() => new OsuJsonWebRequest<T>(Uri);
public T Result => ((OsuJsonWebRequest<T>)WebRequest).ResponseObject;
public T Result => ((OsuJsonWebRequest<T>)WebRequest)?.ResponseObject;
protected APIRequest()
{
@@ -6,7 +6,7 @@ using osu.Game.Rulesets;
namespace osu.Game.Online.API.Requests
{
public abstract class GetRankingsRequest<TModel> : APIRequest<TModel>
public abstract class GetRankingsRequest<TModel> : APIRequest<TModel> where TModel : class
{
private readonly RulesetInfo ruleset;
private readonly int page;
@@ -6,7 +6,7 @@ using osu.Framework.IO.Network;
namespace osu.Game.Online.API.Requests
{
public abstract class PaginatedAPIRequest<T> : APIRequest<T>
public abstract class PaginatedAPIRequest<T> : APIRequest<T> where T : class
{
private readonly int page;
private readonly int itemsPerPage;
+51
View File
@@ -2,9 +2,15 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Newtonsoft.Json;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
using osu.Game.Configuration;
using osu.Game.IO.Serialization;
using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Mods
{
@@ -42,6 +48,51 @@ namespace osu.Game.Rulesets.Mods
[JsonIgnore]
public virtual string Description => string.Empty;
/// <summary>
/// The tooltip to display for this mod when used in a <see cref="ModIcon"/>.
/// </summary>
/// <remarks>
/// Differs from <see cref="Name"/>, as the value of attributes (AR, CS, etc) changeable via the mod
/// are displayed in the tooltip.
/// </remarks>
[JsonIgnore]
public string IconTooltip
{
get
{
string description = SettingDescription;
return string.IsNullOrEmpty(description) ? Name : $"{Name} ({description})";
}
}
/// <summary>
/// The description of editable settings of a mod to use in the <see cref="IconTooltip"/>.
/// </summary>
/// <remarks>
/// Parentheses are added to the tooltip, surrounding the value of this property. If this property is <c>string.Empty</c>,
/// the tooltip will not have parentheses.
/// </remarks>
public virtual string SettingDescription
{
get
{
var tooltipTexts = new List<string>();
foreach ((SettingSourceAttribute attr, PropertyInfo property) in this.GetOrderedSettingsSourceProperties())
{
object bindableObj = property.GetValue(this);
if ((bindableObj as IHasDefaultValue)?.IsDefault == true)
continue;
tooltipTexts.Add($"{attr.Label} {bindableObj}");
}
return string.Join(", ", tooltipTexts.Where(s => !string.IsNullOrEmpty(s)));
}
}
/// <summary>
/// The score multiplier of this mod.
/// </summary>
+17 -1
View File
@@ -7,6 +7,7 @@ using osu.Framework.Graphics.Sprites;
using System;
using System.Collections.Generic;
using osu.Game.Configuration;
using System.Linq;
namespace osu.Game.Rulesets.Mods
{
@@ -52,6 +53,21 @@ namespace osu.Game.Rulesets.Mods
Value = 5,
};
public override string SettingDescription
{
get
{
string drainRate = DrainRate.IsDefault ? string.Empty : $"HP {DrainRate.Value:N1}";
string overallDifficulty = OverallDifficulty.IsDefault ? string.Empty : $"OD {OverallDifficulty.Value:N1}";
return string.Join(", ", new[]
{
drainRate,
overallDifficulty
}.Where(s => !string.IsNullOrEmpty(s)));
}
}
private BeatmapDifficulty difficulty;
public void ReadFromDifficulty(BeatmapDifficulty difficulty)
@@ -79,7 +95,7 @@ namespace osu.Game.Rulesets.Mods
/// <summary>
/// Transfer a setting from <see cref="BeatmapDifficulty"/> to a configuration bindable.
/// Only performs the transfer if the user it not currently overriding..
/// Only performs the transfer if the user is not currently overriding.
/// </summary>
protected void TransferSetting<T>(BindableNumber<T> bindable, T beatmapDefault)
where T : struct, IComparable<T>, IConvertible, IEquatable<T>
+3
View File
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using Humanizer;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
using osu.Game.Beatmaps;
@@ -28,6 +29,8 @@ namespace osu.Game.Rulesets.Mods
MaxValue = 10
};
public override string SettingDescription => Retries.IsDefault ? string.Empty : $"{"lives".ToQuantity(Retries.Value)}";
private int retries;
private BindableNumber<double> health;
+2
View File
@@ -15,5 +15,7 @@ namespace osu.Game.Rulesets.Mods
{
track.AddAdjustment(AdjustableProperty.Tempo, SpeedChange);
}
public override string SettingDescription => SpeedChange.IsDefault ? string.Empty : $"{SpeedChange.Value:N2}x";
}
}
+2
View File
@@ -26,6 +26,8 @@ namespace osu.Game.Rulesets.Mods
[SettingSource("Final rate", "The final speed to ramp to")]
public abstract BindableNumber<double> FinalRate { get; }
public override string SettingDescription => $"{InitialRate.Value:N2}x to {FinalRate.Value:N2}x";
private double finalRateTime;
private double beginRampTime;
@@ -38,6 +38,15 @@ namespace osu.Game.Rulesets.Objects.Drawables
private readonly Lazy<List<DrawableHitObject>> nestedHitObjects = new Lazy<List<DrawableHitObject>>();
public IReadOnlyList<DrawableHitObject> NestedHitObjects => nestedHitObjects.IsValueCreated ? nestedHitObjects.Value : (IReadOnlyList<DrawableHitObject>)Array.Empty<DrawableHitObject>();
/// <summary>
/// Whether this object should handle any user input events.
/// </summary>
public bool HandleUserInput { get; set; } = true;
public override bool PropagatePositionalInputSubTree => HandleUserInput;
public override bool PropagateNonPositionalInputSubTree => HandleUserInput;
/// <summary>
/// Invoked when a <see cref="JudgementResult"/> has been applied by this <see cref="DrawableHitObject"/> or a nested <see cref="DrawableHitObject"/>.
/// </summary>
+1 -3
View File
@@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.UI
private readonly ModType type;
public virtual string TooltipText { get; }
public virtual string TooltipText => mod.IconTooltip;
private Mod mod;
@@ -48,8 +48,6 @@ namespace osu.Game.Rulesets.UI
type = mod.Type;
TooltipText = mod.Name;
Size = new Vector2(size);
Children = new Drawable[]
+2 -6
View File
@@ -10,7 +10,6 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods;
using osuTK;
@@ -62,10 +61,7 @@ namespace osu.Game.Rulesets.UI
hitObjectContainerLazy = new Lazy<HitObjectContainer>(CreateHitObjectContainer);
}
[Resolved]
private IBindable<WorkingBeatmap> beatmap { get; set; }
[Resolved]
[Resolved(CanBeNull = true)]
private IReadOnlyList<Mod> mods { get; set; }
[BackgroundDependencyLoader]
@@ -137,7 +133,7 @@ namespace osu.Game.Rulesets.UI
{
base.Update();
if (beatmap != null)
if (mods != null)
{
foreach (var mod in mods)
{

Some files were not shown because too many files have changed in this diff Show More