1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-15 10:32:50 +08:00

Compare commits

...

178 Commits

141 changed files with 2525 additions and 1465 deletions
+1 -1
View File
@@ -52,6 +52,6 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.412.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.417.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.421.0" />
</ItemGroup>
</Project>
@@ -0,0 +1,132 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Rulesets.Catch.Skinning;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Skinning;
using osu.Game.Tests.Visual;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Catch.Tests
{
public class TestSceneHyperDashColouring : OsuTestScene
{
[Resolved]
private SkinManager skins { get; set; }
[Test]
public void TestDefaultFruitColour()
{
var skin = new TestSkin();
checkHyperDashFruitColour(skin, Catcher.DEFAULT_HYPER_DASH_COLOUR);
}
[Test]
public void TestCustomFruitColour()
{
var skin = new TestSkin
{
HyperDashFruitColour = Color4.Cyan
};
checkHyperDashFruitColour(skin, skin.HyperDashFruitColour);
}
[Test]
public void TestCustomFruitColourPriority()
{
var skin = new TestSkin
{
HyperDashColour = Color4.Goldenrod,
HyperDashFruitColour = Color4.Cyan
};
checkHyperDashFruitColour(skin, skin.HyperDashFruitColour);
}
[Test]
public void TestFruitColourFallback()
{
var skin = new TestSkin
{
HyperDashColour = Color4.Goldenrod
};
checkHyperDashFruitColour(skin, skin.HyperDashColour);
}
private void checkHyperDashFruitColour(ISkin skin, Color4 expectedColour)
{
DrawableFruit drawableFruit = null;
AddStep("create hyper-dash fruit", () =>
{
var fruit = new Fruit { HyperDashTarget = new Banana() };
fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
Child = setupSkinHierarchy(drawableFruit = new DrawableFruit(fruit)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Scale = new Vector2(4f),
}, skin);
});
AddAssert("hyper-dash colour is correct", () => checkLegacyFruitHyperDashColour(drawableFruit, expectedColour));
}
private Drawable setupSkinHierarchy(Drawable child, ISkin skin)
{
var legacySkinProvider = new SkinProvidingContainer(skins.GetSkin(DefaultLegacySkin.Info));
var testSkinProvider = new SkinProvidingContainer(skin);
var legacySkinTransformer = new SkinProvidingContainer(new CatchLegacySkinTransformer(testSkinProvider));
return legacySkinProvider
.WithChild(testSkinProvider
.WithChild(legacySkinTransformer
.WithChild(child)));
}
private bool checkLegacyFruitHyperDashColour(DrawableFruit fruit, Color4 expectedColour) =>
fruit.ChildrenOfType<SkinnableDrawable>().First().Drawable.ChildrenOfType<Sprite>().Any(c => c.Colour == expectedColour);
private class TestSkin : LegacySkin
{
public Color4 HyperDashColour
{
get => Configuration.CustomColours[CatchSkinColour.HyperDash.ToString()];
set => Configuration.CustomColours[CatchSkinColour.HyperDash.ToString()] = value;
}
public Color4 HyperDashAfterImageColour
{
get => Configuration.CustomColours[CatchSkinColour.HyperDashAfterImage.ToString()];
set => Configuration.CustomColours[CatchSkinColour.HyperDashAfterImage.ToString()] = value;
}
public Color4 HyperDashFruitColour
{
get => Configuration.CustomColours[CatchSkinColour.HyperDashFruit.ToString()];
set => Configuration.CustomColours[CatchSkinColour.HyperDashFruit.ToString()] = value;
}
public TestSkin()
: base(new SkinInfo(), null, null, string.Empty)
{
}
}
}
}
+11 -2
View File
@@ -9,17 +9,26 @@ using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play;
using osuTK;
namespace osu.Game.Rulesets.Catch.Mods
{
public class CatchModRelax : ModRelax, IApplicableToDrawableRuleset<CatchHitObject>
public class CatchModRelax : ModRelax, IApplicableToDrawableRuleset<CatchHitObject>, IApplicableToPlayer
{
public override string Description => @"Use the mouse to control the catcher.";
private DrawableRuleset<CatchHitObject> drawableRuleset;
public void ApplyToDrawableRuleset(DrawableRuleset<CatchHitObject> drawableRuleset)
{
drawableRuleset.Cursor.Add(new MouseInputHelper((CatchPlayfield)drawableRuleset.Playfield));
this.drawableRuleset = drawableRuleset;
}
public void ApplyToPlayer(Player player)
{
if (!drawableRuleset.HasReplayLoaded.Value)
drawableRuleset.Cursor.Add(new MouseInputHelper((CatchPlayfield)drawableRuleset.Playfield));
}
private class MouseInputHelper : Drawable, IKeyBindingHandler<CatchAction>, IRequireHighFrequencyMousePosition
@@ -7,6 +7,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects.Drawables;
using osuTK.Graphics;
@@ -67,7 +68,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
BorderColour = Color4.Red,
BorderColour = Catcher.DEFAULT_HYPER_DASH_COLOUR,
BorderThickness = 12f * RADIUS_ADJUST,
Children = new Drawable[]
{
@@ -77,7 +78,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
Alpha = 0.3f,
Blending = BlendingParameters.Additive,
RelativeSizeAxes = Axes.Both,
Colour = Color4.Red,
Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR,
}
}
});
@@ -65,6 +65,15 @@ namespace osu.Game.Rulesets.Catch.Skinning
public SampleChannel GetSample(ISampleInfo sample) => source.GetSample(sample);
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => source.GetConfig<TLookup, TValue>(lookup);
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
{
switch (lookup)
{
case CatchSkinColour colour:
return source.GetConfig<SkinCustomColourLookup, TValue>(new SkinCustomColourLookup(colour));
}
return source.GetConfig<TLookup, TValue>(lookup);
}
}
}
@@ -0,0 +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.
namespace osu.Game.Rulesets.Catch.Skinning
{
public enum CatchSkinColour
{
/// <summary>
/// The colour to be used for the catcher while in hyper-dashing state.
/// </summary>
HyperDash,
/// <summary>
/// The colour to be used for fruits that grant the catcher the ability to hyper-dash.
/// </summary>
HyperDashFruit,
/// <summary>
/// The colour to be used for the "exploding" catcher sprite on beginning of hyper-dashing.
/// </summary>
HyperDashAfterImage,
}
}
@@ -7,6 +7,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Skinning;
using osuTK;
@@ -55,14 +56,16 @@ namespace osu.Game.Rulesets.Catch.Skinning
{
var hyperDash = new Sprite
{
Texture = skin.GetTexture(lookupName),
Colour = Color4.Red,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Blending = BlendingParameters.Additive,
Depth = 1,
Alpha = 0.7f,
Scale = new Vector2(1.2f)
Scale = new Vector2(1.2f),
Texture = skin.GetTexture(lookupName),
Colour = skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDashFruit)?.Value ??
skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDash)?.Value ??
Catcher.DEFAULT_HYPER_DASH_COLOUR,
};
AddInternal(hyperDash);
+2
View File
@@ -21,6 +21,8 @@ namespace osu.Game.Rulesets.Catch.UI
{
public class Catcher : Container, IKeyBindingHandler<CatchAction>
{
public static readonly Color4 DEFAULT_HYPER_DASH_COLOUR = Color4.Red;
/// <summary>
/// Whether we are hyper-dashing or not.
/// </summary>
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;
@@ -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,
}
}
+10 -120
View File
@@ -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
@@ -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.";
}
}
@@ -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
},
@@ -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;
}
}
}
@@ -0,0 +1,106 @@
// 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 NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Tests.Visual;
using osuTK;
namespace osu.Game.Rulesets.Osu.Tests.Mods
{
public class TestSceneOsuModHidden : ModTestScene
{
public TestSceneOsuModHidden()
: base(new OsuRuleset())
{
}
[Test]
public void TestDefaultBeatmapTest() => CreateModTest(new ModTestData
{
Mod = new OsuModHidden(),
Autoplay = true,
PassCondition = checkSomeHit
});
[Test]
public void FirstCircleAfterTwoSpinners() => CreateModTest(new ModTestData
{
Mod = new OsuModHidden(),
Autoplay = true,
Beatmap = new Beatmap
{
HitObjects = new List<HitObject>
{
new Spinner
{
Position = new Vector2(256, 192),
EndTime = 1000,
},
new Spinner
{
Position = new Vector2(256, 192),
StartTime = 1200,
EndTime = 2200,
},
new HitCircle
{
Position = new Vector2(300, 192),
StartTime = 3200,
},
new HitCircle
{
Position = new Vector2(384, 192),
StartTime = 4200,
}
}
},
PassCondition = checkSomeHit
});
[Test]
public void FirstSliderAfterTwoSpinners() => CreateModTest(new ModTestData
{
Mod = new OsuModHidden(),
Autoplay = true,
Beatmap = new Beatmap
{
HitObjects = new List<HitObject>
{
new Spinner
{
Position = new Vector2(256, 192),
EndTime = 1000,
},
new Spinner
{
Position = new Vector2(256, 192),
StartTime = 1200,
EndTime = 2200,
},
new Slider
{
StartTime = 3200,
Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(100, 0), })
},
new Slider
{
StartTime = 5200,
Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(100, 0), })
}
}
},
PassCondition = checkSomeHit
});
private bool checkSomeHit()
{
return Player.ScoreProcessor.JudgedHits >= 4;
}
}
}
@@ -296,6 +296,44 @@ namespace osu.Game.Rulesets.Osu.Tests
addJudgementAssert(hitObjects[1], HitResult.Great);
}
[Test]
public void TestHitSliderHeadBeforeHitCircle()
{
const double time_circle = 1000;
const double time_slider = 1200;
Vector2 positionCircle = Vector2.Zero;
Vector2 positionSlider = new Vector2(80);
var hitObjects = new List<OsuHitObject>
{
new TestHitCircle
{
StartTime = time_circle,
Position = positionCircle
},
new TestSlider
{
StartTime = time_slider,
Position = positionSlider,
Path = new SliderPath(PathType.Linear, new[]
{
Vector2.Zero,
new Vector2(25, 0),
})
}
};
performTest(hitObjects, new List<ReplayFrame>
{
new OsuReplayFrame { Time = time_circle - 100, Position = positionSlider, Actions = { OsuAction.LeftButton } },
new OsuReplayFrame { Time = time_circle, Position = positionCircle, Actions = { OsuAction.RightButton } },
new OsuReplayFrame { Time = time_slider, Position = positionSlider, Actions = { OsuAction.LeftButton } },
});
addJudgementAssert(hitObjects[0], HitResult.Great);
addJudgementAssert(hitObjects[1], HitResult.Great);
}
private void addJudgementAssert(OsuHitObject hitObject, HitResult result)
{
AddAssert($"({hitObject.GetType().ReadableName()} @ {hitObject.StartTime}) judgement is {result}",
@@ -316,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)
{
@@ -342,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
@@ -371,6 +403,9 @@ namespace osu.Game.Rulesets.Osu.Tests
{
HeadCircle.HitWindows = new TestHitWindows();
TailCircle.HitWindows = new TestHitWindows();
HeadCircle.HitWindows.SetDifficulty(0);
TailCircle.HitWindows.SetDifficulty(0);
};
}
}
@@ -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
@@ -23,6 +23,8 @@ namespace osu.Game.Rulesets.Osu.Mods
private const double fade_in_duration_multiplier = 0.4;
private const double fade_out_duration_multiplier = 0.3;
protected override bool IsFirstHideableObject(DrawableHitObject hitObject) => !(hitObject is DrawableSpinner);
public override void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
{
static void adjustFadeIn(OsuHitObject h) => h.TimeFadeIn = h.TimePreempt * fade_in_duration_multiplier;
+17 -1
View File
@@ -11,11 +11,12 @@ using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play;
using static osu.Game.Input.Handlers.ReplayInputHandler;
namespace osu.Game.Rulesets.Osu.Mods
{
public class OsuModRelax : ModRelax, IUpdatableByPlayfield, IApplicableToDrawableRuleset<OsuHitObject>
public class OsuModRelax : ModRelax, IUpdatableByPlayfield, IApplicableToDrawableRuleset<OsuHitObject>, IApplicableToPlayer
{
public override string Description => @"You don't need to click. Give your clicking/tapping fingers a break from the heat of things.";
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).ToArray();
@@ -33,15 +34,30 @@ namespace osu.Game.Rulesets.Osu.Mods
private ReplayState<OsuAction> state;
private double lastStateChangeTime;
private bool hasReplay;
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
{
// grab the input manager for future use.
osuInputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager;
}
public void ApplyToPlayer(Player player)
{
if (osuInputManager.ReplayInputHandler != null)
{
hasReplay = true;
return;
}
osuInputManager.AllowUserPresses = false;
}
public void Update(Playfield playfield)
{
if (hasReplay)
return;
bool requiresHold = false;
bool requiresHit = false;
@@ -125,7 +125,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
return new DrawableSliderTail(slider, tail);
case SliderHeadCircle head:
return new DrawableSliderHead(slider, head) { OnShake = Shake };
return new DrawableSliderHead(slider, head)
{
OnShake = Shake,
CheckHittable = (d, t) => CheckHittable?.Invoke(d, t) ?? true
};
case SliderTick tick:
return new DrawableSliderTick(tick) { Position = tick.Position - slider.Position };
+32 -53
View File
@@ -1,16 +1,17 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Osu.UI
{
/// <summary>
/// Ensures that <see cref="HitObject"/>s are hit in-order.
/// Ensures that <see cref="HitObject"/>s are hit in-order. Affectionately known as "note lock".
/// If a <see cref="HitObject"/> is hit out of order:
/// <list type="number">
/// <item><description>The hit is blocked if it occurred earlier than the previous <see cref="HitObject"/>'s start time.</description></item>
@@ -36,13 +37,9 @@ namespace osu.Game.Rulesets.Osu.UI
{
DrawableHitObject blockingObject = null;
// Find the last hitobject which blocks future hits.
foreach (var obj in hitObjectContainer.AliveObjects)
foreach (var obj in enumerateHitObjectsUpTo(hitObject.HitObject.StartTime))
{
if (obj == hitObject)
break;
if (drawableCanBlockFutureHits(obj))
if (hitObjectCanBlockFutureHits(obj))
blockingObject = obj;
}
@@ -54,74 +51,56 @@ namespace osu.Game.Rulesets.Osu.UI
// 1. The last blocking hitobject has been judged.
// 2. The current time is after the last hitobject's start time.
// Hits at exactly the same time as the blocking hitobject are allowed for maps that contain simultaneous hitobjects (e.g. /b/372245).
if (blockingObject.Judged || time >= blockingObject.HitObject.StartTime)
return true;
return false;
return blockingObject.Judged || time >= blockingObject.HitObject.StartTime;
}
/// <summary>
/// Handles a <see cref="HitObject"/> being hit to potentially miss all earlier <see cref="HitObject"/>s.
/// </summary>
/// <param name="hitObject">The <see cref="HitObject"/> that was hit.</param>
public void HandleHit(HitObject hitObject)
public void HandleHit(DrawableHitObject hitObject)
{
// Hitobjects which themselves don't block future hitobjects don't cause misses (e.g. slider ticks, spinners).
if (!hitObjectCanBlockFutureHits(hitObject))
return;
double maximumTime = hitObject.StartTime;
if (!IsHittable(hitObject, hitObject.HitObject.StartTime + hitObject.Result.TimeOffset))
throw new InvalidOperationException($"A {hitObject} was hit before it became hittable!");
// Iterate through and apply miss results to all top-level and nested hitobjects which block future hits.
foreach (var obj in hitObjectContainer.AliveObjects)
foreach (var obj in enumerateHitObjectsUpTo(hitObject.HitObject.StartTime))
{
if (obj.Judged || obj.HitObject.StartTime >= maximumTime)
if (obj.Judged)
continue;
if (hitObjectCanBlockFutureHits(obj.HitObject))
applyMiss(obj);
foreach (var nested in obj.NestedHitObjects)
{
if (nested.Judged || nested.HitObject.StartTime >= maximumTime)
continue;
if (hitObjectCanBlockFutureHits(nested.HitObject))
applyMiss(nested);
}
if (hitObjectCanBlockFutureHits(obj))
((DrawableOsuHitObject)obj).MissForcefully();
}
static void applyMiss(DrawableHitObject obj) => ((DrawableOsuHitObject)obj).MissForcefully();
}
/// <summary>
/// Whether a <see cref="DrawableHitObject"/> blocks hits on future <see cref="DrawableHitObject"/>s until its start time is reached.
/// </summary>
/// <remarks>
/// This will ONLY match on top-most <see cref="DrawableHitObject"/>s.
/// </remarks>
/// <param name="hitObject">The <see cref="DrawableHitObject"/> to test.</param>
private static bool drawableCanBlockFutureHits(DrawableHitObject hitObject)
{
// Special considerations for slider tails aren't required since only top-most drawable hitobjects are being iterated over.
return hitObject is DrawableHitCircle || hitObject is DrawableSlider;
}
/// <summary>
/// Whether a <see cref="HitObject"/> blocks hits on future <see cref="HitObject"/>s until its start time is reached.
/// </summary>
/// <remarks>
/// This is more rigorous and may not match on top-most <see cref="HitObject"/>s as <see cref="drawableCanBlockFutureHits"/> does.
/// </remarks>
/// <param name="hitObject">The <see cref="HitObject"/> to test.</param>
private static bool hitObjectCanBlockFutureHits(HitObject hitObject)
{
// Unlike the above we will receive slider tails, but they do not block future hits.
if (hitObject is SliderTailCircle)
return false;
private static bool hitObjectCanBlockFutureHits(DrawableHitObject hitObject)
=> hitObject is DrawableHitCircle;
// All other hitcircles continue to block future hits.
return hitObject is HitCircle;
private IEnumerable<DrawableHitObject> enumerateHitObjectsUpTo(double targetTime)
{
foreach (var obj in hitObjectContainer.AliveObjects)
{
if (obj.HitObject.StartTime >= targetTime)
yield break;
yield return obj;
foreach (var nestedObj in obj.NestedHitObjects)
{
if (nestedObj.HitObject.StartTime >= targetTime)
break;
yield return nestedObj;
}
}
}
}
}
+1 -1
View File
@@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Osu.UI
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
{
// Hitobjects that block future hits should miss previous hitobjects if they're hit out-of-order.
hitPolicy.HandleHit(result.HitObject);
hitPolicy.HandleHit(judgedObject);
if (!judgedObject.DisplayResult || !DisplayJudgements.Value)
return;
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

@@ -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
@@ -0,0 +1,42 @@
// 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.Graphics;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Taiko.Skinning;
using osu.Game.Rulesets.Taiko.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Taiko.Tests.Skinning
{
public class TestSceneTaikoPlayfield : TaikoSkinnableTestScene
{
public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[]
{
typeof(HitTarget),
typeof(LegacyHitTarget),
}).ToList();
[Cached(typeof(IScrollingInfo))]
private ScrollingTestContainer.TestScrollingInfo info = new ScrollingTestContainer.TestScrollingInfo
{
Direction = { Value = ScrollingDirection.Left },
TimeRange = { Value = 5000 },
};
public TestSceneTaikoPlayfield()
{
AddStep("Load playfield", () => SetContents(() => new TaikoPlayfield(new ControlPointInfo())
{
Height = 0.4f,
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
}));
}
}
}
@@ -24,7 +24,7 @@ using osuTK;
namespace osu.Game.Rulesets.Taiko.Tests
{
[TestFixture]
public class TestSceneTaikoPlayfield : OsuTestScene
public class TestSceneHits : OsuTestScene
{
private const double default_duration = 1000;
private const float scroll_time = 1000;
@@ -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;
}
}
@@ -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;
}
}
}
@@ -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 -67
View File
@@ -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;
}
}
}
}
@@ -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 osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Skinning;
using osuTK;
namespace osu.Game.Rulesets.Taiko.Skinning
{
public class LegacyHitTarget : CompositeDrawable
{
[BackgroundDependencyLoader]
private void load(ISkinSource skin)
{
RelativeSizeAxes = Axes.Both;
InternalChildren = new Drawable[]
{
new Sprite
{
Texture = skin.GetTexture("approachcircle"),
Scale = new Vector2(0.73f),
Alpha = 0.7f,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
new Sprite
{
Texture = skin.GetTexture("taikobigcircle"),
Scale = new Vector2(0.7f),
Alpha = 0.5f,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
};
}
}
}
@@ -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,15 @@ namespace osu.Game.Rulesets.Taiko.Skinning
return new LegacyHit(taikoComponent.Component);
return null;
case TaikoSkinComponents.DrumRollTick:
return this.GetAnimation("sliderscorepoint", false, false);
case TaikoSkinComponents.HitTarget:
if (GetTexture("taikobigcircle") != null)
return new LegacyHitTarget();
return null;
}
return source.GetDrawableComponent(component);
@@ -7,6 +7,10 @@ namespace osu.Game.Rulesets.Taiko
{
InputDrum,
CentreHit,
RimHit
RimHit,
DrumRollBody,
DrumRollTick,
Swell,
HitTarget
}
}
+2
View File
@@ -22,6 +22,8 @@ namespace osu.Game.Rulesets.Taiko.UI
public HitTarget()
{
RelativeSizeAxes = Axes.Both;
Children = new Drawable[]
{
new Box
+4 -3
View File
@@ -17,6 +17,7 @@ using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Rulesets.Taiko.Objects.Drawables;
using osu.Game.Rulesets.Taiko.Judgements;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Skinning;
using osuTK;
using osuTK.Graphics;
@@ -42,7 +43,7 @@ namespace osu.Game.Rulesets.Taiko.UI
private readonly Container<HitExplosion> hitExplosionContainer;
private readonly Container<KiaiHitExplosion> kiaiExplosionContainer;
private readonly JudgementContainer<DrawableTaikoJudgement> judgementContainer;
internal readonly HitTarget HitTarget;
internal readonly Drawable HitTarget;
private readonly ProxyContainer topLevelHitContainer;
private readonly ProxyContainer barlineContainer;
@@ -90,7 +91,7 @@ namespace osu.Game.Rulesets.Taiko.UI
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Left = HIT_TARGET_OFFSET },
Masking = true,
Children = new Drawable[]
Children = new[]
{
hitExplosionContainer = new Container<HitExplosion>
{
@@ -98,7 +99,7 @@ namespace osu.Game.Rulesets.Taiko.UI
FillMode = FillMode.Fit,
Blending = BlendingParameters.Additive,
},
HitTarget = new HitTarget
HitTarget = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.HitTarget), _ => new HitTarget())
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.Centre,
@@ -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;
}
}
}
}
@@ -0,0 +1,98 @@
// 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 NUnit.Framework;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Bindings;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Input.Bindings;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay
{
[HeadlessTest]
public class TestSceneKeyBindings : OsuManualInputManagerTestScene
{
private readonly ActionReceiver receiver;
public TestSceneKeyBindings()
{
Add(new TestKeyBindingContainer
{
Child = receiver = new ActionReceiver()
});
}
[Test]
public void TestDefaultsWhenNotDatabased()
{
AddStep("fire key", () =>
{
InputManager.PressKey(Key.A);
InputManager.ReleaseKey(Key.A);
});
AddAssert("received key", () => receiver.ReceivedAction);
}
private class TestRuleset : Ruleset
{
public override IEnumerable<Mod> GetModsFor(ModType type) =>
throw new System.NotImplementedException();
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) =>
throw new System.NotImplementedException();
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) =>
throw new System.NotImplementedException();
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) =>
throw new System.NotImplementedException();
public override IEnumerable<KeyBinding> GetDefaultKeyBindings(int variant = 0)
{
return new[]
{
new KeyBinding(InputKey.A, TestAction.Down),
};
}
public override string Description => "test";
public override string ShortName => "test";
}
private enum TestAction
{
Down,
}
private class TestKeyBindingContainer : DatabasedKeyBindingContainer<TestAction>
{
public TestKeyBindingContainer()
: base(new TestRuleset().RulesetInfo, 0)
{
}
}
private class ActionReceiver : CompositeDrawable, IKeyBindingHandler<TestAction>
{
public bool ReceivedAction;
public bool OnPressed(TestAction action)
{
ReceivedAction = action == TestAction.Down;
return true;
}
public void OnReleased(TestAction action)
{
}
}
}
}
@@ -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;
@@ -0,0 +1,43 @@
// 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 NUnit.Framework;
using osu.Game.Overlays;
using osu.Game.Overlays.Dashboard;
using osu.Game.Overlays.Dashboard.Friends;
namespace osu.Game.Tests.Visual.Online
{
public class TestSceneDashboardOverlay : OsuTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(DashboardOverlay),
typeof(DashboardOverlayHeader),
typeof(FriendDisplay)
};
protected override bool UseOnlineAPI => true;
private readonly DashboardOverlay overlay;
public TestSceneDashboardOverlay()
{
Add(overlay = new DashboardOverlay());
}
[Test]
public void TestShow()
{
AddStep("Show", overlay.Show);
}
[Test]
public void TestHide()
{
AddStep("Hide", overlay.Hide);
}
}
}
@@ -9,7 +9,7 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Online;
using osu.Game.Overlays.Direct;
using osu.Game.Overlays.BeatmapListing.Panels;
using osu.Game.Rulesets.Osu;
using osu.Game.Tests.Resources;
using osuTK;
@@ -20,7 +20,7 @@ namespace osu.Game.Tests.Visual.Online
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(PanelDownloadButton)
typeof(BeatmapPanelDownloadButton)
};
private TestDownloadButton downloadButton;
@@ -143,7 +143,7 @@ namespace osu.Game.Tests.Visual.Online
return beatmap;
}
private class TestDownloadButton : PanelDownloadButton
private class TestDownloadButton : BeatmapPanelDownloadButton
{
public new bool DownloadEnabled => base.DownloadEnabled;
@@ -1,215 +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 System.Collections.Generic;
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Overlays;
namespace osu.Game.Tests.Visual.Online
{
[TestFixture]
public class TestSceneDirectOverlay : OsuTestScene
{
private DirectOverlay direct;
protected override bool UseOnlineAPI => true;
protected override void LoadComplete()
{
base.LoadComplete();
Add(direct = new DirectOverlay());
newBeatmaps();
AddStep(@"toggle", direct.ToggleVisibility);
AddStep(@"result counts", () => direct.ResultAmounts = new DirectOverlay.ResultCounts(1, 4, 13));
AddStep(@"trigger disabled", () => Ruleset.Disabled = !Ruleset.Disabled);
}
private void newBeatmaps()
{
direct.BeatmapSets = new[]
{
new BeatmapSetInfo
{
OnlineBeatmapSetID = 578332,
Metadata = new BeatmapMetadata
{
Title = @"OrVid",
Artist = @"An",
AuthorString = @"RLC",
Source = @"",
Tags = @"acuticnotes an-fillnote revid tear tearvid encrpted encryption axi axivid quad her hervid recoll",
},
OnlineInfo = new BeatmapSetOnlineInfo
{
Covers = new BeatmapSetOnlineCovers
{
Card = @"https://assets.ppy.sh/beatmaps/578332/covers/card.jpg?1494591390",
Cover = @"https://assets.ppy.sh/beatmaps/578332/covers/cover.jpg?1494591390",
},
Preview = @"https://b.ppy.sh/preview/578332.mp3",
PlayCount = 97,
FavouriteCount = 72,
},
Beatmaps = new List<BeatmapInfo>
{
new BeatmapInfo
{
Ruleset = Ruleset.Value,
StarDifficulty = 5.35f,
Metadata = new BeatmapMetadata(),
},
},
},
new BeatmapSetInfo
{
OnlineBeatmapSetID = 599627,
Metadata = new BeatmapMetadata
{
Title = @"tiny lamp",
Artist = @"fhana",
AuthorString = @"Sotarks",
Source = @"ぎんぎつね",
Tags = @"lantis junichi sato yuxuki waga kevin mitsunaga towana gingitsune opening op full ver version kalibe collab collaboration",
},
OnlineInfo = new BeatmapSetOnlineInfo
{
Covers = new BeatmapSetOnlineCovers
{
Card = @"https://assets.ppy.sh/beatmaps/599627/covers/card.jpg?1494539318",
Cover = @"https://assets.ppy.sh/beatmaps/599627/covers/cover.jpg?1494539318",
},
Preview = @"https//b.ppy.sh/preview/599627.mp3",
PlayCount = 3082,
FavouriteCount = 14,
},
Beatmaps = new List<BeatmapInfo>
{
new BeatmapInfo
{
Ruleset = Ruleset.Value,
StarDifficulty = 5.81f,
Metadata = new BeatmapMetadata(),
},
},
},
new BeatmapSetInfo
{
OnlineBeatmapSetID = 513268,
Metadata = new BeatmapMetadata
{
Title = @"At Gwanghwamun",
Artist = @"KYUHYUN",
AuthorString = @"Cerulean Veyron",
Source = @"",
Tags = @"soul ballad kh super junior sj suju 슈퍼주니어 kt뮤직 sm엔터테인먼트 s.m.entertainment kt music 1st mini album ep",
},
OnlineInfo = new BeatmapSetOnlineInfo
{
Covers = new BeatmapSetOnlineCovers
{
Card = @"https://assets.ppy.sh/beatmaps/513268/covers/card.jpg?1494502863",
Cover = @"https://assets.ppy.sh/beatmaps/513268/covers/cover.jpg?1494502863",
},
Preview = @"https//b.ppy.sh/preview/513268.mp3",
PlayCount = 2762,
FavouriteCount = 15,
},
Beatmaps = new List<BeatmapInfo>
{
new BeatmapInfo
{
Ruleset = Ruleset.Value,
StarDifficulty = 0.9f,
Metadata = new BeatmapMetadata(),
},
new BeatmapInfo
{
Ruleset = Ruleset.Value,
StarDifficulty = 1.1f,
},
new BeatmapInfo
{
Ruleset = Ruleset.Value,
StarDifficulty = 2.02f,
},
new BeatmapInfo
{
Ruleset = Ruleset.Value,
StarDifficulty = 3.49f,
},
},
},
new BeatmapSetInfo
{
OnlineBeatmapSetID = 586841,
Metadata = new BeatmapMetadata
{
Title = @"RHAPSODY OF BLUE SKY",
Artist = @"fhana",
AuthorString = @"[Kamiya]",
Source = @"小林さんちのメイドラゴン",
Tags = @"kobayashi san chi no maidragon aozora no opening anime maid dragon oblivion karen dynamix imoutosan pata-mon gxytcgxytc",
},
OnlineInfo = new BeatmapSetOnlineInfo
{
Covers = new BeatmapSetOnlineCovers
{
Card = @"https://assets.ppy.sh/beatmaps/586841/covers/card.jpg?1494052741",
Cover = @"https://assets.ppy.sh/beatmaps/586841/covers/cover.jpg?1494052741",
},
Preview = @"https//b.ppy.sh/preview/586841.mp3",
PlayCount = 62317,
FavouriteCount = 161,
},
Beatmaps = new List<BeatmapInfo>
{
new BeatmapInfo
{
Ruleset = Ruleset.Value,
StarDifficulty = 1.26f,
Metadata = new BeatmapMetadata(),
},
new BeatmapInfo
{
Ruleset = Ruleset.Value,
StarDifficulty = 2.01f,
},
new BeatmapInfo
{
Ruleset = Ruleset.Value,
StarDifficulty = 2.87f,
},
new BeatmapInfo
{
Ruleset = Ruleset.Value,
StarDifficulty = 3.76f,
},
new BeatmapInfo
{
Ruleset = Ruleset.Value,
StarDifficulty = 3.93f,
},
new BeatmapInfo
{
Ruleset = Ruleset.Value,
StarDifficulty = 4.37f,
},
new BeatmapInfo
{
Ruleset = Ruleset.Value,
StarDifficulty = 5.13f,
},
new BeatmapInfo
{
Ruleset = Ruleset.Value,
StarDifficulty = 5.42f,
},
},
},
};
}
}
}
@@ -8,7 +8,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Overlays.Direct;
using osu.Game.Overlays.BeatmapListing.Panels;
using osu.Game.Rulesets;
using osu.Game.Users;
using osuTK;
@@ -20,8 +20,8 @@ namespace osu.Game.Tests.Visual.Online
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(DirectGridPanel),
typeof(DirectListPanel),
typeof(GridBeatmapPanel),
typeof(ListBeatmapPanel),
typeof(IconPill)
};
@@ -126,12 +126,12 @@ namespace osu.Game.Tests.Visual.Online
Spacing = new Vector2(5, 20),
Children = new Drawable[]
{
new DirectGridPanel(normal),
new DirectGridPanel(undownloadable),
new DirectGridPanel(manyDifficulties),
new DirectListPanel(normal),
new DirectListPanel(undownloadable),
new DirectListPanel(manyDifficulties),
new GridBeatmapPanel(normal),
new GridBeatmapPanel(undownloadable),
new GridBeatmapPanel(manyDifficulties),
new ListBeatmapPanel(normal),
new ListBeatmapPanel(undownloadable),
new ListBeatmapPanel(manyDifficulties),
},
},
};
@@ -10,6 +10,7 @@ using osu.Game.Users;
using osu.Game.Overlays;
using osu.Framework.Allocation;
using NUnit.Framework;
using osu.Game.Online.API;
namespace osu.Game.Tests.Visual.Online
{
@@ -27,7 +28,7 @@ namespace osu.Game.Tests.Visual.Online
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
private FriendDisplay display;
private TestFriendDisplay display;
[SetUp]
public void Setup() => Schedule(() =>
@@ -35,7 +36,7 @@ namespace osu.Game.Tests.Visual.Online
Child = new BasicScrollContainer
{
RelativeSizeAxes = Axes.Both,
Child = display = new FriendDisplay()
Child = display = new TestFriendDisplay()
};
});
@@ -83,5 +84,17 @@ namespace osu.Game.Tests.Visual.Online
LastVisit = DateTimeOffset.Now
}
};
private class TestFriendDisplay : FriendDisplay
{
public void Fetch()
{
base.APIStateChanged(API, APIState.Online);
}
public override void APIStateChanged(IAPIProvider api, APIState state)
{
}
}
}
}
@@ -0,0 +1,85 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Online.Chat;
using osu.Game.Rulesets;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.Online
{
[HeadlessTest]
public class TestSceneNowPlayingCommand : OsuTestScene
{
[Cached(typeof(IChannelPostTarget))]
private PostTarget postTarget { get; set; }
public TestSceneNowPlayingCommand()
{
Add(postTarget = new PostTarget());
}
[Test]
public void TestGenericActivity()
{
AddStep("Set activity", () => API.Activity.Value = new UserActivity.InLobby());
AddStep("Run command", () => Add(new NowPlayingCommand()));
AddAssert("Check correct response", () => postTarget.LastMessage.Contains("is listening"));
}
[Test]
public void TestEditActivity()
{
AddStep("Set activity", () => API.Activity.Value = new UserActivity.Editing(new BeatmapInfo()));
AddStep("Run command", () => Add(new NowPlayingCommand()));
AddAssert("Check correct response", () => postTarget.LastMessage.Contains("is editing"));
}
[Test]
public void TestPlayActivity()
{
AddStep("Set activity", () => API.Activity.Value = new UserActivity.SoloGame(new BeatmapInfo(), new RulesetInfo()));
AddStep("Run command", () => Add(new NowPlayingCommand()));
AddAssert("Check correct response", () => postTarget.LastMessage.Contains("is playing"));
}
[TestCase(true)]
[TestCase(false)]
public void TestLinkPresence(bool hasOnlineId)
{
AddStep("Set activity", () => API.Activity.Value = new UserActivity.InLobby());
AddStep("Set beatmap", () => Beatmap.Value = new DummyWorkingBeatmap(null, null)
{
BeatmapInfo = { OnlineBeatmapID = hasOnlineId ? 1234 : (int?)null }
});
AddStep("Run command", () => Add(new NowPlayingCommand()));
if (hasOnlineId)
AddAssert("Check link presence", () => postTarget.LastMessage.Contains("https://osu.ppy.sh/b/1234"));
else
AddAssert("Check link not present", () => !postTarget.LastMessage.Contains("https://"));
}
public class PostTarget : Component, IChannelPostTarget
{
public void PostMessage(string text, bool isAction = false, Channel target = null)
{
LastMessage = text;
}
public string LastMessage { get; private set; }
}
}
}
@@ -359,6 +359,68 @@ namespace osu.Game.Tests.Visual.SongSelect
AddUntilStep("no selection", () => songSelect.Carousel.SelectedBeatmap == null);
}
[Test]
public void TestPresentNewRulesetNewBeatmap()
{
createSongSelect();
changeRuleset(2);
addRulesetImportStep(2);
AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap.RulesetID == 2);
addRulesetImportStep(0);
addRulesetImportStep(0);
addRulesetImportStep(0);
BeatmapInfo target = null;
AddStep("select beatmap/ruleset externally", () =>
{
target = manager.GetAllUsableBeatmapSets()
.Last(b => b.Beatmaps.Any(bi => bi.RulesetID == 0)).Beatmaps.Last();
Ruleset.Value = rulesets.AvailableRulesets.First(r => r.ID == 0);
Beatmap.Value = manager.GetWorkingBeatmap(target);
});
AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap.Equals(target));
// this is an important check, to make sure updateComponentFromBeatmap() was actually run
AddUntilStep("selection shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap.BeatmapInfo == target);
}
[Test]
public void TestPresentNewBeatmapNewRuleset()
{
createSongSelect();
changeRuleset(2);
addRulesetImportStep(2);
AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap.RulesetID == 2);
addRulesetImportStep(0);
addRulesetImportStep(0);
addRulesetImportStep(0);
BeatmapInfo target = null;
AddStep("select beatmap/ruleset externally", () =>
{
target = manager.GetAllUsableBeatmapSets()
.Last(b => b.Beatmaps.Any(bi => bi.RulesetID == 0)).Beatmaps.Last();
Beatmap.Value = manager.GetWorkingBeatmap(target);
Ruleset.Value = rulesets.AvailableRulesets.First(r => r.ID == 0);
});
AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap.Equals(target));
AddUntilStep("has correct ruleset", () => Ruleset.Value.ID == 0);
// this is an important check, to make sure updateComponentFromBeatmap() was actually run
AddUntilStep("selection shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap.BeatmapInfo == target);
}
[Test]
public void TestRulesetChangeResetsMods()
{
+2 -2
View File
@@ -47,8 +47,8 @@ namespace osu.Game.Tests.Visual
typeof(IdleTracker),
typeof(OnScreenDisplay),
typeof(NotificationOverlay),
typeof(DirectOverlay),
typeof(SocialOverlay),
typeof(BeatmapListingOverlay),
typeof(DashboardOverlay),
typeof(ChannelManager),
typeof(ChatOverlay),
typeof(SettingsOverlay),
@@ -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)
@@ -187,27 +187,13 @@ namespace osu.Game.Beatmaps.Formats
writer.WriteLine("[HitObjects]");
// TODO: implement other legacy rulesets
switch (beatmap.BeatmapInfo.RulesetID)
{
case 0:
foreach (var h in beatmap.HitObjects)
handleOsuHitObject(writer, h);
break;
case 1:
foreach (var h in beatmap.HitObjects)
handleTaikoHitObject(writer, h);
break;
case 2:
foreach (var h in beatmap.HitObjects)
handleCatchHitObject(writer, h);
break;
case 3:
foreach (var h in beatmap.HitObjects)
handleManiaHitObject(writer, h);
break;
}
}
@@ -328,12 +314,6 @@ namespace osu.Game.Beatmaps.Formats
}
}
private void handleTaikoHitObject(TextWriter writer, HitObject hitObject) => throw new NotImplementedException();
private void handleCatchHitObject(TextWriter writer, HitObject hitObject) => throw new NotImplementedException();
private void handleManiaHitObject(TextWriter writer, HitObject hitObject) => throw new NotImplementedException();
private string getSampleBank(IList<HitSampleInfo> samples, bool banksOnly = false, bool zeroBanks = false)
{
LegacySampleBank normalBank = toLegacySampleBank(samples.SingleOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL)?.Bank);
+2 -1
View File
@@ -150,7 +150,8 @@ namespace osu.Game.Beatmaps.Formats
HitObjects,
Variables,
Fonts,
Mania
CatchTheBeat,
Mania,
}
internal class LegacyDifficultyControlPoint : DifficultyControlPoint
@@ -62,6 +62,14 @@ namespace osu.Game.Input.Bindings
store.KeyBindingChanged -= ReloadMappings;
}
protected override void ReloadMappings() => KeyBindings = store.Query(ruleset?.ID, variant).ToList();
protected override void ReloadMappings()
{
if (ruleset != null && !ruleset.ID.HasValue)
// if the provided ruleset is not stored to the database, we have no way to retrieve custom bindings.
// fallback to defaults instead.
KeyBindings = DefaultKeyBindings;
else
KeyBindings = store.Query(ruleset?.ID, variant).ToList();
}
}
}
+24 -7
View File
@@ -18,24 +18,32 @@ namespace osu.Game.Online.API
public T Result { get; private set; }
protected APIRequest()
{
base.Success += () => TriggerSuccess(((OsuJsonWebRequest<T>)WebRequest)?.ResponseObject);
}
/// <summary>
/// Invoked on successful completion of an API request.
/// This will be scheduled to the API's internal scheduler (run on update thread automatically).
/// </summary>
public new event APISuccessHandler<T> Success;
protected override void PostProcess()
{
base.PostProcess();
Result = ((OsuJsonWebRequest<T>)WebRequest)?.ResponseObject;
}
internal void TriggerSuccess(T result)
{
if (Result != null)
throw new InvalidOperationException("Attempted to trigger success more than once");
Result = result;
Success?.Invoke(result);
TriggerSuccess();
}
internal override void TriggerSuccess()
{
base.TriggerSuccess();
Success?.Invoke(Result);
}
}
@@ -99,6 +107,8 @@ namespace osu.Game.Online.API
if (checkAndScheduleFailure())
return;
PostProcess();
API.Schedule(delegate
{
if (cancelled) return;
@@ -107,7 +117,14 @@ namespace osu.Game.Online.API
});
}
internal void TriggerSuccess()
/// <summary>
/// Perform any post-processing actions after a successful request.
/// </summary>
protected virtual void PostProcess()
{
}
internal virtual void TriggerSuccess()
{
Success?.Invoke();
}
@@ -1,30 +1,40 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.ComponentModel;
using osu.Framework.IO.Network;
using osu.Game.Overlays;
using osu.Game.Overlays.Direct;
using osu.Game.Overlays.BeatmapListing;
using osu.Game.Rulesets;
namespace osu.Game.Online.API.Requests
{
public class SearchBeatmapSetsRequest : APIRequest<SearchBeatmapSetsResponse>
{
public SearchCategory SearchCategory { get; set; }
public SortCriteria 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 = SortCriteria.Ranked;
SortDirection = SortDirection.Descending;
Genre = SearchGenre.Any;
Language = SearchLanguage.Any;
}
protected override WebRequest CreateWebRequest()
@@ -35,31 +45,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,
}
}
+6 -2
View File
@@ -18,7 +18,7 @@ namespace osu.Game.Online.Chat
/// <summary>
/// Manages everything channel related
/// </summary>
public class ChannelManager : PollingComponent
public class ChannelManager : PollingComponent, IChannelPostTarget
{
/// <summary>
/// The channels the player joins on startup
@@ -204,6 +204,10 @@ namespace osu.Game.Online.Chat
switch (command)
{
case "np":
AddInternal(new NowPlayingCommand());
break;
case "me":
if (string.IsNullOrWhiteSpace(content))
{
@@ -234,7 +238,7 @@ namespace osu.Game.Online.Chat
break;
case "help":
target.AddNewMessages(new InfoMessage("Supported commands: /help, /me [action], /join [channel]"));
target.AddNewMessages(new InfoMessage("Supported commands: /help, /me [action], /join [channel], /np"));
break;
default:
@@ -0,0 +1,19 @@
// 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;
namespace osu.Game.Online.Chat
{
[Cached(typeof(IChannelPostTarget))]
public interface IChannelPostTarget
{
/// <summary>
/// Posts a message to the currently opened channel.
/// </summary>
/// <param name="text">The message text that is going to be posted</param>
/// <param name="isAction">Is true if the message is an action, e.g.: user is currently eating </param>
/// <param name="target">An optional target channel. If null, <see cref="ChannelManager.CurrentChannel"/> will be used.</param>
void PostMessage(string text, bool isAction = false, Channel target = null);
}
}
+55
View File
@@ -0,0 +1,55 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Online.API;
using osu.Game.Users;
namespace osu.Game.Online.Chat
{
public class NowPlayingCommand : Component
{
[Resolved]
private IChannelPostTarget channelManager { get; set; }
[Resolved]
private IAPIProvider api { get; set; }
[Resolved]
private Bindable<WorkingBeatmap> currentBeatmap { get; set; }
protected override void LoadComplete()
{
base.LoadComplete();
string verb;
BeatmapInfo beatmap;
switch (api.Activity.Value)
{
case UserActivity.SoloGame solo:
verb = "playing";
beatmap = solo.Beatmap;
break;
case UserActivity.Editing edit:
verb = "editing";
beatmap = edit.Beatmap;
break;
default:
verb = "listening to";
beatmap = currentBeatmap.Value.BeatmapInfo;
break;
}
var beatmapString = beatmap.OnlineBeatmapID.HasValue ? $"[https://osu.ppy.sh/b/{beatmap.OnlineBeatmapID} {beatmap}]" : beatmap.ToString();
channelManager.PostMessage($"is {verb} {beatmapString}", true);
Expire();
}
}
}
+2 -2
View File
@@ -3,7 +3,7 @@
using System;
using System.Threading.Tasks;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Threading;
namespace osu.Game.Online
@@ -11,7 +11,7 @@ namespace osu.Game.Online
/// <summary>
/// A component which requires a constant polling process.
/// </summary>
public abstract class PollingComponent : Component
public abstract class PollingComponent : CompositeDrawable // switch away from Component because InternalChildren are used in usages.
{
private double? lastTimePolled;
+7 -7
View File
@@ -65,9 +65,9 @@ namespace osu.Game
private NowPlayingOverlay nowPlaying;
private DirectOverlay direct;
private BeatmapListingOverlay beatmapListing;
private SocialOverlay social;
private DashboardOverlay dashboard;
private UserProfileOverlay userProfile;
@@ -610,8 +610,8 @@ namespace osu.Game
loadComponentSingleFile(screenshotManager, Add);
//overlay elements
loadComponentSingleFile(direct = new DirectOverlay(), overlayContent.Add, true);
loadComponentSingleFile(social = new SocialOverlay(), overlayContent.Add, true);
loadComponentSingleFile(beatmapListing = new BeatmapListingOverlay(), overlayContent.Add, true);
loadComponentSingleFile(dashboard = new DashboardOverlay(), overlayContent.Add, true);
var rankingsOverlay = loadComponentSingleFile(new RankingsOverlay(), overlayContent.Add, true);
loadComponentSingleFile(channelManager = new ChannelManager(), AddInternal, true);
loadComponentSingleFile(chatOverlay = new ChatOverlay(), overlayContent.Add, true);
@@ -670,7 +670,7 @@ namespace osu.Game
}
// ensure only one of these overlays are open at once.
var singleDisplayOverlays = new OverlayContainer[] { chatOverlay, social, direct, changelogOverlay, rankingsOverlay };
var singleDisplayOverlays = new OverlayContainer[] { chatOverlay, dashboard, beatmapListing, changelogOverlay, rankingsOverlay };
foreach (var overlay in singleDisplayOverlays)
{
@@ -842,7 +842,7 @@ namespace osu.Game
return true;
case GlobalAction.ToggleSocial:
social.ToggleVisibility();
dashboard.ToggleVisibility();
return true;
case GlobalAction.ResetInputSettings:
@@ -865,7 +865,7 @@ namespace osu.Game
return true;
case GlobalAction.ToggleDirect:
direct.ToggleVisibility();
beatmapListing.ToggleVisibility();
return true;
case GlobalAction.ToggleGameplayMouseButtons:
@@ -5,7 +5,7 @@ using osu.Framework.Bindables;
using osu.Game.Beatmaps;
using osu.Game.Online;
namespace osu.Game.Overlays.Direct
namespace osu.Game.Overlays
{
public abstract class BeatmapDownloadTrackingComposite : DownloadTrackingComposite<BeatmapSetInfo, BeatmapManager>
{
@@ -0,0 +1,164 @@
// 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.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) ? SortCriteria.Ranked : SortCriteria.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);
}
public void TakeFocus() => searchControl.TakeFocus();
}
}
@@ -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]
@@ -114,6 +121,8 @@ namespace osu.Game.Overlays.BeatmapListing
background.Colour = colourProvider.Dark6;
}
public void TakeFocus() => textBox.TakeFocus();
private class BeatmapSearchTextBox : SearchTextBox
{
protected override Color4 SelectionColour => Color4.Gray;
@@ -8,17 +8,16 @@ using osu.Framework.Graphics;
using osuTK.Graphics;
using osuTK;
using osu.Framework.Input.Events;
using osu.Game.Overlays.Direct;
namespace osu.Game.Overlays.BeatmapListing
{
public class BeatmapListingSortTabControl : OverlaySortTabControl<DirectSortCriteria>
public class BeatmapListingSortTabControl : OverlaySortTabControl<SortCriteria>
{
public readonly Bindable<SortDirection> SortDirection = new Bindable<SortDirection>(Overlays.SortDirection.Descending);
public BeatmapListingSortTabControl()
{
Current.Value = DirectSortCriteria.Ranked;
Current.Value = SortCriteria.Ranked;
}
protected override SortTabControl CreateControl() => new BeatmapSortTabControl
@@ -30,7 +29,7 @@ namespace osu.Game.Overlays.BeatmapListing
{
public readonly Bindable<SortDirection> SortDirection = new Bindable<SortDirection>();
protected override TabItem<DirectSortCriteria> CreateTabItem(DirectSortCriteria value) => new BeatmapSortTabItem(value)
protected override TabItem<SortCriteria> CreateTabItem(SortCriteria value) => new BeatmapSortTabItem(value)
{
SortDirection = { BindTarget = SortDirection }
};
@@ -40,12 +39,12 @@ namespace osu.Game.Overlays.BeatmapListing
{
public readonly Bindable<SortDirection> SortDirection = new Bindable<SortDirection>();
public BeatmapSortTabItem(DirectSortCriteria value)
public BeatmapSortTabItem(SortCriteria value)
: base(value)
{
}
protected override TabButton CreateTabButton(DirectSortCriteria value) => new BeatmapTabButton(value)
protected override TabButton CreateTabButton(SortCriteria value) => new BeatmapTabButton(value)
{
Active = { BindTarget = Active },
SortDirection = { BindTarget = SortDirection }
@@ -67,7 +66,7 @@ namespace osu.Game.Overlays.BeatmapListing
private readonly SpriteIcon icon;
public BeatmapTabButton(DirectSortCriteria value)
public BeatmapTabButton(SortCriteria value)
: base(value)
{
Add(icon = new SpriteIcon
@@ -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,9 +26,9 @@ using osu.Game.Graphics.UserInterface;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Overlays.Direct
namespace osu.Game.Overlays.BeatmapListing.Panels
{
public abstract class DirectPanel : OsuClickableContainer, IHasContextMenu
public abstract class BeatmapPanel : OsuClickableContainer, IHasContextMenu
{
public readonly BeatmapSetInfo SetInfo;
@@ -49,7 +49,7 @@ namespace osu.Game.Overlays.Direct
protected Action ViewBeatmap;
protected DirectPanel(BeatmapSetInfo setInfo)
protected BeatmapPanel(BeatmapSetInfo setInfo)
{
Debug.Assert(setInfo.OnlineBeatmapSetID != null);
@@ -148,7 +148,7 @@ namespace osu.Game.Overlays.Direct
if (SetInfo.Beatmaps.Count > maximum_difficulty_icons)
{
foreach (var ruleset in SetInfo.Beatmaps.Select(b => b.Ruleset).Distinct())
icons.Add(new GroupedDifficultyIcon(SetInfo.Beatmaps.FindAll(b => b.Ruleset.Equals(ruleset)), ruleset, this is DirectListPanel ? Color4.White : colours.Gray5));
icons.Add(new GroupedDifficultyIcon(SetInfo.Beatmaps.FindAll(b => b.Ruleset.Equals(ruleset)), ruleset, this is ListBeatmapPanel ? Color4.White : colours.Gray5));
}
else
{
@@ -11,9 +11,9 @@ using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online;
namespace osu.Game.Overlays.Direct
namespace osu.Game.Overlays.BeatmapListing.Panels
{
public class PanelDownloadButton : BeatmapDownloadTrackingComposite
public class BeatmapPanelDownloadButton : BeatmapDownloadTrackingComposite
{
protected bool DownloadEnabled => button.Enabled.Value;
@@ -26,7 +26,7 @@ namespace osu.Game.Overlays.Direct
private readonly DownloadButton button;
private Bindable<bool> noVideoSetting;
public PanelDownloadButton(BeatmapSetInfo beatmapSet)
public BeatmapPanelDownloadButton(BeatmapSetInfo beatmapSet)
: base(beatmapSet)
{
InternalChild = shakeContainer = new ShakeContainer
@@ -10,7 +10,7 @@ using osu.Game.Graphics.UserInterface;
using osu.Game.Online;
using osuTK.Graphics;
namespace osu.Game.Overlays.Direct
namespace osu.Game.Overlays.BeatmapListing.Panels
{
public class DownloadProgressBar : BeatmapDownloadTrackingComposite
{
@@ -1,25 +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 osuTK;
using osuTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Overlays.Direct
namespace osu.Game.Overlays.BeatmapListing.Panels
{
public class DirectGridPanel : DirectPanel
public class GridBeatmapPanel : BeatmapPanel
{
private const float horizontal_padding = 10;
private const float vertical_padding = 5;
@@ -31,7 +31,7 @@ namespace osu.Game.Overlays.Direct
protected override PlayButton PlayButton => playButton;
protected override Box PreviewBar => progressBar;
public DirectGridPanel(BeatmapSetInfo beatmap)
public GridBeatmapPanel(BeatmapSetInfo beatmap)
: base(beatmap)
{
Width = 380;
@@ -156,7 +156,7 @@ namespace osu.Game.Overlays.Direct
},
},
},
new PanelDownloadButton(SetInfo)
new BeatmapPanelDownloadButton(SetInfo)
{
Size = new Vector2(50, 30),
Margin = new MarginPadding(horizontal_padding),
@@ -8,7 +8,7 @@ using osu.Framework.Graphics.Sprites;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Overlays.Direct
namespace osu.Game.Overlays.BeatmapListing.Panels
{
public class IconPill : CircularContainer
{
@@ -1,25 +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 osuTK;
using osuTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Colour;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Overlays.Direct
namespace osu.Game.Overlays.BeatmapListing.Panels
{
public class DirectListPanel : DirectPanel
public class ListBeatmapPanel : BeatmapPanel
{
private const float transition_duration = 120;
private const float horizontal_padding = 10;
@@ -27,7 +27,7 @@ namespace osu.Game.Overlays.Direct
private const float height = 70;
private FillFlowContainer statusContainer;
protected PanelDownloadButton DownloadButton;
protected BeatmapPanelDownloadButton DownloadButton;
private PlayButton playButton;
private Box progressBar;
@@ -36,7 +36,7 @@ namespace osu.Game.Overlays.Direct
protected override PlayButton PlayButton => playButton;
protected override Box PreviewBar => progressBar;
public DirectListPanel(BeatmapSetInfo beatmap)
public ListBeatmapPanel(BeatmapSetInfo beatmap)
: base(beatmap)
{
RelativeSizeAxes = Axes.X;
@@ -151,7 +151,7 @@ namespace osu.Game.Overlays.Direct
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
AutoSizeAxes = Axes.Both,
Child = DownloadButton = new PanelDownloadButton(SetInfo)
Child = DownloadButton = new BeatmapPanelDownloadButton(SetInfo)
{
Size = new Vector2(height - vertical_padding * 3),
Margin = new MarginPadding { Left = vertical_padding * 2, Right = vertical_padding },
@@ -14,7 +14,7 @@ using osu.Game.Graphics.UserInterface;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Overlays.Direct
namespace osu.Game.Overlays.BeatmapListing.Panels
{
public class PlayButton : Container
{
@@ -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,
}
}
@@ -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
}
}

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