mirror of
https://github.com/ppy/osu.git
synced 2025-03-24 03:17:21 +08:00
Merge branch 'master' into fix-relax-replays
This commit is contained in:
commit
e19a56764b
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
@ -0,0 +1,6 @@
|
||||
[General]
|
||||
Version: 2.4
|
||||
|
||||
[Mania]
|
||||
Keys: 4
|
||||
ColumnLineWidth: 3,1,3,1,1
|
@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Width = 0.5f,
|
||||
Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground, 0), _ => new DefaultColumnBackground())
|
||||
Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground, 1), _ => new DefaultColumnBackground())
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
}
|
||||
|
@ -27,7 +27,6 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
private const double time_after_tail = 5250;
|
||||
|
||||
private List<JudgementResult> judgementResults;
|
||||
private bool allJudgedFired;
|
||||
|
||||
/// <summary>
|
||||
/// -----[ ]-----
|
||||
@ -283,20 +282,15 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
{
|
||||
if (currentPlayer == p) judgementResults.Add(result);
|
||||
};
|
||||
p.ScoreProcessor.AllJudged += () =>
|
||||
{
|
||||
if (currentPlayer == p) allJudgedFired = true;
|
||||
};
|
||||
};
|
||||
|
||||
LoadScreen(currentPlayer = p);
|
||||
allJudgedFired = false;
|
||||
judgementResults = new List<JudgementResult>();
|
||||
});
|
||||
|
||||
AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
|
||||
AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
|
||||
AddUntilStep("Wait for all judged", () => allJudgedFired);
|
||||
AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value);
|
||||
}
|
||||
|
||||
private class ScoreAccessibleReplayPlayer : ReplayPlayer
|
||||
|
@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
||||
{
|
||||
TargetColumns = (int)Math.Max(1, roundedCircleSize);
|
||||
|
||||
if (TargetColumns >= 10)
|
||||
if (TargetColumns > ManiaRuleset.MAX_STAGE_KEYS)
|
||||
{
|
||||
TargetColumns /= 2;
|
||||
Dual = true;
|
||||
|
64
osu.Game.Rulesets.Mania/DualStageVariantGenerator.cs
Normal file
64
osu.Game.Rulesets.Mania/DualStageVariantGenerator.cs
Normal file
@ -0,0 +1,64 @@
|
||||
// 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 System.Linq;
|
||||
using osu.Framework.Input.Bindings;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania
|
||||
{
|
||||
public class DualStageVariantGenerator
|
||||
{
|
||||
private readonly int singleStageVariant;
|
||||
private readonly InputKey[] stage1LeftKeys;
|
||||
private readonly InputKey[] stage1RightKeys;
|
||||
private readonly InputKey[] stage2LeftKeys;
|
||||
private readonly InputKey[] stage2RightKeys;
|
||||
|
||||
public DualStageVariantGenerator(int singleStageVariant)
|
||||
{
|
||||
this.singleStageVariant = singleStageVariant;
|
||||
|
||||
// 10K is special because it expands towards the centre of the keyboard (VM/BN), rather than towards the edges of the keyboard.
|
||||
if (singleStageVariant == 10)
|
||||
{
|
||||
stage1LeftKeys = new[] { InputKey.Q, InputKey.W, InputKey.E, InputKey.R, InputKey.V };
|
||||
stage1RightKeys = new[] { InputKey.M, InputKey.I, InputKey.O, InputKey.P, InputKey.BracketLeft };
|
||||
|
||||
stage2LeftKeys = new[] { InputKey.S, InputKey.D, InputKey.F, InputKey.G, InputKey.B };
|
||||
stage2RightKeys = new[] { InputKey.N, InputKey.J, InputKey.K, InputKey.L, InputKey.Semicolon };
|
||||
}
|
||||
else
|
||||
{
|
||||
stage1LeftKeys = new[] { InputKey.Q, InputKey.W, InputKey.E, InputKey.R };
|
||||
stage1RightKeys = new[] { InputKey.I, InputKey.O, InputKey.P, InputKey.BracketLeft };
|
||||
|
||||
stage2LeftKeys = new[] { InputKey.S, InputKey.D, InputKey.F, InputKey.G };
|
||||
stage2RightKeys = new[] { InputKey.J, InputKey.K, InputKey.L, InputKey.Semicolon };
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<KeyBinding> GenerateMappings()
|
||||
{
|
||||
var stage1Bindings = new VariantMappingGenerator
|
||||
{
|
||||
LeftKeys = stage1LeftKeys,
|
||||
RightKeys = stage1RightKeys,
|
||||
SpecialKey = InputKey.V,
|
||||
SpecialAction = ManiaAction.Special1,
|
||||
NormalActionStart = ManiaAction.Key1
|
||||
}.GenerateKeyBindingsFor(singleStageVariant, out var nextNormal);
|
||||
|
||||
var stage2Bindings = new VariantMappingGenerator
|
||||
{
|
||||
LeftKeys = stage2LeftKeys,
|
||||
RightKeys = stage2RightKeys,
|
||||
SpecialKey = InputKey.B,
|
||||
SpecialAction = ManiaAction.Special2,
|
||||
NormalActionStart = nextNormal
|
||||
}.GenerateKeyBindingsFor(singleStageVariant, out _);
|
||||
|
||||
return stage1Bindings.Concat(stage2Bindings);
|
||||
}
|
||||
}
|
||||
}
|
@ -78,5 +78,11 @@ namespace osu.Game.Rulesets.Mania
|
||||
|
||||
[Description("Key 18")]
|
||||
Key18,
|
||||
|
||||
[Description("Key 19")]
|
||||
Key19,
|
||||
|
||||
[Description("Key 20")]
|
||||
Key20,
|
||||
}
|
||||
}
|
||||
|
@ -35,6 +35,11 @@ namespace osu.Game.Rulesets.Mania
|
||||
{
|
||||
public class ManiaRuleset : Ruleset, ILegacyRuleset
|
||||
{
|
||||
/// <summary>
|
||||
/// The maximum number of supported keys in a single stage.
|
||||
/// </summary>
|
||||
public const int MAX_STAGE_KEYS = 10;
|
||||
|
||||
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => new DrawableManiaRuleset(this, beatmap, mods);
|
||||
|
||||
public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor();
|
||||
@ -202,6 +207,7 @@ namespace osu.Game.Rulesets.Mania
|
||||
new ManiaModKey7(),
|
||||
new ManiaModKey8(),
|
||||
new ManiaModKey9(),
|
||||
new ManiaModKey10(),
|
||||
new ManiaModKey1(),
|
||||
new ManiaModKey2(),
|
||||
new ManiaModKey3()),
|
||||
@ -250,9 +256,9 @@ namespace osu.Game.Rulesets.Mania
|
||||
{
|
||||
get
|
||||
{
|
||||
for (int i = 1; i <= 9; i++)
|
||||
for (int i = 1; i <= MAX_STAGE_KEYS; i++)
|
||||
yield return (int)PlayfieldType.Single + i;
|
||||
for (int i = 2; i <= 18; i += 2)
|
||||
for (int i = 2; i <= MAX_STAGE_KEYS * 2; i += 2)
|
||||
yield return (int)PlayfieldType.Dual + i;
|
||||
}
|
||||
}
|
||||
@ -262,73 +268,10 @@ namespace osu.Game.Rulesets.Mania
|
||||
switch (getPlayfieldType(variant))
|
||||
{
|
||||
case PlayfieldType.Single:
|
||||
return new VariantMappingGenerator
|
||||
{
|
||||
LeftKeys = new[]
|
||||
{
|
||||
InputKey.A,
|
||||
InputKey.S,
|
||||
InputKey.D,
|
||||
InputKey.F
|
||||
},
|
||||
RightKeys = new[]
|
||||
{
|
||||
InputKey.J,
|
||||
InputKey.K,
|
||||
InputKey.L,
|
||||
InputKey.Semicolon
|
||||
},
|
||||
SpecialKey = InputKey.Space,
|
||||
SpecialAction = ManiaAction.Special1,
|
||||
NormalActionStart = ManiaAction.Key1,
|
||||
}.GenerateKeyBindingsFor(variant, out _);
|
||||
return new SingleStageVariantGenerator(variant).GenerateMappings();
|
||||
|
||||
case PlayfieldType.Dual:
|
||||
int keys = getDualStageKeyCount(variant);
|
||||
|
||||
var stage1Bindings = new VariantMappingGenerator
|
||||
{
|
||||
LeftKeys = new[]
|
||||
{
|
||||
InputKey.Q,
|
||||
InputKey.W,
|
||||
InputKey.E,
|
||||
InputKey.R,
|
||||
},
|
||||
RightKeys = new[]
|
||||
{
|
||||
InputKey.X,
|
||||
InputKey.C,
|
||||
InputKey.V,
|
||||
InputKey.B
|
||||
},
|
||||
SpecialKey = InputKey.S,
|
||||
SpecialAction = ManiaAction.Special1,
|
||||
NormalActionStart = ManiaAction.Key1
|
||||
}.GenerateKeyBindingsFor(keys, out var nextNormal);
|
||||
|
||||
var stage2Bindings = new VariantMappingGenerator
|
||||
{
|
||||
LeftKeys = new[]
|
||||
{
|
||||
InputKey.Number7,
|
||||
InputKey.Number8,
|
||||
InputKey.Number9,
|
||||
InputKey.Number0
|
||||
},
|
||||
RightKeys = new[]
|
||||
{
|
||||
InputKey.K,
|
||||
InputKey.L,
|
||||
InputKey.Semicolon,
|
||||
InputKey.Quote
|
||||
},
|
||||
SpecialKey = InputKey.I,
|
||||
SpecialAction = ManiaAction.Special2,
|
||||
NormalActionStart = nextNormal
|
||||
}.GenerateKeyBindingsFor(keys, out _);
|
||||
|
||||
return stage1Bindings.Concat(stage2Bindings);
|
||||
return new DualStageVariantGenerator(getDualStageKeyCount(variant)).GenerateMappings();
|
||||
}
|
||||
|
||||
return Array.Empty<KeyBinding>();
|
||||
@ -364,59 +307,6 @@ namespace osu.Game.Rulesets.Mania
|
||||
{
|
||||
return (PlayfieldType)Enum.GetValues(typeof(PlayfieldType)).Cast<int>().OrderByDescending(i => i).First(v => variant >= v);
|
||||
}
|
||||
|
||||
private class VariantMappingGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// All the <see cref="InputKey"/>s available to the left hand.
|
||||
/// </summary>
|
||||
public InputKey[] LeftKeys;
|
||||
|
||||
/// <summary>
|
||||
/// All the <see cref="InputKey"/>s available to the right hand.
|
||||
/// </summary>
|
||||
public InputKey[] RightKeys;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="InputKey"/> for the special key.
|
||||
/// </summary>
|
||||
public InputKey SpecialKey;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ManiaAction"/> at which the normal columns should begin.
|
||||
/// </summary>
|
||||
public ManiaAction NormalActionStart;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ManiaAction"/> for the special column.
|
||||
/// </summary>
|
||||
public ManiaAction SpecialAction;
|
||||
|
||||
/// <summary>
|
||||
/// Generates a list of <see cref="KeyBinding"/>s for a specific number of columns.
|
||||
/// </summary>
|
||||
/// <param name="columns">The number of columns that need to be bound.</param>
|
||||
/// <param name="nextNormalAction">The next <see cref="ManiaAction"/> to use for normal columns.</param>
|
||||
/// <returns>The keybindings.</returns>
|
||||
public IEnumerable<KeyBinding> GenerateKeyBindingsFor(int columns, out ManiaAction nextNormalAction)
|
||||
{
|
||||
ManiaAction currentNormalAction = NormalActionStart;
|
||||
|
||||
var bindings = new List<KeyBinding>();
|
||||
|
||||
for (int i = LeftKeys.Length - columns / 2; i < LeftKeys.Length; i++)
|
||||
bindings.Add(new KeyBinding(LeftKeys[i], currentNormalAction++));
|
||||
|
||||
if (columns % 2 == 1)
|
||||
bindings.Add(new KeyBinding(SpecialKey, SpecialAction));
|
||||
|
||||
for (int i = 0; i < columns / 2; i++)
|
||||
bindings.Add(new KeyBinding(RightKeys[i], currentNormalAction++));
|
||||
|
||||
nextNormalAction = currentNormalAction;
|
||||
return bindings;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum PlayfieldType
|
||||
|
13
osu.Game.Rulesets.Mania/Mods/ManiaModKey10.cs
Normal file
13
osu.Game.Rulesets.Mania/Mods/ManiaModKey10.cs
Normal file
@ -0,0 +1,13 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Mods
|
||||
{
|
||||
public class ManiaModKey10 : ManiaKeyMod
|
||||
{
|
||||
public override int KeyCount => 10;
|
||||
public override string Name => "Ten Keys";
|
||||
public override string Acronym => "10K";
|
||||
public override string Description => @"Play with ten keys.";
|
||||
}
|
||||
}
|
41
osu.Game.Rulesets.Mania/SingleStageVariantGenerator.cs
Normal file
41
osu.Game.Rulesets.Mania/SingleStageVariantGenerator.cs
Normal file
@ -0,0 +1,41 @@
|
||||
// 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.Framework.Input.Bindings;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania
|
||||
{
|
||||
public class SingleStageVariantGenerator
|
||||
{
|
||||
private readonly int variant;
|
||||
private readonly InputKey[] leftKeys;
|
||||
private readonly InputKey[] rightKeys;
|
||||
|
||||
public SingleStageVariantGenerator(int variant)
|
||||
{
|
||||
this.variant = variant;
|
||||
|
||||
// 10K is special because it expands towards the centre of the keyboard (V/N), rather than towards the edges of the keyboard.
|
||||
if (variant == 10)
|
||||
{
|
||||
leftKeys = new[] { InputKey.A, InputKey.S, InputKey.D, InputKey.F, InputKey.V };
|
||||
rightKeys = new[] { InputKey.N, InputKey.J, InputKey.K, InputKey.L, InputKey.Semicolon };
|
||||
}
|
||||
else
|
||||
{
|
||||
leftKeys = new[] { InputKey.A, InputKey.S, InputKey.D, InputKey.F };
|
||||
rightKeys = new[] { InputKey.J, InputKey.K, InputKey.L, InputKey.Semicolon };
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<KeyBinding> GenerateMappings() => new VariantMappingGenerator
|
||||
{
|
||||
LeftKeys = leftKeys,
|
||||
RightKeys = rightKeys,
|
||||
SpecialKey = InputKey.Space,
|
||||
SpecialAction = ManiaAction.Special1,
|
||||
NormalActionStart = ManiaAction.Key1,
|
||||
}.GenerateKeyBindingsFor(variant, out _);
|
||||
}
|
||||
}
|
@ -67,6 +67,7 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Width = leftLineWidth,
|
||||
Scale = new Vector2(0.740f, 1),
|
||||
Colour = lineColour,
|
||||
Alpha = hasLeftLine ? 1 : 0
|
||||
},
|
||||
@ -76,6 +77,7 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
||||
Origin = Anchor.TopRight,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Width = rightLineWidth,
|
||||
Scale = new Vector2(0.740f, 1),
|
||||
Colour = lineColour,
|
||||
Alpha = hasRightLine ? 1 : 0
|
||||
},
|
||||
|
61
osu.Game.Rulesets.Mania/VariantMappingGenerator.cs
Normal file
61
osu.Game.Rulesets.Mania/VariantMappingGenerator.cs
Normal file
@ -0,0 +1,61 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Input.Bindings;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania
|
||||
{
|
||||
public class VariantMappingGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// All the <see cref="InputKey"/>s available to the left hand.
|
||||
/// </summary>
|
||||
public InputKey[] LeftKeys;
|
||||
|
||||
/// <summary>
|
||||
/// All the <see cref="InputKey"/>s available to the right hand.
|
||||
/// </summary>
|
||||
public InputKey[] RightKeys;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="InputKey"/> for the special key.
|
||||
/// </summary>
|
||||
public InputKey SpecialKey;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ManiaAction"/> at which the normal columns should begin.
|
||||
/// </summary>
|
||||
public ManiaAction NormalActionStart;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ManiaAction"/> for the special column.
|
||||
/// </summary>
|
||||
public ManiaAction SpecialAction;
|
||||
|
||||
/// <summary>
|
||||
/// Generates a list of <see cref="KeyBinding"/>s for a specific number of columns.
|
||||
/// </summary>
|
||||
/// <param name="columns">The number of columns that need to be bound.</param>
|
||||
/// <param name="nextNormalAction">The next <see cref="ManiaAction"/> to use for normal columns.</param>
|
||||
/// <returns>The keybindings.</returns>
|
||||
public IEnumerable<KeyBinding> GenerateKeyBindingsFor(int columns, out ManiaAction nextNormalAction)
|
||||
{
|
||||
ManiaAction currentNormalAction = NormalActionStart;
|
||||
|
||||
var bindings = new List<KeyBinding>();
|
||||
|
||||
for (int i = LeftKeys.Length - columns / 2; i < LeftKeys.Length; i++)
|
||||
bindings.Add(new KeyBinding(LeftKeys[i], currentNormalAction++));
|
||||
|
||||
if (columns % 2 == 1)
|
||||
bindings.Add(new KeyBinding(SpecialKey, SpecialAction));
|
||||
|
||||
for (int i = 0; i < columns / 2; i++)
|
||||
bindings.Add(new KeyBinding(RightKeys[i], currentNormalAction++));
|
||||
|
||||
nextNormalAction = currentNormalAction;
|
||||
return bindings;
|
||||
}
|
||||
}
|
||||
}
|
@ -354,7 +354,6 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
|
||||
private ScoreAccessibleReplayPlayer currentPlayer;
|
||||
private List<JudgementResult> judgementResults;
|
||||
private bool allJudgedFired;
|
||||
|
||||
private void performTest(List<OsuHitObject> hitObjects, List<ReplayFrame> frames)
|
||||
{
|
||||
@ -380,20 +379,15 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
if (currentPlayer == p) judgementResults.Add(result);
|
||||
};
|
||||
p.ScoreProcessor.AllJudged += () =>
|
||||
{
|
||||
if (currentPlayer == p) allJudgedFired = true;
|
||||
};
|
||||
};
|
||||
|
||||
LoadScreen(currentPlayer = p);
|
||||
allJudgedFired = false;
|
||||
judgementResults = new List<JudgementResult>();
|
||||
});
|
||||
|
||||
AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
|
||||
AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
|
||||
AddUntilStep("Wait for all judged", () => allJudgedFired);
|
||||
AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value);
|
||||
}
|
||||
|
||||
private class TestHitCircle : HitCircle
|
||||
|
@ -47,7 +47,6 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
private const double time_slider_end = 4000;
|
||||
|
||||
private List<JudgementResult> judgementResults;
|
||||
private bool allJudgedFired;
|
||||
|
||||
/// <summary>
|
||||
/// Scenario:
|
||||
@ -375,20 +374,15 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
if (currentPlayer == p) judgementResults.Add(result);
|
||||
};
|
||||
p.ScoreProcessor.AllJudged += () =>
|
||||
{
|
||||
if (currentPlayer == p) allJudgedFired = true;
|
||||
};
|
||||
};
|
||||
|
||||
LoadScreen(currentPlayer = p);
|
||||
allJudgedFired = false;
|
||||
judgementResults = new List<JudgementResult>();
|
||||
});
|
||||
|
||||
AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
|
||||
AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
|
||||
AddUntilStep("Wait for all judged", () => allJudgedFired);
|
||||
AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value);
|
||||
}
|
||||
|
||||
private class ScoreAccessibleReplayPlayer : ReplayPlayer
|
||||
|
@ -6,7 +6,7 @@ using System.Collections.Generic;
|
||||
using osu.Game.Rulesets.Taiko.Skinning;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Tests
|
||||
namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||
{
|
||||
public abstract class TaikoSkinnableTestScene : SkinnableTestScene
|
||||
{
|
@ -0,0 +1,86 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Rulesets.Taiko.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Taiko.Skinning;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestSceneDrawableDrumRoll : TaikoSkinnableTestScene
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[]
|
||||
{
|
||||
typeof(DrawableDrumRoll),
|
||||
typeof(DrawableDrumRollTick),
|
||||
typeof(LegacyDrumRoll),
|
||||
}).ToList();
|
||||
|
||||
[Cached(typeof(IScrollingInfo))]
|
||||
private ScrollingTestContainer.TestScrollingInfo info = new ScrollingTestContainer.TestScrollingInfo
|
||||
{
|
||||
Direction = { Value = ScrollingDirection.Left },
|
||||
TimeRange = { Value = 5000 },
|
||||
};
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
AddStep("Drum roll", () => SetContents(() =>
|
||||
{
|
||||
var hoc = new ScrollingHitObjectContainer();
|
||||
|
||||
hoc.Add(new DrawableDrumRoll(createDrumRollAtCurrentTime())
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Width = 500,
|
||||
});
|
||||
|
||||
return hoc;
|
||||
}));
|
||||
|
||||
AddStep("Drum roll (strong)", () => SetContents(() =>
|
||||
{
|
||||
var hoc = new ScrollingHitObjectContainer();
|
||||
|
||||
hoc.Add(new DrawableDrumRoll(createDrumRollAtCurrentTime(true))
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Width = 500,
|
||||
});
|
||||
|
||||
return hoc;
|
||||
}));
|
||||
}
|
||||
|
||||
private DrumRoll createDrumRollAtCurrentTime(bool strong = false)
|
||||
{
|
||||
var drumroll = new DrumRoll
|
||||
{
|
||||
IsStrong = strong,
|
||||
StartTime = Time.Current + 1000,
|
||||
Duration = 4000,
|
||||
};
|
||||
|
||||
var cpi = new ControlPointInfo();
|
||||
cpi.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
|
||||
drumroll.ApplyDefaults(cpi, new BeatmapDifficulty());
|
||||
|
||||
return drumroll;
|
||||
}
|
||||
}
|
||||
}
|
@ -13,7 +13,7 @@ using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Rulesets.Taiko.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Taiko.Skinning;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Tests
|
||||
namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestSceneDrawableHit : TaikoSkinnableTestScene
|
||||
@ -24,6 +24,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
||||
typeof(DrawableCentreHit),
|
||||
typeof(DrawableRimHit),
|
||||
typeof(LegacyHit),
|
||||
typeof(LegacyCirclePiece),
|
||||
}).ToList();
|
||||
|
||||
[BackgroundDependencyLoader]
|
@ -6,14 +6,14 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osuTK;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Taiko.Skinning;
|
||||
using osu.Game.Rulesets.Taiko.UI;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Tests
|
||||
namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestSceneInputDrum : TaikoSkinnableTestScene
|
@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
||||
[Test]
|
||||
public void TestZeroTickTimeOffsets()
|
||||
{
|
||||
AddUntilStep("gameplay finished", () => Player.ScoreProcessor.HasCompleted);
|
||||
AddUntilStep("gameplay finished", () => Player.ScoreProcessor.HasCompleted.Value);
|
||||
AddAssert("all tick offsets are 0", () => Player.Results.Where(r => r.HitObject is SwellTick).All(r => r.TimeOffset == 0));
|
||||
}
|
||||
|
||||
|
@ -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 osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
@ -16,7 +15,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
{
|
||||
}
|
||||
|
||||
protected override CompositeDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.CentreHit),
|
||||
protected override SkinnableDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.CentreHit),
|
||||
_ => new CentreHitCirclePiece(), confineMode: ConfineMode.ScaleToFit);
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,8 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
{
|
||||
@ -29,25 +31,29 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
/// </summary>
|
||||
private int rollingHits;
|
||||
|
||||
private readonly Container<DrawableDrumRollTick> tickContainer;
|
||||
private Container tickContainer;
|
||||
|
||||
private Color4 colourIdle;
|
||||
private Color4 colourEngaged;
|
||||
|
||||
private ElongatedCirclePiece elongatedPiece;
|
||||
|
||||
public DrawableDrumRoll(DrumRoll drumRoll)
|
||||
: base(drumRoll)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y;
|
||||
elongatedPiece.Add(tickContainer = new Container<DrawableDrumRollTick> { RelativeSizeAxes = Axes.Both });
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
elongatedPiece.AccentColour = colourIdle = colours.YellowDark;
|
||||
colourIdle = colours.YellowDark;
|
||||
colourEngaged = colours.YellowDarker;
|
||||
|
||||
updateColour();
|
||||
|
||||
Content.Add(tickContainer = new Container { RelativeSizeAxes = Axes.Both });
|
||||
|
||||
if (MainPiece.Drawable is IHasAccentColour accentMain)
|
||||
accentMain.AccentColour = colourIdle;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@ -86,7 +92,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
return base.CreateNestedHitObject(hitObject);
|
||||
}
|
||||
|
||||
protected override CompositeDrawable CreateMainPiece() => elongatedPiece = new ElongatedCirclePiece();
|
||||
protected override SkinnableDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.DrumRollBody),
|
||||
_ => new ElongatedCirclePiece());
|
||||
|
||||
public override bool OnPressed(TaikoAction action) => false;
|
||||
|
||||
@ -102,8 +109,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
|
||||
rollingHits = Math.Clamp(rollingHits, 0, rolling_hits_for_engaged_colour);
|
||||
|
||||
Color4 newColour = Interpolation.ValueAt((float)rollingHits / rolling_hits_for_engaged_colour, colourIdle, colourEngaged, 0, 1);
|
||||
(MainPiece as IHasAccentColour)?.FadeAccent(newColour, 100);
|
||||
updateColour();
|
||||
}
|
||||
|
||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||
@ -132,8 +138,22 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
OriginPosition = new Vector2(DrawHeight);
|
||||
Content.X = DrawHeight / 2;
|
||||
}
|
||||
|
||||
protected override DrawableStrongNestedHit CreateStrongHit(StrongHitObject hitObject) => new StrongNestedHit(hitObject, this);
|
||||
|
||||
private void updateColour()
|
||||
{
|
||||
Color4 newColour = Interpolation.ValueAt((float)rollingHits / rolling_hits_for_engaged_colour, colourIdle, colourEngaged, 0, 1);
|
||||
(MainPiece.Drawable as IHasAccentColour)?.FadeAccent(newColour, 100);
|
||||
}
|
||||
|
||||
private class StrongNestedHit : DrawableStrongNestedHit
|
||||
{
|
||||
public StrongNestedHit(StrongHitObject strong, DrawableDrumRoll drumRoll)
|
||||
|
@ -3,10 +3,10 @@
|
||||
|
||||
using System;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
{
|
||||
@ -20,10 +20,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
|
||||
public override bool DisplayResult => false;
|
||||
|
||||
protected override CompositeDrawable CreateMainPiece() => new TickPiece
|
||||
{
|
||||
Filled = HitObject.FirstTick
|
||||
};
|
||||
protected override SkinnableDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.DrumRollTick),
|
||||
_ => new TickPiece
|
||||
{
|
||||
Filled = HitObject.FirstTick
|
||||
});
|
||||
|
||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||
{
|
||||
|
@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
// If we're far enough away from the left stage, we should bring outselves in front of it
|
||||
ProxyContent();
|
||||
|
||||
var flash = (MainPiece as CirclePiece)?.FlashBox;
|
||||
var flash = (MainPiece.Drawable as CirclePiece)?.FlashBox;
|
||||
flash?.FadeTo(0.9f).FadeOut(300);
|
||||
|
||||
const float gravity_time = 300;
|
||||
|
@ -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 osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
@ -16,7 +15,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
{
|
||||
}
|
||||
|
||||
protected override CompositeDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.RimHit),
|
||||
protected override SkinnableDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.RimHit),
|
||||
_ => new RimHitCirclePiece(), confineMode: ConfineMode.ScaleToFit);
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
{
|
||||
@ -114,12 +115,13 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
targetRing.BorderColour = colours.YellowDark.Opacity(0.25f);
|
||||
}
|
||||
|
||||
protected override CompositeDrawable CreateMainPiece() => new SwellCirclePiece
|
||||
{
|
||||
// to allow for rotation transform
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
};
|
||||
protected override SkinnableDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.Swell),
|
||||
_ => new SwellCirclePiece
|
||||
{
|
||||
// to allow for rotation transform
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
});
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
@ -184,7 +186,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
.Then()
|
||||
.FadeTo(completion / 8, 2000, Easing.OutQuint);
|
||||
|
||||
MainPiece.RotateTo((float)(completion * HitObject.Duration / 8), 4000, Easing.OutQuint);
|
||||
MainPiece.Drawable.RotateTo((float)(completion * HitObject.Duration / 8), 4000, Easing.OutQuint);
|
||||
|
||||
expandingRing.ScaleTo(1f + Math.Min(target_ring_scale - 1f, (target_ring_scale - 1f) * completion * 1.3f), 260, Easing.OutQuint);
|
||||
|
||||
|
@ -2,9 +2,9 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
{
|
||||
@ -31,6 +31,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
|
||||
public override bool OnPressed(TaikoAction action) => false;
|
||||
|
||||
protected override CompositeDrawable CreateMainPiece() => new TickPiece();
|
||||
protected override SkinnableDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.DrumRollTick),
|
||||
_ => new TickPiece());
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ using System.Collections.Generic;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
{
|
||||
@ -115,7 +116,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
public new TObject HitObject;
|
||||
|
||||
protected readonly Vector2 BaseSize;
|
||||
protected readonly CompositeDrawable MainPiece;
|
||||
protected readonly SkinnableDrawable MainPiece;
|
||||
|
||||
private readonly Container<DrawableStrongNestedHit> strongHitContainer;
|
||||
|
||||
@ -167,7 +168,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
// Normal and clap samples are handled by the drum
|
||||
protected override IEnumerable<HitSampleInfo> GetSamples() => HitObject.Samples.Where(s => s.Name != HitSampleInfo.HIT_NORMAL && s.Name != HitSampleInfo.HIT_CLAP);
|
||||
|
||||
protected abstract CompositeDrawable CreateMainPiece();
|
||||
protected abstract SkinnableDrawable CreateMainPiece();
|
||||
|
||||
/// <summary>
|
||||
/// Creates the handler for this <see cref="DrawableHitObject"/>'s <see cref="StrongHitObject"/>.
|
||||
|
@ -10,6 +10,7 @@ using osuTK.Graphics;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
|
||||
@ -21,7 +22,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
|
||||
/// for a usage example.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public abstract class CirclePiece : BeatSyncedContainer
|
||||
public abstract class CirclePiece : BeatSyncedContainer, IHasAccentColour
|
||||
{
|
||||
public const float SYMBOL_SIZE = 0.45f;
|
||||
public const float SYMBOL_BORDER = 8;
|
||||
|
@ -1,7 +1,9 @@
|
||||
// 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.Game.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
|
||||
{
|
||||
@ -12,18 +14,15 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
|
||||
RelativeSizeAxes = Axes.Y;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
AccentColour = colours.YellowDark;
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
var padding = Content.DrawHeight * Content.Width / 2;
|
||||
|
||||
Content.Padding = new MarginPadding
|
||||
{
|
||||
Left = padding,
|
||||
Right = padding,
|
||||
};
|
||||
|
||||
Width = Parent.DrawSize.X + DrawHeight;
|
||||
}
|
||||
}
|
||||
|
96
osu.Game.Rulesets.Taiko/Skinning/LegacyCirclePiece.cs
Normal file
96
osu.Game.Rulesets.Taiko/Skinning/LegacyCirclePiece.cs
Normal file
@ -0,0 +1,96 @@
|
||||
// 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.Animations;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Taiko.Objects.Drawables;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Skinning
|
||||
{
|
||||
public class LegacyCirclePiece : CompositeDrawable, IHasAccentColour
|
||||
{
|
||||
private Drawable backgroundLayer;
|
||||
|
||||
public LegacyCirclePiece()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ISkinSource skin, DrawableHitObject drawableHitObject)
|
||||
{
|
||||
Drawable getDrawableFor(string lookup)
|
||||
{
|
||||
const string normal_hit = "taikohit";
|
||||
const string big_hit = "taikobig";
|
||||
|
||||
string prefix = ((drawableHitObject as DrawableTaikoHitObject)?.HitObject.IsStrong ?? false) ? big_hit : normal_hit;
|
||||
|
||||
return skin.GetAnimation($"{prefix}{lookup}", true, false) ??
|
||||
// fallback to regular size if "big" version doesn't exist.
|
||||
skin.GetAnimation($"{normal_hit}{lookup}", true, false);
|
||||
}
|
||||
|
||||
// backgroundLayer is guaranteed to exist due to the pre-check in TaikoLegacySkinTransformer.
|
||||
AddInternal(backgroundLayer = getDrawableFor("circle"));
|
||||
|
||||
var foregroundLayer = getDrawableFor("circleoverlay");
|
||||
if (foregroundLayer != null)
|
||||
AddInternal(foregroundLayer);
|
||||
|
||||
// Animations in taiko skins are used in a custom way (>150 combo and animating in time with beat).
|
||||
// For now just stop at first frame for sanity.
|
||||
foreach (var c in InternalChildren)
|
||||
{
|
||||
(c as IFramedAnimation)?.Stop();
|
||||
|
||||
c.Anchor = Anchor.Centre;
|
||||
c.Origin = Anchor.Centre;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
updateAccentColour();
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
// Not all skins (including the default osu-stable) have similar sizes for "hitcircle" and "hitcircleoverlay".
|
||||
// This ensures they are scaled relative to each other but also match the expected DrawableHit size.
|
||||
foreach (var c in InternalChildren)
|
||||
c.Scale = new Vector2(DrawHeight / 128);
|
||||
}
|
||||
|
||||
private Color4 accentColour;
|
||||
|
||||
public Color4 AccentColour
|
||||
{
|
||||
get => accentColour;
|
||||
set
|
||||
{
|
||||
if (value == accentColour)
|
||||
return;
|
||||
|
||||
accentColour = value;
|
||||
if (IsLoaded)
|
||||
updateAccentColour();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateAccentColour()
|
||||
{
|
||||
backgroundLayer.Colour = accentColour;
|
||||
}
|
||||
}
|
||||
}
|
83
osu.Game.Rulesets.Taiko/Skinning/LegacyDrumRoll.cs
Normal file
83
osu.Game.Rulesets.Taiko/Skinning/LegacyDrumRoll.cs
Normal file
@ -0,0 +1,83 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Skinning
|
||||
{
|
||||
public class LegacyDrumRoll : CompositeDrawable, IHasAccentColour
|
||||
{
|
||||
private LegacyCirclePiece headCircle;
|
||||
|
||||
private Sprite body;
|
||||
|
||||
private Sprite end;
|
||||
|
||||
public LegacyDrumRoll()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ISkinSource skin, OsuColour colours)
|
||||
{
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
end = new Sprite
|
||||
{
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreLeft,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Texture = skin.GetTexture("taiko-roll-end"),
|
||||
FillMode = FillMode.Fit,
|
||||
},
|
||||
body = new Sprite
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Texture = skin.GetTexture("taiko-roll-middle"),
|
||||
},
|
||||
headCircle = new LegacyCirclePiece
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
},
|
||||
};
|
||||
|
||||
AccentColour = colours.YellowDark;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
updateAccentColour();
|
||||
}
|
||||
|
||||
private Color4 accentColour;
|
||||
|
||||
public Color4 AccentColour
|
||||
{
|
||||
get => accentColour;
|
||||
set
|
||||
{
|
||||
if (value == accentColour)
|
||||
return;
|
||||
|
||||
accentColour = value;
|
||||
if (IsLoaded)
|
||||
updateAccentColour();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateAccentColour()
|
||||
{
|
||||
headCircle.AccentColour = accentColour;
|
||||
body.Colour = accentColour;
|
||||
end.Colour = accentColour;
|
||||
}
|
||||
}
|
||||
}
|
@ -2,90 +2,25 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Animations;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Taiko.Objects.Drawables;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Skinning
|
||||
{
|
||||
public class LegacyHit : CompositeDrawable, IHasAccentColour
|
||||
public class LegacyHit : LegacyCirclePiece
|
||||
{
|
||||
private readonly TaikoSkinComponents component;
|
||||
|
||||
private Drawable backgroundLayer;
|
||||
|
||||
public LegacyHit(TaikoSkinComponents component)
|
||||
{
|
||||
this.component = component;
|
||||
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ISkinSource skin, DrawableHitObject drawableHitObject)
|
||||
private void load()
|
||||
{
|
||||
Drawable getDrawableFor(string lookup)
|
||||
{
|
||||
const string normal_hit = "taikohit";
|
||||
const string big_hit = "taikobig";
|
||||
|
||||
string prefix = ((drawableHitObject as DrawableTaikoHitObject)?.HitObject.IsStrong ?? false) ? big_hit : normal_hit;
|
||||
|
||||
return skin.GetAnimation($"{prefix}{lookup}", true, false) ??
|
||||
// fallback to regular size if "big" version doesn't exist.
|
||||
skin.GetAnimation($"{normal_hit}{lookup}", true, false);
|
||||
}
|
||||
|
||||
// backgroundLayer is guaranteed to exist due to the pre-check in TaikoLegacySkinTransformer.
|
||||
AddInternal(backgroundLayer = getDrawableFor("circle"));
|
||||
|
||||
var foregroundLayer = getDrawableFor("circleoverlay");
|
||||
if (foregroundLayer != null)
|
||||
AddInternal(foregroundLayer);
|
||||
|
||||
// Animations in taiko skins are used in a custom way (>150 combo and animating in time with beat).
|
||||
// For now just stop at first frame for sanity.
|
||||
foreach (var c in InternalChildren)
|
||||
{
|
||||
(c as IFramedAnimation)?.Stop();
|
||||
|
||||
c.Anchor = Anchor.Centre;
|
||||
c.Origin = Anchor.Centre;
|
||||
}
|
||||
|
||||
AccentColour = component == TaikoSkinComponents.CentreHit
|
||||
? new Color4(235, 69, 44, 255)
|
||||
: new Color4(67, 142, 172, 255);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
// Not all skins (including the default osu-stable) have similar sizes for "hitcircle" and "hitcircleoverlay".
|
||||
// This ensures they are scaled relative to each other but also match the expected DrawableHit size.
|
||||
foreach (var c in InternalChildren)
|
||||
c.Scale = new Vector2(DrawWidth / 128);
|
||||
}
|
||||
|
||||
private Color4 accentColour;
|
||||
|
||||
public Color4 AccentColour
|
||||
{
|
||||
get => accentColour;
|
||||
set
|
||||
{
|
||||
if (value == accentColour)
|
||||
return;
|
||||
|
||||
backgroundLayer.Colour = accentColour = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,12 @@ namespace osu.Game.Rulesets.Taiko.Skinning
|
||||
|
||||
switch (taikoComponent.Component)
|
||||
{
|
||||
case TaikoSkinComponents.DrumRollBody:
|
||||
if (GetTexture("taiko-roll-middle") != null)
|
||||
return new LegacyDrumRoll();
|
||||
|
||||
return null;
|
||||
|
||||
case TaikoSkinComponents.InputDrum:
|
||||
if (GetTexture("taiko-bar-left") != null)
|
||||
return new LegacyInputDrum();
|
||||
@ -40,6 +46,9 @@ namespace osu.Game.Rulesets.Taiko.Skinning
|
||||
return new LegacyHit(taikoComponent.Component);
|
||||
|
||||
return null;
|
||||
|
||||
case TaikoSkinComponents.DrumRollTick:
|
||||
return this.GetAnimation("sliderscorepoint", false, false);
|
||||
}
|
||||
|
||||
return source.GetDrawableComponent(component);
|
||||
|
@ -7,6 +7,9 @@ namespace osu.Game.Rulesets.Taiko
|
||||
{
|
||||
InputDrum,
|
||||
CentreHit,
|
||||
RimHit
|
||||
RimHit,
|
||||
DrumRollBody,
|
||||
DrumRollTick,
|
||||
Swell
|
||||
}
|
||||
}
|
||||
|
@ -241,6 +241,11 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
{
|
||||
var controlPoints = decoder.Decode(stream).ControlPointInfo;
|
||||
|
||||
Assert.That(controlPoints.TimingPoints.Count, Is.EqualTo(4));
|
||||
Assert.That(controlPoints.DifficultyPoints.Count, Is.EqualTo(3));
|
||||
Assert.That(controlPoints.EffectPoints.Count, Is.EqualTo(3));
|
||||
Assert.That(controlPoints.SamplePoints.Count, Is.EqualTo(3));
|
||||
|
||||
Assert.That(controlPoints.DifficultyPointAt(500).SpeedMultiplier, Is.EqualTo(1.5).Within(0.1));
|
||||
Assert.That(controlPoints.DifficultyPointAt(1500).SpeedMultiplier, Is.EqualTo(1.5).Within(0.1));
|
||||
Assert.That(controlPoints.DifficultyPointAt(2500).SpeedMultiplier, Is.EqualTo(0.75).Within(0.1));
|
||||
|
@ -0,0 +1,139 @@
|
||||
// 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.Audio;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Storyboards;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public class TestSceneCompletionCancellation : PlayerTestScene
|
||||
{
|
||||
private Track track;
|
||||
|
||||
[Resolved]
|
||||
private AudioManager audio { get; set; }
|
||||
|
||||
private int resultsDisplayWaitCount =>
|
||||
(int)((Screens.Play.Player.RESULTS_DISPLAY_DELAY / TimePerAction) * 2);
|
||||
|
||||
protected override bool AllowFail => false;
|
||||
|
||||
public TestSceneCompletionCancellation()
|
||||
: base(new OsuRuleset())
|
||||
{
|
||||
}
|
||||
|
||||
[SetUpSteps]
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
// Ensure track has actually running before attempting to seek
|
||||
AddUntilStep("wait for track to start running", () => track.IsRunning);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCancelCompletionOnRewind()
|
||||
{
|
||||
complete();
|
||||
cancel();
|
||||
|
||||
checkNoRanking();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestReCompleteAfterCancellation()
|
||||
{
|
||||
complete();
|
||||
cancel();
|
||||
complete();
|
||||
|
||||
AddUntilStep("attempted to push ranking", () => ((FakeRankingPushPlayer)Player).GotoRankingInvoked);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether can still pause after cancelling completion by reverting <see cref="IScreen.ValidForResume"/> back to true.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestCanPauseAfterCancellation()
|
||||
{
|
||||
complete();
|
||||
cancel();
|
||||
|
||||
AddStep("pause", () => Player.Pause());
|
||||
AddAssert("paused successfully", () => Player.GameplayClockContainer.IsPaused.Value);
|
||||
|
||||
checkNoRanking();
|
||||
}
|
||||
|
||||
private void complete()
|
||||
{
|
||||
AddStep("seek to completion", () => track.Seek(5000));
|
||||
AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value);
|
||||
}
|
||||
|
||||
private void cancel()
|
||||
{
|
||||
AddStep("rewind to cancel", () => track.Seek(4000));
|
||||
AddUntilStep("completion cleared by processor", () => !Player.ScoreProcessor.HasCompleted.Value);
|
||||
}
|
||||
|
||||
private void checkNoRanking()
|
||||
{
|
||||
// wait to ensure there was no attempt of pushing the results screen.
|
||||
AddWaitStep("wait", resultsDisplayWaitCount);
|
||||
AddAssert("no attempt to push ranking", () => !((FakeRankingPushPlayer)Player).GotoRankingInvoked);
|
||||
}
|
||||
|
||||
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
|
||||
{
|
||||
var working = new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audio);
|
||||
track = working.Track;
|
||||
return working;
|
||||
}
|
||||
|
||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
|
||||
{
|
||||
var beatmap = new Beatmap();
|
||||
|
||||
for (int i = 1; i <= 19; i++)
|
||||
{
|
||||
beatmap.HitObjects.Add(new HitCircle
|
||||
{
|
||||
Position = new Vector2(256, 192),
|
||||
StartTime = i * 250,
|
||||
});
|
||||
}
|
||||
|
||||
return beatmap;
|
||||
}
|
||||
|
||||
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new FakeRankingPushPlayer();
|
||||
|
||||
public class FakeRankingPushPlayer : TestPlayer
|
||||
{
|
||||
public bool GotoRankingInvoked;
|
||||
|
||||
public FakeRankingPushPlayer()
|
||||
: base(true, true)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void GotoRanking()
|
||||
{
|
||||
GotoRankingInvoked = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Overlays;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Overlays.BeatmapListing;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
@ -13,6 +14,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||
{
|
||||
typeof(BeatmapListingOverlay),
|
||||
typeof(BeatmapListingFilterControl)
|
||||
};
|
||||
|
||||
protected override bool UseOnlineAPI => true;
|
||||
|
@ -15,25 +15,27 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
public class TestSceneBeatmapListingSearchSection : OsuTestScene
|
||||
public class TestSceneBeatmapListingSearchControl : OsuTestScene
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||
{
|
||||
typeof(BeatmapListingSearchSection),
|
||||
typeof(BeatmapListingSearchControl),
|
||||
};
|
||||
|
||||
[Cached]
|
||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
|
||||
|
||||
private readonly BeatmapListingSearchSection section;
|
||||
private readonly BeatmapListingSearchControl control;
|
||||
|
||||
public TestSceneBeatmapListingSearchSection()
|
||||
public TestSceneBeatmapListingSearchControl()
|
||||
{
|
||||
OsuSpriteText query;
|
||||
OsuSpriteText ruleset;
|
||||
OsuSpriteText category;
|
||||
OsuSpriteText genre;
|
||||
OsuSpriteText language;
|
||||
|
||||
Add(section = new BeatmapListingSearchSection
|
||||
Add(control = new BeatmapListingSearchControl
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
@ -49,20 +51,24 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
query = new OsuSpriteText(),
|
||||
ruleset = new OsuSpriteText(),
|
||||
category = new OsuSpriteText(),
|
||||
genre = new OsuSpriteText(),
|
||||
language = new OsuSpriteText(),
|
||||
}
|
||||
});
|
||||
|
||||
section.Query.BindValueChanged(q => query.Text = $"Query: {q.NewValue}", true);
|
||||
section.Ruleset.BindValueChanged(r => ruleset.Text = $"Ruleset: {r.NewValue}", true);
|
||||
section.Category.BindValueChanged(c => category.Text = $"Category: {c.NewValue}", true);
|
||||
control.Query.BindValueChanged(q => query.Text = $"Query: {q.NewValue}", true);
|
||||
control.Ruleset.BindValueChanged(r => ruleset.Text = $"Ruleset: {r.NewValue}", true);
|
||||
control.Category.BindValueChanged(c => category.Text = $"Category: {c.NewValue}", true);
|
||||
control.Genre.BindValueChanged(g => genre.Text = $"Genre: {g.NewValue}", true);
|
||||
control.Language.BindValueChanged(l => language.Text = $"Language: {l.NewValue}", true);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCovers()
|
||||
{
|
||||
AddStep("Set beatmap", () => section.BeatmapSet = beatmap_set);
|
||||
AddStep("Set beatmap (no cover)", () => section.BeatmapSet = no_cover_beatmap_set);
|
||||
AddStep("Set null beatmap", () => section.BeatmapSet = null);
|
||||
AddStep("Set beatmap", () => control.BeatmapSet = beatmap_set);
|
||||
AddStep("Set beatmap (no cover)", () => control.BeatmapSet = no_cover_beatmap_set);
|
||||
AddStep("Set null beatmap", () => control.BeatmapSet = null);
|
||||
}
|
||||
|
||||
private static readonly BeatmapSetInfo beatmap_set = new BeatmapSetInfo
|
@ -13,18 +13,17 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
public class TestSceneBeatmapListingSort : OsuTestScene
|
||||
public class TestSceneBeatmapListingSortTabControl : OsuTestScene
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||
{
|
||||
typeof(BeatmapListingSortTabControl),
|
||||
typeof(OverlaySortTabControl<>),
|
||||
};
|
||||
|
||||
[Cached]
|
||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
|
||||
|
||||
public TestSceneBeatmapListingSort()
|
||||
public TestSceneBeatmapListingSortTabControl()
|
||||
{
|
||||
BeatmapListingSortTabControl control;
|
||||
OsuSpriteText current;
|
@ -8,7 +8,6 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.BeatmapListing;
|
||||
using osuTK;
|
||||
@ -20,8 +19,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||
{
|
||||
typeof(BeatmapSearchFilterRow<>),
|
||||
typeof(BeatmapSearchRulesetFilterRow),
|
||||
typeof(BeatmapSearchSmallFilterRow<>),
|
||||
typeof(BeatmapSearchRulesetFilterRow)
|
||||
};
|
||||
|
||||
[Cached]
|
||||
@ -42,8 +40,8 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new BeatmapSearchRulesetFilterRow(),
|
||||
new BeatmapSearchFilterRow<BeatmapSearchCategory>("Categories"),
|
||||
new BeatmapSearchSmallFilterRow<BeatmapSearchCategory>("Header Name")
|
||||
new BeatmapSearchFilterRow<SearchCategory>("Categories"),
|
||||
new BeatmapSearchFilterRow<SearchCategory>("Header Name")
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -56,6 +56,7 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
/// <summary>
|
||||
/// All control points, of all types.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public IEnumerable<ControlPoint> AllControlPoints => Groups.SelectMany(g => g.ControlPoints).ToArray();
|
||||
|
||||
/// <summary>
|
||||
|
@ -386,17 +386,10 @@ namespace osu.Game.Beatmaps.Formats
|
||||
SampleVolume = sampleVolume,
|
||||
CustomSampleBank = customSampleBank,
|
||||
}, timingChange);
|
||||
|
||||
// To handle the scenario where a non-timing line shares the same time value as a subsequent timing line but
|
||||
// appears earlier in the file, we buffer non-timing control points and rewrite them *after* control points from the timing line
|
||||
// with the same time value (allowing them to overwrite as necessary).
|
||||
//
|
||||
// The expected outcome is that we prefer the non-timing line's adjustments over the timing line's adjustments when time is equal.
|
||||
if (timingChange)
|
||||
flushPendingPoints();
|
||||
}
|
||||
|
||||
private readonly List<ControlPoint> pendingControlPoints = new List<ControlPoint>();
|
||||
private readonly HashSet<Type> pendingControlPointTypes = new HashSet<Type>();
|
||||
private double pendingControlPointsTime;
|
||||
|
||||
private void addControlPoint(double time, ControlPoint point, bool timingChange)
|
||||
@ -405,21 +398,28 @@ namespace osu.Game.Beatmaps.Formats
|
||||
flushPendingPoints();
|
||||
|
||||
if (timingChange)
|
||||
{
|
||||
beatmap.ControlPointInfo.Add(time, point);
|
||||
return;
|
||||
}
|
||||
pendingControlPoints.Insert(0, point);
|
||||
else
|
||||
pendingControlPoints.Add(point);
|
||||
|
||||
pendingControlPoints.Add(point);
|
||||
pendingControlPointsTime = time;
|
||||
}
|
||||
|
||||
private void flushPendingPoints()
|
||||
{
|
||||
foreach (var p in pendingControlPoints)
|
||||
beatmap.ControlPointInfo.Add(pendingControlPointsTime, p);
|
||||
// Changes from non-timing-points are added to the end of the list (see addControlPoint()) and should override any changes from timing-points (added to the start of the list).
|
||||
for (int i = pendingControlPoints.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var type = pendingControlPoints[i].GetType();
|
||||
if (pendingControlPointTypes.Contains(type))
|
||||
continue;
|
||||
|
||||
pendingControlPointTypes.Add(type);
|
||||
beatmap.ControlPointInfo.Add(pendingControlPointsTime, pendingControlPoints[i]);
|
||||
}
|
||||
|
||||
pendingControlPoints.Clear();
|
||||
pendingControlPointTypes.Clear();
|
||||
}
|
||||
|
||||
private void handleHitObject(string line)
|
||||
|
@ -1,9 +1,9 @@
|
||||
// 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.ComponentModel;
|
||||
using osu.Framework.IO.Network;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.BeatmapListing;
|
||||
using osu.Game.Overlays.Direct;
|
||||
using osu.Game.Rulesets;
|
||||
|
||||
@ -11,20 +11,31 @@ namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class SearchBeatmapSetsRequest : APIRequest<SearchBeatmapSetsResponse>
|
||||
{
|
||||
public SearchCategory SearchCategory { get; set; }
|
||||
|
||||
public DirectSortCriteria SortCriteria { get; set; }
|
||||
|
||||
public SortDirection SortDirection { get; set; }
|
||||
|
||||
public SearchGenre Genre { get; set; }
|
||||
|
||||
public SearchLanguage Language { get; set; }
|
||||
|
||||
private readonly string query;
|
||||
private readonly RulesetInfo ruleset;
|
||||
private readonly BeatmapSearchCategory searchCategory;
|
||||
private readonly DirectSortCriteria sortCriteria;
|
||||
private readonly SortDirection direction;
|
||||
private string directionString => direction == SortDirection.Descending ? @"desc" : @"asc";
|
||||
|
||||
public SearchBeatmapSetsRequest(string query, RulesetInfo ruleset, BeatmapSearchCategory searchCategory = BeatmapSearchCategory.Any, DirectSortCriteria sortCriteria = DirectSortCriteria.Ranked, SortDirection direction = SortDirection.Descending)
|
||||
private string directionString => SortDirection == SortDirection.Descending ? @"desc" : @"asc";
|
||||
|
||||
public SearchBeatmapSetsRequest(string query, RulesetInfo ruleset)
|
||||
{
|
||||
this.query = string.IsNullOrEmpty(query) ? string.Empty : System.Uri.EscapeDataString(query);
|
||||
this.ruleset = ruleset;
|
||||
this.searchCategory = searchCategory;
|
||||
this.sortCriteria = sortCriteria;
|
||||
this.direction = direction;
|
||||
|
||||
SearchCategory = SearchCategory.Any;
|
||||
SortCriteria = DirectSortCriteria.Ranked;
|
||||
SortDirection = SortDirection.Descending;
|
||||
Genre = SearchGenre.Any;
|
||||
Language = SearchLanguage.Any;
|
||||
}
|
||||
|
||||
protected override WebRequest CreateWebRequest()
|
||||
@ -35,31 +46,19 @@ namespace osu.Game.Online.API.Requests
|
||||
if (ruleset.ID.HasValue)
|
||||
req.AddParameter("m", ruleset.ID.Value.ToString());
|
||||
|
||||
req.AddParameter("s", searchCategory.ToString().ToLowerInvariant());
|
||||
req.AddParameter("sort", $"{sortCriteria.ToString().ToLowerInvariant()}_{directionString}");
|
||||
req.AddParameter("s", SearchCategory.ToString().ToLowerInvariant());
|
||||
|
||||
if (Genre != SearchGenre.Any)
|
||||
req.AddParameter("g", ((int)Genre).ToString());
|
||||
|
||||
if (Language != SearchLanguage.Any)
|
||||
req.AddParameter("l", ((int)Language).ToString());
|
||||
|
||||
req.AddParameter("sort", $"{SortCriteria.ToString().ToLowerInvariant()}_{directionString}");
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
protected override string Target => @"beatmapsets/search";
|
||||
}
|
||||
|
||||
public enum BeatmapSearchCategory
|
||||
{
|
||||
Any,
|
||||
|
||||
[Description("Has Leaderboard")]
|
||||
Leaderboard,
|
||||
Ranked,
|
||||
Qualified,
|
||||
Loved,
|
||||
Favourites,
|
||||
|
||||
[Description("Pending & WIP")]
|
||||
Pending,
|
||||
Graveyard,
|
||||
|
||||
[Description("My Maps")]
|
||||
Mine,
|
||||
}
|
||||
}
|
||||
|
163
osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs
Normal file
163
osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs
Normal file
@ -0,0 +1,163 @@
|
||||
// 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;
|
||||
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.Framework.Threading;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Overlays.Direct;
|
||||
using osu.Game.Rulesets;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Overlays.BeatmapListing
|
||||
{
|
||||
public class BeatmapListingFilterControl : CompositeDrawable
|
||||
{
|
||||
public Action<List<BeatmapSetInfo>> SearchFinished;
|
||||
public Action SearchStarted;
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private RulesetStore rulesets { get; set; }
|
||||
|
||||
private readonly BeatmapListingSearchControl searchControl;
|
||||
private readonly BeatmapListingSortTabControl sortControl;
|
||||
private readonly Box sortControlBackground;
|
||||
|
||||
private SearchBeatmapSetsRequest getSetsRequest;
|
||||
|
||||
public BeatmapListingFilterControl()
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
InternalChild = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0, 10),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Masking = true,
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Colour = Color4.Black.Opacity(0.25f),
|
||||
Type = EdgeEffectType.Shadow,
|
||||
Radius = 3,
|
||||
Offset = new Vector2(0f, 1f),
|
||||
},
|
||||
Child = searchControl = new BeatmapListingSearchControl(),
|
||||
},
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 40,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
sortControlBackground = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
sortControl = new BeatmapListingSortTabControl
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Margin = new MarginPadding { Left = 20 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
{
|
||||
sortControlBackground.Colour = colourProvider.Background5;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
var sortCriteria = sortControl.Current;
|
||||
var sortDirection = sortControl.SortDirection;
|
||||
|
||||
searchControl.Query.BindValueChanged(query =>
|
||||
{
|
||||
sortCriteria.Value = string.IsNullOrEmpty(query.NewValue) ? DirectSortCriteria.Ranked : DirectSortCriteria.Relevance;
|
||||
sortDirection.Value = SortDirection.Descending;
|
||||
queueUpdateSearch(true);
|
||||
});
|
||||
|
||||
searchControl.Ruleset.BindValueChanged(_ => queueUpdateSearch());
|
||||
searchControl.Category.BindValueChanged(_ => queueUpdateSearch());
|
||||
searchControl.Genre.BindValueChanged(_ => queueUpdateSearch());
|
||||
searchControl.Language.BindValueChanged(_ => queueUpdateSearch());
|
||||
|
||||
sortCriteria.BindValueChanged(_ => queueUpdateSearch());
|
||||
sortDirection.BindValueChanged(_ => queueUpdateSearch());
|
||||
}
|
||||
|
||||
private ScheduledDelegate queryChangedDebounce;
|
||||
|
||||
private void queueUpdateSearch(bool queryTextChanged = false)
|
||||
{
|
||||
SearchStarted?.Invoke();
|
||||
|
||||
getSetsRequest?.Cancel();
|
||||
|
||||
queryChangedDebounce?.Cancel();
|
||||
queryChangedDebounce = Scheduler.AddDelayed(updateSearch, queryTextChanged ? 500 : 100);
|
||||
}
|
||||
|
||||
private void updateSearch()
|
||||
{
|
||||
getSetsRequest = new SearchBeatmapSetsRequest(searchControl.Query.Value, searchControl.Ruleset.Value)
|
||||
{
|
||||
SearchCategory = searchControl.Category.Value,
|
||||
SortCriteria = sortControl.Current.Value,
|
||||
SortDirection = sortControl.SortDirection.Value,
|
||||
Genre = searchControl.Genre.Value,
|
||||
Language = searchControl.Language.Value
|
||||
};
|
||||
|
||||
getSetsRequest.Success += response => Schedule(() => onSearchFinished(response));
|
||||
|
||||
api.Queue(getSetsRequest);
|
||||
}
|
||||
|
||||
private void onSearchFinished(SearchBeatmapSetsResponse response)
|
||||
{
|
||||
var beatmaps = response.BeatmapSets.Select(r => r.ToBeatmapSet(rulesets)).ToList();
|
||||
|
||||
searchControl.BeatmapSet = response.Total == 0 ? null : beatmaps.First();
|
||||
|
||||
SearchFinished?.Invoke(beatmaps);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
getSetsRequest?.Cancel();
|
||||
queryChangedDebounce?.Cancel();
|
||||
|
||||
base.Dispose(isDisposing);
|
||||
}
|
||||
}
|
||||
}
|
@ -5,8 +5,6 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Rulesets;
|
||||
using osuTK;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
@ -14,16 +12,21 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osuTK.Graphics;
|
||||
using osu.Game.Rulesets;
|
||||
|
||||
namespace osu.Game.Overlays.BeatmapListing
|
||||
{
|
||||
public class BeatmapListingSearchSection : CompositeDrawable
|
||||
public class BeatmapListingSearchControl : CompositeDrawable
|
||||
{
|
||||
public Bindable<string> Query => textBox.Current;
|
||||
|
||||
public Bindable<RulesetInfo> Ruleset => modeFilter.Current;
|
||||
|
||||
public Bindable<BeatmapSearchCategory> Category => categoryFilter.Current;
|
||||
public Bindable<SearchCategory> Category => categoryFilter.Current;
|
||||
|
||||
public Bindable<SearchGenre> Genre => genreFilter.Current;
|
||||
|
||||
public Bindable<SearchLanguage> Language => languageFilter.Current;
|
||||
|
||||
public BeatmapSetInfo BeatmapSet
|
||||
{
|
||||
@ -42,12 +45,14 @@ namespace osu.Game.Overlays.BeatmapListing
|
||||
|
||||
private readonly BeatmapSearchTextBox textBox;
|
||||
private readonly BeatmapSearchRulesetFilterRow modeFilter;
|
||||
private readonly BeatmapSearchFilterRow<BeatmapSearchCategory> categoryFilter;
|
||||
private readonly BeatmapSearchFilterRow<SearchCategory> categoryFilter;
|
||||
private readonly BeatmapSearchFilterRow<SearchGenre> genreFilter;
|
||||
private readonly BeatmapSearchFilterRow<SearchLanguage> languageFilter;
|
||||
|
||||
private readonly Box background;
|
||||
private readonly UpdateableBeatmapSetCover beatmapCover;
|
||||
|
||||
public BeatmapListingSearchSection()
|
||||
public BeatmapListingSearchControl()
|
||||
{
|
||||
AutoSizeAxes = Axes.Y;
|
||||
RelativeSizeAxes = Axes.X;
|
||||
@ -97,7 +102,9 @@ namespace osu.Game.Overlays.BeatmapListing
|
||||
Children = new Drawable[]
|
||||
{
|
||||
modeFilter = new BeatmapSearchRulesetFilterRow(),
|
||||
categoryFilter = new BeatmapSearchFilterRow<BeatmapSearchCategory>(@"Categories"),
|
||||
categoryFilter = new BeatmapSearchFilterRow<SearchCategory>(@"Categories"),
|
||||
genreFilter = new BeatmapSearchFilterRow<SearchGenre>(@"Genre"),
|
||||
languageFilter = new BeatmapSearchFilterRow<SearchLanguage>(@"Language"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -105,7 +112,7 @@ namespace osu.Game.Overlays.BeatmapListing
|
||||
}
|
||||
});
|
||||
|
||||
Category.Value = BeatmapSearchCategory.Leaderboard;
|
||||
categoryFilter.Current.Value = SearchCategory.Leaderboard;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
@ -15,6 +15,8 @@ using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using Humanizer;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Overlays.BeatmapListing
|
||||
{
|
||||
@ -53,8 +55,8 @@ namespace osu.Game.Overlays.BeatmapListing
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Font = OsuFont.GetFont(size: 10),
|
||||
Text = headerName.ToUpper()
|
||||
Font = OsuFont.GetFont(size: 13),
|
||||
Text = headerName.Titleize()
|
||||
},
|
||||
CreateFilter().With(f =>
|
||||
{
|
||||
@ -81,7 +83,7 @@ namespace osu.Game.Overlays.BeatmapListing
|
||||
|
||||
if (typeof(T).IsEnum)
|
||||
{
|
||||
foreach (var val in (T[])Enum.GetValues(typeof(T)))
|
||||
foreach (var val in OrderAttributeUtils.GetValuesInOrder<T>())
|
||||
AddItem(val);
|
||||
}
|
||||
}
|
||||
|
@ -1,32 +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.Graphics.UserInterface;
|
||||
|
||||
namespace osu.Game.Overlays.BeatmapListing
|
||||
{
|
||||
public class BeatmapSearchSmallFilterRow<T> : BeatmapSearchFilterRow<T>
|
||||
{
|
||||
public BeatmapSearchSmallFilterRow(string headerName)
|
||||
: base(headerName)
|
||||
{
|
||||
}
|
||||
|
||||
protected override BeatmapSearchFilter CreateFilter() => new SmallBeatmapSearchFilter();
|
||||
|
||||
private class SmallBeatmapSearchFilter : BeatmapSearchFilter
|
||||
{
|
||||
protected override TabItem<T> CreateTabItem(T value) => new SmallTabItem(value);
|
||||
|
||||
private class SmallTabItem : FilterTabItem
|
||||
{
|
||||
public SmallTabItem(T value)
|
||||
: base(value)
|
||||
{
|
||||
}
|
||||
|
||||
protected override float TextSize => 10;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
26
osu.Game/Overlays/BeatmapListing/SearchCategory.cs
Normal file
26
osu.Game/Overlays/BeatmapListing/SearchCategory.cs
Normal file
@ -0,0 +1,26 @@
|
||||
// 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.ComponentModel;
|
||||
|
||||
namespace osu.Game.Overlays.BeatmapListing
|
||||
{
|
||||
public enum SearchCategory
|
||||
{
|
||||
Any,
|
||||
|
||||
[Description("Has Leaderboard")]
|
||||
Leaderboard,
|
||||
Ranked,
|
||||
Qualified,
|
||||
Loved,
|
||||
Favourites,
|
||||
|
||||
[Description("Pending & WIP")]
|
||||
Pending,
|
||||
Graveyard,
|
||||
|
||||
[Description("My Maps")]
|
||||
Mine,
|
||||
}
|
||||
}
|
25
osu.Game/Overlays/BeatmapListing/SearchGenre.cs
Normal file
25
osu.Game/Overlays/BeatmapListing/SearchGenre.cs
Normal file
@ -0,0 +1,25 @@
|
||||
// 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.ComponentModel;
|
||||
|
||||
namespace osu.Game.Overlays.BeatmapListing
|
||||
{
|
||||
public enum SearchGenre
|
||||
{
|
||||
Any = 0,
|
||||
Unspecified = 1,
|
||||
|
||||
[Description("Video Game")]
|
||||
VideoGame = 2,
|
||||
Anime = 3,
|
||||
Rock = 4,
|
||||
Pop = 5,
|
||||
Other = 6,
|
||||
Novelty = 7,
|
||||
|
||||
[Description("Hip Hop")]
|
||||
HipHop = 9,
|
||||
Electronic = 10
|
||||
}
|
||||
}
|
47
osu.Game/Overlays/BeatmapListing/SearchLanguage.cs
Normal file
47
osu.Game/Overlays/BeatmapListing/SearchLanguage.cs
Normal file
@ -0,0 +1,47 @@
|
||||
// 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.Utils;
|
||||
|
||||
namespace osu.Game.Overlays.BeatmapListing
|
||||
{
|
||||
[HasOrderedElements]
|
||||
public enum SearchLanguage
|
||||
{
|
||||
[Order(0)]
|
||||
Any,
|
||||
|
||||
[Order(11)]
|
||||
Other,
|
||||
|
||||
[Order(1)]
|
||||
English,
|
||||
|
||||
[Order(6)]
|
||||
Japanese,
|
||||
|
||||
[Order(2)]
|
||||
Chinese,
|
||||
|
||||
[Order(10)]
|
||||
Instrumental,
|
||||
|
||||
[Order(7)]
|
||||
Korean,
|
||||
|
||||
[Order(3)]
|
||||
French,
|
||||
|
||||
[Order(4)]
|
||||
German,
|
||||
|
||||
[Order(9)]
|
||||
Swedish,
|
||||
|
||||
[Order(8)]
|
||||
Spanish,
|
||||
|
||||
[Order(5)]
|
||||
Italian
|
||||
}
|
||||
}
|
@ -1,27 +1,23 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using osu.Framework.Allocation;
|
||||
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.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Overlays.BeatmapListing;
|
||||
using osu.Game.Overlays.Direct;
|
||||
using osu.Game.Rulesets;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Overlays
|
||||
{
|
||||
@ -30,14 +26,9 @@ namespace osu.Game.Overlays
|
||||
[Resolved]
|
||||
private PreviewTrackManager previewTrackManager { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private RulesetStore rulesets { get; set; }
|
||||
|
||||
private SearchBeatmapSetsRequest getSetsRequest;
|
||||
|
||||
private Drawable currentContent;
|
||||
private BeatmapListingSearchSection searchSection;
|
||||
private BeatmapListingSortTabControl sortControl;
|
||||
private LoadingLayer loadingLayer;
|
||||
private Container panelTarget;
|
||||
|
||||
public BeatmapListingOverlay()
|
||||
: base(OverlayColourScheme.Blue)
|
||||
@ -63,27 +54,13 @@ namespace osu.Game.Overlays
|
||||
AutoSizeAxes = Axes.Y,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0, 10),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new FillFlowContainer
|
||||
new BeatmapListingHeader(),
|
||||
new BeatmapListingFilterControl
|
||||
{
|
||||
AutoSizeAxes = Axes.Y,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Direction = FillDirection.Vertical,
|
||||
Masking = true,
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Colour = Color4.Black.Opacity(0.25f),
|
||||
Type = EdgeEffectType.Shadow,
|
||||
Radius = 3,
|
||||
Offset = new Vector2(0f, 1f),
|
||||
},
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new BeatmapListingHeader(),
|
||||
searchSection = new BeatmapListingSearchSection(),
|
||||
}
|
||||
SearchStarted = onSearchStarted,
|
||||
SearchFinished = onSearchFinished,
|
||||
},
|
||||
new Container
|
||||
{
|
||||
@ -96,128 +73,41 @@ namespace osu.Game.Overlays
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = ColourProvider.Background4,
|
||||
},
|
||||
new FillFlowContainer
|
||||
panelTarget = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 40,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = ColourProvider.Background5
|
||||
},
|
||||
sortControl = new BeatmapListingSortTabControl
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Margin = new MarginPadding { Left = 20 }
|
||||
}
|
||||
}
|
||||
},
|
||||
new Container
|
||||
{
|
||||
AutoSizeAxes = Axes.Y,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Padding = new MarginPadding { Horizontal = 20 },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
panelTarget = new Container
|
||||
{
|
||||
AutoSizeAxes = Axes.Y,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
},
|
||||
loadingLayer = new LoadingLayer(panelTarget),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Padding = new MarginPadding { Horizontal = 20 }
|
||||
},
|
||||
loadingLayer = new LoadingLayer(panelTarget)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
private CancellationTokenSource cancellationToken;
|
||||
|
||||
private void onSearchStarted()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
var sortCriteria = sortControl.Current;
|
||||
var sortDirection = sortControl.SortDirection;
|
||||
|
||||
searchSection.Query.BindValueChanged(query =>
|
||||
{
|
||||
sortCriteria.Value = string.IsNullOrEmpty(query.NewValue) ? DirectSortCriteria.Ranked : DirectSortCriteria.Relevance;
|
||||
sortDirection.Value = SortDirection.Descending;
|
||||
|
||||
queueUpdateSearch(true);
|
||||
});
|
||||
|
||||
searchSection.Ruleset.BindValueChanged(_ => queueUpdateSearch());
|
||||
searchSection.Category.BindValueChanged(_ => queueUpdateSearch());
|
||||
sortCriteria.BindValueChanged(_ => queueUpdateSearch());
|
||||
sortDirection.BindValueChanged(_ => queueUpdateSearch());
|
||||
}
|
||||
|
||||
private ScheduledDelegate queryChangedDebounce;
|
||||
|
||||
private LoadingLayer loadingLayer;
|
||||
private Container panelTarget;
|
||||
|
||||
private void queueUpdateSearch(bool queryTextChanged = false)
|
||||
{
|
||||
getSetsRequest?.Cancel();
|
||||
|
||||
queryChangedDebounce?.Cancel();
|
||||
queryChangedDebounce = Scheduler.AddDelayed(updateSearch, queryTextChanged ? 500 : 100);
|
||||
}
|
||||
|
||||
private void updateSearch()
|
||||
{
|
||||
if (!IsLoaded)
|
||||
return;
|
||||
|
||||
if (State.Value == Visibility.Hidden)
|
||||
return;
|
||||
|
||||
if (API == null)
|
||||
return;
|
||||
cancellationToken?.Cancel();
|
||||
|
||||
previewTrackManager.StopAnyPlaying(this);
|
||||
|
||||
loadingLayer.Show();
|
||||
|
||||
getSetsRequest = new SearchBeatmapSetsRequest(
|
||||
searchSection.Query.Value,
|
||||
searchSection.Ruleset.Value,
|
||||
searchSection.Category.Value,
|
||||
sortControl.Current.Value,
|
||||
sortControl.SortDirection.Value);
|
||||
|
||||
getSetsRequest.Success += response => Schedule(() => recreatePanels(response));
|
||||
|
||||
API.Queue(getSetsRequest);
|
||||
if (panelTarget.Any())
|
||||
loadingLayer.Show();
|
||||
}
|
||||
|
||||
private void recreatePanels(SearchBeatmapSetsResponse response)
|
||||
private void onSearchFinished(List<BeatmapSetInfo> beatmaps)
|
||||
{
|
||||
if (response.Total == 0)
|
||||
if (!beatmaps.Any())
|
||||
{
|
||||
searchSection.BeatmapSet = null;
|
||||
LoadComponentAsync(new NotFoundDrawable(), addContentToPlaceholder);
|
||||
LoadComponentAsync(new NotFoundDrawable(), addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token);
|
||||
return;
|
||||
}
|
||||
|
||||
var beatmaps = response.BeatmapSets.Select(r => r.ToBeatmapSet(rulesets)).ToList();
|
||||
|
||||
var newPanels = new FillFlowContainer<DirectPanel>
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
@ -232,18 +122,14 @@ namespace osu.Game.Overlays
|
||||
})
|
||||
};
|
||||
|
||||
LoadComponentAsync(newPanels, loaded =>
|
||||
{
|
||||
addContentToPlaceholder(loaded);
|
||||
searchSection.BeatmapSet = beatmaps.First();
|
||||
});
|
||||
LoadComponentAsync(newPanels, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token);
|
||||
}
|
||||
|
||||
private void addContentToPlaceholder(Drawable content)
|
||||
{
|
||||
loadingLayer.Hide();
|
||||
|
||||
Drawable lastContent = currentContent;
|
||||
var lastContent = currentContent;
|
||||
|
||||
if (lastContent != null)
|
||||
{
|
||||
@ -262,9 +148,7 @@ namespace osu.Game.Overlays
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
getSetsRequest?.Cancel();
|
||||
queryChangedDebounce?.Cancel();
|
||||
|
||||
cancellationToken?.Cancel();
|
||||
base.Dispose(isDisposing);
|
||||
}
|
||||
|
||||
|
@ -6,20 +6,20 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Overlays.BeatmapListing;
|
||||
using osu.Game.Overlays.SearchableList;
|
||||
using osu.Game.Rulesets;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Overlays.Direct
|
||||
{
|
||||
public class FilterControl : SearchableListFilterControl<DirectSortCriteria, BeatmapSearchCategory>
|
||||
public class FilterControl : SearchableListFilterControl<DirectSortCriteria, SearchCategory>
|
||||
{
|
||||
private DirectRulesetSelector rulesetSelector;
|
||||
|
||||
protected override Color4 BackgroundColour => Color4Extensions.FromHex(@"384552");
|
||||
protected override DirectSortCriteria DefaultTab => DirectSortCriteria.Ranked;
|
||||
protected override BeatmapSearchCategory DefaultCategory => BeatmapSearchCategory.Leaderboard;
|
||||
protected override SearchCategory DefaultCategory => SearchCategory.Leaderboard;
|
||||
|
||||
protected override Drawable CreateSupplementaryControls() => rulesetSelector = new DirectRulesetSelector();
|
||||
|
||||
|
@ -16,6 +16,7 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Overlays.BeatmapListing;
|
||||
using osu.Game.Overlays.Direct;
|
||||
using osu.Game.Overlays.SearchableList;
|
||||
using osu.Game.Rulesets;
|
||||
@ -24,7 +25,7 @@ using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Overlays
|
||||
{
|
||||
public class DirectOverlay : SearchableListOverlay<DirectTab, DirectSortCriteria, BeatmapSearchCategory>
|
||||
public class DirectOverlay : SearchableListOverlay<DirectTab, DirectSortCriteria, SearchCategory>
|
||||
{
|
||||
private const float panel_padding = 10f;
|
||||
|
||||
@ -40,7 +41,7 @@ namespace osu.Game.Overlays
|
||||
protected override Color4 TrianglesColourDark => Color4Extensions.FromHex(@"3f5265");
|
||||
|
||||
protected override SearchableListHeader<DirectTab> CreateHeader() => new Header();
|
||||
protected override SearchableListFilterControl<DirectSortCriteria, BeatmapSearchCategory> CreateFilterControl() => new FilterControl();
|
||||
protected override SearchableListFilterControl<DirectSortCriteria, SearchCategory> CreateFilterControl() => new FilterControl();
|
||||
|
||||
private IEnumerable<BeatmapSetInfo> beatmapSets;
|
||||
|
||||
@ -255,11 +256,11 @@ namespace osu.Game.Overlays
|
||||
|
||||
previewTrackManager.StopAnyPlaying(this);
|
||||
|
||||
getSetsRequest = new SearchBeatmapSetsRequest(
|
||||
currentQuery.Value,
|
||||
((FilterControl)Filter).Ruleset.Value,
|
||||
Filter.DisplayStyleControl.Dropdown.Current.Value,
|
||||
Filter.Tabs.Current.Value); //todo: sort direction (?)
|
||||
getSetsRequest = new SearchBeatmapSetsRequest(currentQuery.Value, ((FilterControl)Filter).Ruleset.Value)
|
||||
{
|
||||
SearchCategory = Filter.DisplayStyleControl.Dropdown.Current.Value,
|
||||
SortCriteria = Filter.Tabs.Current.Value
|
||||
};
|
||||
|
||||
getSetsRequest.Success += response =>
|
||||
{
|
||||
|
@ -398,7 +398,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
}
|
||||
}
|
||||
|
||||
public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds) => AllJudged && base.UpdateSubTreeMasking(source, maskingBounds);
|
||||
public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds) => false;
|
||||
|
||||
protected override void UpdateAfterChildren()
|
||||
{
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.TypeExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
@ -12,11 +13,6 @@ namespace osu.Game.Rulesets.Scoring
|
||||
{
|
||||
public abstract class JudgementProcessor : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Invoked when all <see cref="HitObject"/>s have been judged by this <see cref="JudgementProcessor"/>.
|
||||
/// </summary>
|
||||
public event Action AllJudged;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when a new judgement has occurred. This occurs after the judgement has been processed by this <see cref="JudgementProcessor"/>.
|
||||
/// </summary>
|
||||
@ -32,10 +28,12 @@ namespace osu.Game.Rulesets.Scoring
|
||||
/// </summary>
|
||||
public int JudgedHits { get; private set; }
|
||||
|
||||
private readonly BindableBool hasCompleted = new BindableBool();
|
||||
|
||||
/// <summary>
|
||||
/// Whether all <see cref="Judgement"/>s have been processed.
|
||||
/// </summary>
|
||||
public bool HasCompleted => JudgedHits == MaxHits;
|
||||
public IBindable<bool> HasCompleted => hasCompleted;
|
||||
|
||||
/// <summary>
|
||||
/// Applies a <see cref="IBeatmap"/> to this <see cref="ScoreProcessor"/>.
|
||||
@ -60,8 +58,7 @@ namespace osu.Game.Rulesets.Scoring
|
||||
|
||||
NewJudgement?.Invoke(result);
|
||||
|
||||
if (HasCompleted)
|
||||
AllJudged?.Invoke();
|
||||
updateHasCompleted();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -72,6 +69,8 @@ namespace osu.Game.Rulesets.Scoring
|
||||
{
|
||||
JudgedHits--;
|
||||
|
||||
updateHasCompleted();
|
||||
|
||||
RevertResultInternal(result);
|
||||
}
|
||||
|
||||
@ -134,5 +133,7 @@ namespace osu.Game.Rulesets.Scoring
|
||||
ApplyResult(result);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateHasCompleted() => hasCompleted.Value = JudgedHits == MaxHits;
|
||||
}
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
isBreakTime.Value = getCurrentBreak()?.HasEffect == true
|
||||
|| Clock.CurrentTime < gameplayStartTime
|
||||
|| scoreProcessor?.HasCompleted == true;
|
||||
|| scoreProcessor?.HasCompleted.Value == true;
|
||||
}
|
||||
|
||||
private BreakPeriod getCurrentBreak()
|
||||
|
@ -37,6 +37,11 @@ namespace osu.Game.Screens.Play
|
||||
[Cached]
|
||||
public class Player : ScreenWithBeatmapBackground
|
||||
{
|
||||
/// <summary>
|
||||
/// The delay upon completion of the beatmap before displaying the results screen.
|
||||
/// </summary>
|
||||
public const double RESULTS_DISPLAY_DELAY = 1000.0;
|
||||
|
||||
public override bool AllowBackButton => false; // handled by HoldForMenuButton
|
||||
|
||||
protected override UserActivity InitialActivity => new UserActivity.SoloGame(Beatmap.Value.BeatmapInfo, Ruleset.Value);
|
||||
@ -197,7 +202,7 @@ namespace osu.Game.Screens.Play
|
||||
};
|
||||
|
||||
// Bind the judgement processors to ourselves
|
||||
ScoreProcessor.AllJudged += onCompletion;
|
||||
ScoreProcessor.HasCompleted.ValueChanged += updateCompletionState;
|
||||
HealthProcessor.Failed += onFail;
|
||||
|
||||
foreach (var mod in Mods.Value.OfType<IApplicableToScoreProcessor>())
|
||||
@ -412,22 +417,33 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
private ScheduledDelegate completionProgressDelegate;
|
||||
|
||||
private void onCompletion()
|
||||
private void updateCompletionState(ValueChangedEvent<bool> completionState)
|
||||
{
|
||||
// screen may be in the exiting transition phase.
|
||||
if (!this.IsCurrentScreen())
|
||||
return;
|
||||
|
||||
if (!completionState.NewValue)
|
||||
{
|
||||
completionProgressDelegate?.Cancel();
|
||||
completionProgressDelegate = null;
|
||||
ValidForResume = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (completionProgressDelegate != null)
|
||||
throw new InvalidOperationException($"{nameof(updateCompletionState)} was fired more than once");
|
||||
|
||||
// Only show the completion screen if the player hasn't failed
|
||||
if (HealthProcessor.HasFailed || completionProgressDelegate != null)
|
||||
if (HealthProcessor.HasFailed)
|
||||
return;
|
||||
|
||||
ValidForResume = false;
|
||||
|
||||
if (!showResults) return;
|
||||
|
||||
using (BeginDelayedSequence(1000))
|
||||
scheduleGotoRanking();
|
||||
using (BeginDelayedSequence(RESULTS_DISPLAY_DELAY))
|
||||
completionProgressDelegate = Schedule(GotoRanking);
|
||||
}
|
||||
|
||||
protected virtual ScoreInfo CreateScore()
|
||||
@ -679,12 +695,6 @@ namespace osu.Game.Screens.Play
|
||||
storyboardReplacesBackground.Value = false;
|
||||
}
|
||||
|
||||
private void scheduleGotoRanking()
|
||||
{
|
||||
completionProgressDelegate?.Cancel();
|
||||
completionProgressDelegate = Schedule(GotoRanking);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
@ -28,8 +28,15 @@ namespace osu.Game.Screens.Select
|
||||
{
|
||||
public class BeatmapCarousel : CompositeDrawable, IKeyBindingHandler<GlobalAction>
|
||||
{
|
||||
private const float bleed_top = FilterControl.HEIGHT;
|
||||
private const float bleed_bottom = Footer.HEIGHT;
|
||||
/// <summary>
|
||||
/// Height of the area above the carousel that should be treated as visible due to transparency of elements in front of it.
|
||||
/// </summary>
|
||||
public float BleedTop { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Height of the area below the carousel that should be treated as visible due to transparency of elements in front of it.
|
||||
/// </summary>
|
||||
public float BleedBottom { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Triggered when the <see cref="BeatmapSets"/> loaded change and are completely loaded.
|
||||
@ -217,6 +224,9 @@ namespace osu.Game.Screens.Select
|
||||
/// <returns>True if a selection was made, False if it wasn't.</returns>
|
||||
public bool SelectBeatmap(BeatmapInfo beatmap, bool bypassFilters = true)
|
||||
{
|
||||
// ensure that any pending events from BeatmapManager have been run before attempting a selection.
|
||||
Scheduler.Update();
|
||||
|
||||
if (beatmap?.Hidden != false)
|
||||
return false;
|
||||
|
||||
@ -373,17 +383,17 @@ namespace osu.Game.Screens.Select
|
||||
/// the beatmap carousel bleeds into the <see cref="FilterControl"/> and the <see cref="Footer"/>
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
private float visibleHalfHeight => (DrawHeight + bleed_bottom + bleed_top) / 2;
|
||||
private float visibleHalfHeight => (DrawHeight + BleedBottom + BleedTop) / 2;
|
||||
|
||||
/// <summary>
|
||||
/// The position of the lower visible bound with respect to the current scroll position.
|
||||
/// </summary>
|
||||
private float visibleBottomBound => scroll.Current + DrawHeight + bleed_bottom;
|
||||
private float visibleBottomBound => scroll.Current + DrawHeight + BleedBottom;
|
||||
|
||||
/// <summary>
|
||||
/// The position of the upper visible bound with respect to the current scroll position.
|
||||
/// </summary>
|
||||
private float visibleUpperBound => scroll.Current - bleed_top;
|
||||
private float visibleUpperBound => scroll.Current - BleedTop;
|
||||
|
||||
public void FlushPendingFilterOperations()
|
||||
{
|
||||
@ -641,7 +651,11 @@ namespace osu.Game.Screens.Select
|
||||
case DrawableCarouselBeatmap beatmap:
|
||||
{
|
||||
if (beatmap.Item.State.Value == CarouselItemState.Selected)
|
||||
scrollTarget = currentY + beatmap.DrawHeight / 2 - DrawHeight / 2;
|
||||
// scroll position at currentY makes the set panel appear at the very top of the carousel's screen space
|
||||
// move down by half of visible height (height of the carousel's visible extent, including semi-transparent areas)
|
||||
// then reapply the top semi-transparent area (because carousel's screen space starts below it)
|
||||
// and finally add half of the panel's own height to achieve vertical centering of the panel itself
|
||||
scrollTarget = currentY - visibleHalfHeight + BleedTop + beatmap.DrawHeight / 2;
|
||||
|
||||
void performMove(float y, float? startY = null)
|
||||
{
|
||||
|
@ -153,6 +153,8 @@ namespace osu.Game.Screens.Select
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
BleedTop = FilterControl.HEIGHT,
|
||||
BleedBottom = Footer.HEIGHT,
|
||||
SelectionChanged = updateSelectedBeatmap,
|
||||
BeatmapSetsChanged = carouselBeatmapsLoaded,
|
||||
GetRecommendedBeatmap = recommender.GetRecommendedBeatmap,
|
||||
|
@ -74,7 +74,7 @@ namespace osu.Game.Skinning
|
||||
switch (pair.Key)
|
||||
{
|
||||
case "ColumnLineWidth":
|
||||
parseArrayValue(pair.Value, currentConfig.ColumnLineWidth);
|
||||
parseArrayValue(pair.Value, currentConfig.ColumnLineWidth, false);
|
||||
break;
|
||||
|
||||
case "ColumnSpacing":
|
||||
@ -124,7 +124,7 @@ namespace osu.Game.Skinning
|
||||
pendingLines.Clear();
|
||||
}
|
||||
|
||||
private void parseArrayValue(string value, float[] output)
|
||||
private void parseArrayValue(string value, float[] output, bool applyScaleFactor = true)
|
||||
{
|
||||
string[] values = value.Split(',');
|
||||
|
||||
@ -133,7 +133,7 @@ namespace osu.Game.Skinning
|
||||
if (i >= output.Length)
|
||||
break;
|
||||
|
||||
output[i] = float.Parse(values[i], CultureInfo.InvariantCulture) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR;
|
||||
output[i] = float.Parse(values[i], CultureInfo.InvariantCulture) * (applyScaleFactor ? LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR : 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -249,6 +249,14 @@ namespace osu.Game.Skinning
|
||||
|
||||
case LegacyManiaSkinConfigurationLookups.RightStageImage:
|
||||
return SkinUtils.As<TValue>(getManiaImage(existing, "StageRight"));
|
||||
|
||||
case LegacyManiaSkinConfigurationLookups.LeftLineWidth:
|
||||
Debug.Assert(maniaLookup.TargetColumn != null);
|
||||
return SkinUtils.As<TValue>(new Bindable<float>(existing.ColumnLineWidth[maniaLookup.TargetColumn.Value]));
|
||||
|
||||
case LegacyManiaSkinConfigurationLookups.RightLineWidth:
|
||||
Debug.Assert(maniaLookup.TargetColumn != null);
|
||||
return SkinUtils.As<TValue>(new Bindable<float>(existing.ColumnLineWidth[maniaLookup.TargetColumn.Value + 1]));
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@ -46,7 +46,7 @@ namespace osu.Game.Tests.Visual
|
||||
public bool CheckFailed(bool failed)
|
||||
{
|
||||
if (!failed)
|
||||
return ScoreProcessor.HasCompleted && !HealthProcessor.HasFailed;
|
||||
return ScoreProcessor.HasCompleted.Value && !HealthProcessor.HasFailed;
|
||||
|
||||
return HealthProcessor.HasFailed;
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual
|
||||
|
||||
public void Flip() => scrollingInfo.Direction.Value = scrollingInfo.Direction.Value == ScrollingDirection.Up ? ScrollingDirection.Down : ScrollingDirection.Up;
|
||||
|
||||
private class TestScrollingInfo : IScrollingInfo
|
||||
public class TestScrollingInfo : IScrollingInfo
|
||||
{
|
||||
public readonly Bindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>();
|
||||
IBindable<ScrollingDirection> IScrollingInfo.Direction => Direction;
|
||||
@ -54,7 +54,7 @@ namespace osu.Game.Tests.Visual
|
||||
IScrollAlgorithm IScrollingInfo.Algorithm => Algorithm;
|
||||
}
|
||||
|
||||
private class TestScrollAlgorithm : IScrollAlgorithm
|
||||
public class TestScrollAlgorithm : IScrollAlgorithm
|
||||
{
|
||||
public readonly SortedList<MultiplierControlPoint> ControlPoints = new SortedList<MultiplierControlPoint>();
|
||||
|
||||
|
52
osu.Game/Utils/OrderAttribute.cs
Normal file
52
osu.Game/Utils/OrderAttribute.cs
Normal file
@ -0,0 +1,52 @@
|
||||
// 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;
|
||||
|
||||
namespace osu.Game.Utils
|
||||
{
|
||||
public static class OrderAttributeUtils
|
||||
{
|
||||
/// <summary>
|
||||
/// Get values of an enum in order. Supports custom ordering via <see cref="OrderAttribute"/>.
|
||||
/// </summary>
|
||||
public static IEnumerable<T> GetValuesInOrder<T>()
|
||||
{
|
||||
var type = typeof(T);
|
||||
|
||||
if (!type.IsEnum)
|
||||
throw new InvalidOperationException("T must be an enum");
|
||||
|
||||
IEnumerable<T> items = (T[])Enum.GetValues(type);
|
||||
|
||||
if (Attribute.GetCustomAttribute(type, typeof(HasOrderedElementsAttribute)) == null)
|
||||
return items;
|
||||
|
||||
return items.OrderBy(i =>
|
||||
{
|
||||
if (type.GetField(i.ToString()).GetCustomAttributes(typeof(OrderAttribute), false).FirstOrDefault() is OrderAttribute attr)
|
||||
return attr.Order;
|
||||
|
||||
throw new ArgumentException($"Not all values of {nameof(T)} have {nameof(OrderAttribute)} specified.");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public class OrderAttribute : Attribute
|
||||
{
|
||||
public readonly int Order;
|
||||
|
||||
public OrderAttribute(int order)
|
||||
{
|
||||
Order = order;
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Enum)]
|
||||
public class HasOrderedElementsAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user