mirror of
https://github.com/ppy/osu.git
synced 2024-09-21 15:27:24 +08:00
Merge remote-tracking branch 'upstream/master' into test-scene-create-ruleset
This commit is contained in:
commit
227503c0af
@ -52,6 +52,6 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.412.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.412.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.411.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.421.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
132
osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs
Normal file
132
osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs
Normal file
@ -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)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -71,8 +71,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
|||||||
|
|
||||||
protected override Skill[] CreateSkills(IBeatmap beatmap)
|
protected override Skill[] CreateSkills(IBeatmap beatmap)
|
||||||
{
|
{
|
||||||
using (var catcher = new Catcher(beatmap.BeatmapInfo.BaseDifficulty))
|
halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.BeatmapInfo.BaseDifficulty) * 0.5f;
|
||||||
halfCatcherWidth = catcher.CatchWidth * 0.5f;
|
|
||||||
|
|
||||||
return new Skill[]
|
return new Skill[]
|
||||||
{
|
{
|
||||||
|
@ -9,17 +9,26 @@ using osu.Game.Rulesets.Catch.Objects;
|
|||||||
using osu.Game.Rulesets.Catch.UI;
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Screens.Play;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Mods
|
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.";
|
public override string Description => @"Use the mouse to control the catcher.";
|
||||||
|
|
||||||
|
private DrawableRuleset<CatchHitObject> drawableRuleset;
|
||||||
|
|
||||||
public void ApplyToDrawableRuleset(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
|
private class MouseInputHelper : Drawable, IKeyBindingHandler<CatchAction>, IRequireHighFrequencyMousePosition
|
||||||
|
@ -7,6 +7,7 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
@ -67,7 +68,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
BorderColour = Color4.Red,
|
BorderColour = Catcher.DEFAULT_HYPER_DASH_COLOUR,
|
||||||
BorderThickness = 12f * RADIUS_ADJUST,
|
BorderThickness = 12f * RADIUS_ADJUST,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
@ -77,7 +78,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
Alpha = 0.3f,
|
Alpha = 0.3f,
|
||||||
Blending = BlendingParameters.Additive,
|
Blending = BlendingParameters.Additive,
|
||||||
RelativeSizeAxes = Axes.Both,
|
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 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
23
osu.Game.Rulesets.Catch/Skinning/CatchSkinColour.cs
Normal file
23
osu.Game.Rulesets.Catch/Skinning/CatchSkinColour.cs
Normal file
@ -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.Containers;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -55,14 +56,16 @@ namespace osu.Game.Rulesets.Catch.Skinning
|
|||||||
{
|
{
|
||||||
var hyperDash = new Sprite
|
var hyperDash = new Sprite
|
||||||
{
|
{
|
||||||
Texture = skin.GetTexture(lookupName),
|
|
||||||
Colour = Color4.Red,
|
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Blending = BlendingParameters.Additive,
|
Blending = BlendingParameters.Additive,
|
||||||
Depth = 1,
|
Depth = 1,
|
||||||
Alpha = 0.7f,
|
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);
|
AddInternal(hyperDash);
|
||||||
|
@ -21,6 +21,8 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
{
|
{
|
||||||
public class Catcher : Container, IKeyBindingHandler<CatchAction>
|
public class Catcher : Container, IKeyBindingHandler<CatchAction>
|
||||||
{
|
{
|
||||||
|
public static readonly Color4 DEFAULT_HYPER_DASH_COLOUR = Color4.Red;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether we are hyper-dashing or not.
|
/// Whether we are hyper-dashing or not.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -42,11 +44,6 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private const float allowed_catch_range = 0.8f;
|
private const float allowed_catch_range = 0.8f;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Width of the area that can be used to attempt catches during gameplay.
|
|
||||||
/// </summary>
|
|
||||||
internal float CatchWidth => CatcherArea.CATCHER_SIZE * Math.Abs(Scale.X) * allowed_catch_range;
|
|
||||||
|
|
||||||
protected bool Dashing
|
protected bool Dashing
|
||||||
{
|
{
|
||||||
get => dashing;
|
get => dashing;
|
||||||
@ -77,6 +74,11 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Width of the area that can be used to attempt catches during gameplay.
|
||||||
|
/// </summary>
|
||||||
|
private readonly float catchWidth;
|
||||||
|
|
||||||
private Container<DrawableHitObject> caughtFruit;
|
private Container<DrawableHitObject> caughtFruit;
|
||||||
|
|
||||||
private CatcherSprite catcherIdle;
|
private CatcherSprite catcherIdle;
|
||||||
@ -104,7 +106,9 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
Size = new Vector2(CatcherArea.CATCHER_SIZE);
|
Size = new Vector2(CatcherArea.CATCHER_SIZE);
|
||||||
if (difficulty != null)
|
if (difficulty != null)
|
||||||
Scale = new Vector2(1.0f - 0.7f * (difficulty.CircleSize - 5) / 5);
|
Scale = calculateScale(difficulty);
|
||||||
|
|
||||||
|
catchWidth = CalculateCatchWidth(Scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -137,6 +141,26 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
updateCatcher();
|
updateCatcher();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculates the scale of the catcher based off the provided beatmap difficulty.
|
||||||
|
/// </summary>
|
||||||
|
private static Vector2 calculateScale(BeatmapDifficulty difficulty)
|
||||||
|
=> new Vector2(1.0f - 0.7f * (difficulty.CircleSize - 5) / 5);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculates the width of the area used for attempting catches in gameplay.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="scale">The scale of the catcher.</param>
|
||||||
|
internal static float CalculateCatchWidth(Vector2 scale)
|
||||||
|
=> CatcherArea.CATCHER_SIZE * Math.Abs(scale.X) * allowed_catch_range;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculates the width of the area used for attempting catches in gameplay.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="difficulty">The beatmap difficulty.</param>
|
||||||
|
internal static float CalculateCatchWidth(BeatmapDifficulty difficulty)
|
||||||
|
=> CalculateCatchWidth(calculateScale(difficulty));
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Add a caught fruit to the catcher's stack.
|
/// Add a caught fruit to the catcher's stack.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -175,7 +199,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
/// <returns>Whether the catch is possible.</returns>
|
/// <returns>Whether the catch is possible.</returns>
|
||||||
public bool AttemptCatch(CatchHitObject fruit)
|
public bool AttemptCatch(CatchHitObject fruit)
|
||||||
{
|
{
|
||||||
var halfCatchWidth = CatchWidth * 0.5f;
|
var halfCatchWidth = catchWidth * 0.5f;
|
||||||
|
|
||||||
// this stuff wil disappear once we move fruit to non-relative coordinate space in the future.
|
// this stuff wil disappear once we move fruit to non-relative coordinate space in the future.
|
||||||
var catchObjectPosition = fruit.X * CatchPlayfield.BASE_WIDTH;
|
var catchObjectPosition = fruit.X * CatchPlayfield.BASE_WIDTH;
|
||||||
|
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,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Width = 0.5f,
|
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
|
RelativeSizeAxes = Axes.Both
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,6 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
private const double time_after_tail = 5250;
|
private const double time_after_tail = 5250;
|
||||||
|
|
||||||
private List<JudgementResult> judgementResults;
|
private List<JudgementResult> judgementResults;
|
||||||
private bool allJudgedFired;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// -----[ ]-----
|
/// -----[ ]-----
|
||||||
@ -283,20 +282,15 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
{
|
{
|
||||||
if (currentPlayer == p) judgementResults.Add(result);
|
if (currentPlayer == p) judgementResults.Add(result);
|
||||||
};
|
};
|
||||||
p.ScoreProcessor.AllJudged += () =>
|
|
||||||
{
|
|
||||||
if (currentPlayer == p) allJudgedFired = true;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
LoadScreen(currentPlayer = p);
|
LoadScreen(currentPlayer = p);
|
||||||
allJudgedFired = false;
|
|
||||||
judgementResults = new List<JudgementResult>();
|
judgementResults = new List<JudgementResult>();
|
||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
|
AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
|
||||||
AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
|
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
|
private class ScoreAccessibleReplayPlayer : ReplayPlayer
|
||||||
|
@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
|||||||
{
|
{
|
||||||
TargetColumns = (int)Math.Max(1, roundedCircleSize);
|
TargetColumns = (int)Math.Max(1, roundedCircleSize);
|
||||||
|
|
||||||
if (TargetColumns >= 10)
|
if (TargetColumns > ManiaRuleset.MAX_STAGE_KEYS)
|
||||||
{
|
{
|
||||||
TargetColumns /= 2;
|
TargetColumns /= 2;
|
||||||
Dual = true;
|
Dual = true;
|
||||||
|
64
osu.Game.Rulesets.Mania/DualStageVariantGenerator.cs
Normal file
64
osu.Game.Rulesets.Mania/DualStageVariantGenerator.cs
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Input.Bindings;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania
|
||||||
|
{
|
||||||
|
public class DualStageVariantGenerator
|
||||||
|
{
|
||||||
|
private readonly int singleStageVariant;
|
||||||
|
private readonly InputKey[] stage1LeftKeys;
|
||||||
|
private readonly InputKey[] stage1RightKeys;
|
||||||
|
private readonly InputKey[] stage2LeftKeys;
|
||||||
|
private readonly InputKey[] stage2RightKeys;
|
||||||
|
|
||||||
|
public DualStageVariantGenerator(int singleStageVariant)
|
||||||
|
{
|
||||||
|
this.singleStageVariant = singleStageVariant;
|
||||||
|
|
||||||
|
// 10K is special because it expands towards the centre of the keyboard (VM/BN), rather than towards the edges of the keyboard.
|
||||||
|
if (singleStageVariant == 10)
|
||||||
|
{
|
||||||
|
stage1LeftKeys = new[] { InputKey.Q, InputKey.W, InputKey.E, InputKey.R, InputKey.V };
|
||||||
|
stage1RightKeys = new[] { InputKey.M, InputKey.I, InputKey.O, InputKey.P, InputKey.BracketLeft };
|
||||||
|
|
||||||
|
stage2LeftKeys = new[] { InputKey.S, InputKey.D, InputKey.F, InputKey.G, InputKey.B };
|
||||||
|
stage2RightKeys = new[] { InputKey.N, InputKey.J, InputKey.K, InputKey.L, InputKey.Semicolon };
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
stage1LeftKeys = new[] { InputKey.Q, InputKey.W, InputKey.E, InputKey.R };
|
||||||
|
stage1RightKeys = new[] { InputKey.I, InputKey.O, InputKey.P, InputKey.BracketLeft };
|
||||||
|
|
||||||
|
stage2LeftKeys = new[] { InputKey.S, InputKey.D, InputKey.F, InputKey.G };
|
||||||
|
stage2RightKeys = new[] { InputKey.J, InputKey.K, InputKey.L, InputKey.Semicolon };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<KeyBinding> GenerateMappings()
|
||||||
|
{
|
||||||
|
var stage1Bindings = new VariantMappingGenerator
|
||||||
|
{
|
||||||
|
LeftKeys = stage1LeftKeys,
|
||||||
|
RightKeys = stage1RightKeys,
|
||||||
|
SpecialKey = InputKey.V,
|
||||||
|
SpecialAction = ManiaAction.Special1,
|
||||||
|
NormalActionStart = ManiaAction.Key1
|
||||||
|
}.GenerateKeyBindingsFor(singleStageVariant, out var nextNormal);
|
||||||
|
|
||||||
|
var stage2Bindings = new VariantMappingGenerator
|
||||||
|
{
|
||||||
|
LeftKeys = stage2LeftKeys,
|
||||||
|
RightKeys = stage2RightKeys,
|
||||||
|
SpecialKey = InputKey.B,
|
||||||
|
SpecialAction = ManiaAction.Special2,
|
||||||
|
NormalActionStart = nextNormal
|
||||||
|
}.GenerateKeyBindingsFor(singleStageVariant, out _);
|
||||||
|
|
||||||
|
return stage1Bindings.Concat(stage2Bindings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -78,5 +78,11 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
|
|
||||||
[Description("Key 18")]
|
[Description("Key 18")]
|
||||||
Key18,
|
Key18,
|
||||||
|
|
||||||
|
[Description("Key 19")]
|
||||||
|
Key19,
|
||||||
|
|
||||||
|
[Description("Key 20")]
|
||||||
|
Key20,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,11 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
{
|
{
|
||||||
public class ManiaRuleset : Ruleset, ILegacyRuleset
|
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 DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => new DrawableManiaRuleset(this, beatmap, mods);
|
||||||
|
|
||||||
public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor();
|
public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor();
|
||||||
@ -202,6 +207,7 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
new ManiaModKey7(),
|
new ManiaModKey7(),
|
||||||
new ManiaModKey8(),
|
new ManiaModKey8(),
|
||||||
new ManiaModKey9(),
|
new ManiaModKey9(),
|
||||||
|
new ManiaModKey10(),
|
||||||
new ManiaModKey1(),
|
new ManiaModKey1(),
|
||||||
new ManiaModKey2(),
|
new ManiaModKey2(),
|
||||||
new ManiaModKey3()),
|
new ManiaModKey3()),
|
||||||
@ -250,9 +256,9 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
for (int i = 1; i <= 9; i++)
|
for (int i = 1; i <= MAX_STAGE_KEYS; i++)
|
||||||
yield return (int)PlayfieldType.Single + 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;
|
yield return (int)PlayfieldType.Dual + i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -262,73 +268,10 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
switch (getPlayfieldType(variant))
|
switch (getPlayfieldType(variant))
|
||||||
{
|
{
|
||||||
case PlayfieldType.Single:
|
case PlayfieldType.Single:
|
||||||
return new VariantMappingGenerator
|
return new SingleStageVariantGenerator(variant).GenerateMappings();
|
||||||
{
|
|
||||||
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 _);
|
|
||||||
|
|
||||||
case PlayfieldType.Dual:
|
case PlayfieldType.Dual:
|
||||||
int keys = getDualStageKeyCount(variant);
|
return new DualStageVariantGenerator(getDualStageKeyCount(variant)).GenerateMappings();
|
||||||
|
|
||||||
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 Array.Empty<KeyBinding>();
|
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);
|
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
|
public enum PlayfieldType
|
||||||
|
13
osu.Game.Rulesets.Mania/Mods/ManiaModKey10.cs
Normal file
13
osu.Game.Rulesets.Mania/Mods/ManiaModKey10.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Mods
|
||||||
|
{
|
||||||
|
public class ManiaModKey10 : ManiaKeyMod
|
||||||
|
{
|
||||||
|
public override int KeyCount => 10;
|
||||||
|
public override string Name => "Ten Keys";
|
||||||
|
public override string Acronym => "10K";
|
||||||
|
public override string Description => @"Play with ten keys.";
|
||||||
|
}
|
||||||
|
}
|
41
osu.Game.Rulesets.Mania/SingleStageVariantGenerator.cs
Normal file
41
osu.Game.Rulesets.Mania/SingleStageVariantGenerator.cs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using osu.Framework.Input.Bindings;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania
|
||||||
|
{
|
||||||
|
public class SingleStageVariantGenerator
|
||||||
|
{
|
||||||
|
private readonly int variant;
|
||||||
|
private readonly InputKey[] leftKeys;
|
||||||
|
private readonly InputKey[] rightKeys;
|
||||||
|
|
||||||
|
public SingleStageVariantGenerator(int variant)
|
||||||
|
{
|
||||||
|
this.variant = variant;
|
||||||
|
|
||||||
|
// 10K is special because it expands towards the centre of the keyboard (V/N), rather than towards the edges of the keyboard.
|
||||||
|
if (variant == 10)
|
||||||
|
{
|
||||||
|
leftKeys = new[] { InputKey.A, InputKey.S, InputKey.D, InputKey.F, InputKey.V };
|
||||||
|
rightKeys = new[] { InputKey.N, InputKey.J, InputKey.K, InputKey.L, InputKey.Semicolon };
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
leftKeys = new[] { InputKey.A, InputKey.S, InputKey.D, InputKey.F };
|
||||||
|
rightKeys = new[] { InputKey.J, InputKey.K, InputKey.L, InputKey.Semicolon };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<KeyBinding> GenerateMappings() => new VariantMappingGenerator
|
||||||
|
{
|
||||||
|
LeftKeys = leftKeys,
|
||||||
|
RightKeys = rightKeys,
|
||||||
|
SpecialKey = InputKey.Space,
|
||||||
|
SpecialAction = ManiaAction.Special1,
|
||||||
|
NormalActionStart = ManiaAction.Key1,
|
||||||
|
}.GenerateKeyBindingsFor(variant, out _);
|
||||||
|
}
|
||||||
|
}
|
@ -67,6 +67,7 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Y,
|
RelativeSizeAxes = Axes.Y,
|
||||||
Width = leftLineWidth,
|
Width = leftLineWidth,
|
||||||
|
Scale = new Vector2(0.740f, 1),
|
||||||
Colour = lineColour,
|
Colour = lineColour,
|
||||||
Alpha = hasLeftLine ? 1 : 0
|
Alpha = hasLeftLine ? 1 : 0
|
||||||
},
|
},
|
||||||
@ -76,6 +77,7 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
|||||||
Origin = Anchor.TopRight,
|
Origin = Anchor.TopRight,
|
||||||
RelativeSizeAxes = Axes.Y,
|
RelativeSizeAxes = Axes.Y,
|
||||||
Width = rightLineWidth,
|
Width = rightLineWidth,
|
||||||
|
Scale = new Vector2(0.740f, 1),
|
||||||
Colour = lineColour,
|
Colour = lineColour,
|
||||||
Alpha = hasRightLine ? 1 : 0
|
Alpha = hasRightLine ? 1 : 0
|
||||||
},
|
},
|
||||||
|
61
osu.Game.Rulesets.Mania/VariantMappingGenerator.cs
Normal file
61
osu.Game.Rulesets.Mania/VariantMappingGenerator.cs
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using osu.Framework.Input.Bindings;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania
|
||||||
|
{
|
||||||
|
public class VariantMappingGenerator
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// All the <see cref="InputKey"/>s available to the left hand.
|
||||||
|
/// </summary>
|
||||||
|
public InputKey[] LeftKeys;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// All the <see cref="InputKey"/>s available to the right hand.
|
||||||
|
/// </summary>
|
||||||
|
public InputKey[] RightKeys;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="InputKey"/> for the special key.
|
||||||
|
/// </summary>
|
||||||
|
public InputKey SpecialKey;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="ManiaAction"/> at which the normal columns should begin.
|
||||||
|
/// </summary>
|
||||||
|
public ManiaAction NormalActionStart;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="ManiaAction"/> for the special column.
|
||||||
|
/// </summary>
|
||||||
|
public ManiaAction SpecialAction;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a list of <see cref="KeyBinding"/>s for a specific number of columns.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="columns">The number of columns that need to be bound.</param>
|
||||||
|
/// <param name="nextNormalAction">The next <see cref="ManiaAction"/> to use for normal columns.</param>
|
||||||
|
/// <returns>The keybindings.</returns>
|
||||||
|
public IEnumerable<KeyBinding> GenerateKeyBindingsFor(int columns, out ManiaAction nextNormalAction)
|
||||||
|
{
|
||||||
|
ManiaAction currentNormalAction = NormalActionStart;
|
||||||
|
|
||||||
|
var bindings = new List<KeyBinding>();
|
||||||
|
|
||||||
|
for (int i = LeftKeys.Length - columns / 2; i < LeftKeys.Length; i++)
|
||||||
|
bindings.Add(new KeyBinding(LeftKeys[i], currentNormalAction++));
|
||||||
|
|
||||||
|
if (columns % 2 == 1)
|
||||||
|
bindings.Add(new KeyBinding(SpecialKey, SpecialAction));
|
||||||
|
|
||||||
|
for (int i = 0; i < columns / 2; i++)
|
||||||
|
bindings.Add(new KeyBinding(RightKeys[i], currentNormalAction++));
|
||||||
|
|
||||||
|
nextNormalAction = currentNormalAction;
|
||||||
|
return bindings;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
103
osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs
Normal file
103
osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
// 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
|
||||||
|
{
|
||||||
|
protected override Ruleset CreatePlayerRuleset() => 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);
|
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)
|
private void addJudgementAssert(OsuHitObject hitObject, HitResult result)
|
||||||
{
|
{
|
||||||
AddAssert($"({hitObject.GetType().ReadableName()} @ {hitObject.StartTime}) judgement is {result}",
|
AddAssert($"({hitObject.GetType().ReadableName()} @ {hitObject.StartTime}) judgement is {result}",
|
||||||
@ -316,7 +354,6 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
|
|
||||||
private ScoreAccessibleReplayPlayer currentPlayer;
|
private ScoreAccessibleReplayPlayer currentPlayer;
|
||||||
private List<JudgementResult> judgementResults;
|
private List<JudgementResult> judgementResults;
|
||||||
private bool allJudgedFired;
|
|
||||||
|
|
||||||
private void performTest(List<OsuHitObject> hitObjects, List<ReplayFrame> frames)
|
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);
|
if (currentPlayer == p) judgementResults.Add(result);
|
||||||
};
|
};
|
||||||
p.ScoreProcessor.AllJudged += () =>
|
|
||||||
{
|
|
||||||
if (currentPlayer == p) allJudgedFired = true;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
LoadScreen(currentPlayer = p);
|
LoadScreen(currentPlayer = p);
|
||||||
allJudgedFired = false;
|
|
||||||
judgementResults = new List<JudgementResult>();
|
judgementResults = new List<JudgementResult>();
|
||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
|
AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
|
||||||
AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
|
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
|
private class TestHitCircle : HitCircle
|
||||||
@ -371,6 +403,9 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
HeadCircle.HitWindows = new TestHitWindows();
|
HeadCircle.HitWindows = new TestHitWindows();
|
||||||
TailCircle.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 const double time_slider_end = 4000;
|
||||||
|
|
||||||
private List<JudgementResult> judgementResults;
|
private List<JudgementResult> judgementResults;
|
||||||
private bool allJudgedFired;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Scenario:
|
/// Scenario:
|
||||||
@ -375,20 +374,15 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
if (currentPlayer == p) judgementResults.Add(result);
|
if (currentPlayer == p) judgementResults.Add(result);
|
||||||
};
|
};
|
||||||
p.ScoreProcessor.AllJudged += () =>
|
|
||||||
{
|
|
||||||
if (currentPlayer == p) allJudgedFired = true;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
LoadScreen(currentPlayer = p);
|
LoadScreen(currentPlayer = p);
|
||||||
allJudgedFired = false;
|
|
||||||
judgementResults = new List<JudgementResult>();
|
judgementResults = new List<JudgementResult>();
|
||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
|
AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
|
||||||
AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
|
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
|
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_in_duration_multiplier = 0.4;
|
||||||
private const double fade_out_duration_multiplier = 0.3;
|
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)
|
public override void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
|
||||||
{
|
{
|
||||||
static void adjustFadeIn(OsuHitObject h) => h.TimeFadeIn = h.TimePreempt * fade_in_duration_multiplier;
|
static void adjustFadeIn(OsuHitObject h) => h.TimeFadeIn = h.TimePreempt * fade_in_duration_multiplier;
|
||||||
|
@ -11,11 +11,12 @@ using osu.Game.Rulesets.Osu.Objects;
|
|||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Replays;
|
using osu.Game.Rulesets.Replays;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Screens.Play;
|
||||||
using static osu.Game.Input.Handlers.ReplayInputHandler;
|
using static osu.Game.Input.Handlers.ReplayInputHandler;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Mods
|
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 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();
|
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 ReplayState<OsuAction> state;
|
||||||
private double lastStateChangeTime;
|
private double lastStateChangeTime;
|
||||||
|
|
||||||
|
private bool hasReplay;
|
||||||
|
|
||||||
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
||||||
{
|
{
|
||||||
// grab the input manager for future use.
|
// grab the input manager for future use.
|
||||||
osuInputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager;
|
osuInputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ApplyToPlayer(Player player)
|
||||||
|
{
|
||||||
|
if (osuInputManager.ReplayInputHandler != null)
|
||||||
|
{
|
||||||
|
hasReplay = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
osuInputManager.AllowUserPresses = false;
|
osuInputManager.AllowUserPresses = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Update(Playfield playfield)
|
public void Update(Playfield playfield)
|
||||||
{
|
{
|
||||||
|
if (hasReplay)
|
||||||
|
return;
|
||||||
|
|
||||||
bool requiresHold = false;
|
bool requiresHold = false;
|
||||||
bool requiresHit = false;
|
bool requiresHit = false;
|
||||||
|
|
||||||
|
@ -125,7 +125,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
return new DrawableSliderTail(slider, tail);
|
return new DrawableSliderTail(slider, tail);
|
||||||
|
|
||||||
case SliderHeadCircle head:
|
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:
|
case SliderTick tick:
|
||||||
return new DrawableSliderTick(tick) { Position = tick.Position - slider.Position };
|
return new DrawableSliderTick(tick) { Position = tick.Position - slider.Position };
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// 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;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.UI
|
namespace osu.Game.Rulesets.Osu.UI
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <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:
|
/// If a <see cref="HitObject"/> is hit out of order:
|
||||||
/// <list type="number">
|
/// <list type="number">
|
||||||
/// <item><description>The hit is blocked if it occurred earlier than the previous <see cref="HitObject"/>'s start time.</description></item>
|
/// <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;
|
DrawableHitObject blockingObject = null;
|
||||||
|
|
||||||
// Find the last hitobject which blocks future hits.
|
foreach (var obj in enumerateHitObjectsUpTo(hitObject.HitObject.StartTime))
|
||||||
foreach (var obj in hitObjectContainer.AliveObjects)
|
|
||||||
{
|
{
|
||||||
if (obj == hitObject)
|
if (hitObjectCanBlockFutureHits(obj))
|
||||||
break;
|
|
||||||
|
|
||||||
if (drawableCanBlockFutureHits(obj))
|
|
||||||
blockingObject = obj;
|
blockingObject = obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,74 +51,56 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
// 1. The last blocking hitobject has been judged.
|
// 1. The last blocking hitobject has been judged.
|
||||||
// 2. The current time is after the last hitobject's start time.
|
// 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).
|
// 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 blockingObject.Judged || time >= blockingObject.HitObject.StartTime;
|
||||||
return true;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handles a <see cref="HitObject"/> being hit to potentially miss all earlier <see cref="HitObject"/>s.
|
/// Handles a <see cref="HitObject"/> being hit to potentially miss all earlier <see cref="HitObject"/>s.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="hitObject">The <see cref="HitObject"/> that was hit.</param>
|
/// <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).
|
// Hitobjects which themselves don't block future hitobjects don't cause misses (e.g. slider ticks, spinners).
|
||||||
if (!hitObjectCanBlockFutureHits(hitObject))
|
if (!hitObjectCanBlockFutureHits(hitObject))
|
||||||
return;
|
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 enumerateHitObjectsUpTo(hitObject.HitObject.StartTime))
|
||||||
foreach (var obj in hitObjectContainer.AliveObjects)
|
|
||||||
{
|
{
|
||||||
if (obj.Judged || obj.HitObject.StartTime >= maximumTime)
|
if (obj.Judged)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (hitObjectCanBlockFutureHits(obj.HitObject))
|
if (hitObjectCanBlockFutureHits(obj))
|
||||||
applyMiss(obj);
|
((DrawableOsuHitObject)obj).MissForcefully();
|
||||||
|
|
||||||
foreach (var nested in obj.NestedHitObjects)
|
|
||||||
{
|
|
||||||
if (nested.Judged || nested.HitObject.StartTime >= maximumTime)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (hitObjectCanBlockFutureHits(nested.HitObject))
|
|
||||||
applyMiss(nested);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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>
|
/// <summary>
|
||||||
/// Whether a <see cref="HitObject"/> blocks hits on future <see cref="HitObject"/>s until its start time is reached.
|
/// Whether a <see cref="HitObject"/> blocks hits on future <see cref="HitObject"/>s until its start time is reached.
|
||||||
/// </summary>
|
/// </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>
|
/// <param name="hitObject">The <see cref="HitObject"/> to test.</param>
|
||||||
private static bool hitObjectCanBlockFutureHits(HitObject hitObject)
|
private static bool hitObjectCanBlockFutureHits(DrawableHitObject hitObject)
|
||||||
{
|
=> hitObject is DrawableHitCircle;
|
||||||
// Unlike the above we will receive slider tails, but they do not block future hits.
|
|
||||||
if (hitObject is SliderTailCircle)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// All other hitcircles continue to block future hits.
|
private IEnumerable<DrawableHitObject> enumerateHitObjectsUpTo(double targetTime)
|
||||||
return hitObject is HitCircle;
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
||||||
{
|
{
|
||||||
// Hitobjects that block future hits should miss previous hitobjects if they're hit out-of-order.
|
// 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)
|
if (!judgedObject.DisplayResult || !DisplayJudgements.Value)
|
||||||
return;
|
return;
|
||||||
|
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
BIN
osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/approachcircle.png
Executable file
BIN
osu.Game.Rulesets.Taiko.Tests/Resources/old-skin/approachcircle.png
Executable file
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.Rulesets.Taiko.Skinning;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Tests
|
namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||||
{
|
{
|
||||||
public abstract class TaikoSkinnableTestScene : SkinnableTestScene
|
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.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Taiko.Skinning;
|
using osu.Game.Rulesets.Taiko.Skinning;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Tests
|
namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestSceneDrawableHit : TaikoSkinnableTestScene
|
public class TestSceneDrawableHit : TaikoSkinnableTestScene
|
||||||
@ -24,6 +24,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
|||||||
typeof(DrawableCentreHit),
|
typeof(DrawableCentreHit),
|
||||||
typeof(DrawableRimHit),
|
typeof(DrawableRimHit),
|
||||||
typeof(LegacyHit),
|
typeof(LegacyHit),
|
||||||
|
typeof(LegacyCirclePiece),
|
||||||
}).ToList();
|
}).ToList();
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
@ -6,14 +6,14 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osuTK;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Rulesets.Taiko.Skinning;
|
using osu.Game.Rulesets.Taiko.Skinning;
|
||||||
using osu.Game.Rulesets.Taiko.UI;
|
using osu.Game.Rulesets.Taiko.UI;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Tests
|
namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestSceneInputDrum : TaikoSkinnableTestScene
|
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
|
namespace osu.Game.Rulesets.Taiko.Tests
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestSceneTaikoPlayfield : OsuTestScene
|
public class TestSceneHits : OsuTestScene
|
||||||
{
|
{
|
||||||
private const double default_duration = 1000;
|
private const double default_duration = 1000;
|
||||||
private const float scroll_time = 1000;
|
private const float scroll_time = 1000;
|
@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestZeroTickTimeOffsets()
|
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));
|
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.
|
// 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.
|
// 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.Rulesets.Taiko.Objects.Drawables.Pieces;
|
||||||
using osu.Game.Skinning;
|
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);
|
_ => new CentreHitCirclePiece(), confineMode: ConfineMode.ScaleToFit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,8 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||||
{
|
{
|
||||||
@ -29,25 +31,29 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private int rollingHits;
|
private int rollingHits;
|
||||||
|
|
||||||
private readonly Container<DrawableDrumRollTick> tickContainer;
|
private Container tickContainer;
|
||||||
|
|
||||||
private Color4 colourIdle;
|
private Color4 colourIdle;
|
||||||
private Color4 colourEngaged;
|
private Color4 colourEngaged;
|
||||||
|
|
||||||
private ElongatedCirclePiece elongatedPiece;
|
|
||||||
|
|
||||||
public DrawableDrumRoll(DrumRoll drumRoll)
|
public DrawableDrumRoll(DrumRoll drumRoll)
|
||||||
: base(drumRoll)
|
: base(drumRoll)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Y;
|
RelativeSizeAxes = Axes.Y;
|
||||||
elongatedPiece.Add(tickContainer = new Container<DrawableDrumRollTick> { RelativeSizeAxes = Axes.Both });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load(OsuColour colours)
|
||||||
{
|
{
|
||||||
elongatedPiece.AccentColour = colourIdle = colours.YellowDark;
|
colourIdle = colours.YellowDark;
|
||||||
colourEngaged = colours.YellowDarker;
|
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()
|
protected override void LoadComplete()
|
||||||
@ -86,7 +92,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
return base.CreateNestedHitObject(hitObject);
|
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;
|
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);
|
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);
|
updateColour();
|
||||||
(MainPiece as IHasAccentColour)?.FadeAccent(newColour, 100);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
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);
|
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
|
private class StrongNestedHit : DrawableStrongNestedHit
|
||||||
{
|
{
|
||||||
public StrongNestedHit(StrongHitObject strong, DrawableDrumRoll drumRoll)
|
public StrongNestedHit(StrongHitObject strong, DrawableDrumRoll drumRoll)
|
||||||
|
@ -3,10 +3,10 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
|
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||||
{
|
{
|
||||||
@ -20,10 +20,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
|
|
||||||
public override bool DisplayResult => false;
|
public override bool DisplayResult => false;
|
||||||
|
|
||||||
protected override CompositeDrawable CreateMainPiece() => new TickPiece
|
protected override SkinnableDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.DrumRollTick),
|
||||||
{
|
_ => new TickPiece
|
||||||
Filled = HitObject.FirstTick
|
{
|
||||||
};
|
Filled = HitObject.FirstTick
|
||||||
|
});
|
||||||
|
|
||||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
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
|
// If we're far enough away from the left stage, we should bring outselves in front of it
|
||||||
ProxyContent();
|
ProxyContent();
|
||||||
|
|
||||||
var flash = (MainPiece as CirclePiece)?.FlashBox;
|
var flash = (MainPiece.Drawable as CirclePiece)?.FlashBox;
|
||||||
flash?.FadeTo(0.9f).FadeOut(300);
|
flash?.FadeTo(0.9f).FadeOut(300);
|
||||||
|
|
||||||
const float gravity_time = 300;
|
const float gravity_time = 300;
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// 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.Rulesets.Taiko.Objects.Drawables.Pieces;
|
||||||
using osu.Game.Skinning;
|
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);
|
_ => new RimHitCirclePiece(), confineMode: ConfineMode.ScaleToFit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ using osu.Framework.Graphics.Shapes;
|
|||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
|
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
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);
|
targetRing.BorderColour = colours.YellowDark.Opacity(0.25f);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override CompositeDrawable CreateMainPiece() => new SwellCirclePiece
|
protected override SkinnableDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.Swell),
|
||||||
{
|
_ => new SwellCirclePiece
|
||||||
// to allow for rotation transform
|
{
|
||||||
Anchor = Anchor.Centre,
|
// to allow for rotation transform
|
||||||
Origin = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
};
|
Origin = Anchor.Centre,
|
||||||
|
});
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
@ -184,7 +186,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
.Then()
|
.Then()
|
||||||
.FadeTo(completion / 8, 2000, Easing.OutQuint);
|
.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);
|
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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
|
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
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;
|
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.Containers;
|
||||||
using osu.Framework.Graphics.Primitives;
|
using osu.Framework.Graphics.Primitives;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||||
{
|
{
|
||||||
@ -115,7 +116,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
public new TObject HitObject;
|
public new TObject HitObject;
|
||||||
|
|
||||||
protected readonly Vector2 BaseSize;
|
protected readonly Vector2 BaseSize;
|
||||||
protected readonly CompositeDrawable MainPiece;
|
protected readonly SkinnableDrawable MainPiece;
|
||||||
|
|
||||||
private readonly Container<DrawableStrongNestedHit> strongHitContainer;
|
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
|
// 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 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>
|
/// <summary>
|
||||||
/// Creates the handler for this <see cref="DrawableHitObject"/>'s <see cref="StrongHitObject"/>.
|
/// 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.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Framework.Audio.Track;
|
using osu.Framework.Audio.Track;
|
||||||
using osu.Framework.Graphics.Effects;
|
using osu.Framework.Graphics.Effects;
|
||||||
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
|
namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
|
||||||
@ -21,7 +22,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
|
|||||||
/// for a usage example.
|
/// for a usage example.
|
||||||
/// </para>
|
/// </para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class CirclePiece : BeatSyncedContainer
|
public abstract class CirclePiece : BeatSyncedContainer, IHasAccentColour
|
||||||
{
|
{
|
||||||
public const float SYMBOL_SIZE = 0.45f;
|
public const float SYMBOL_SIZE = 0.45f;
|
||||||
public const float SYMBOL_BORDER = 8;
|
public const float SYMBOL_BORDER = 8;
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
|
namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
|
||||||
{
|
{
|
||||||
@ -12,18 +14,15 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
|
|||||||
RelativeSizeAxes = Axes.Y;
|
RelativeSizeAxes = Axes.Y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colours)
|
||||||
|
{
|
||||||
|
AccentColour = colours.YellowDark;
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
var padding = Content.DrawHeight * Content.Width / 2;
|
|
||||||
|
|
||||||
Content.Padding = new MarginPadding
|
|
||||||
{
|
|
||||||
Left = padding,
|
|
||||||
Right = padding,
|
|
||||||
};
|
|
||||||
|
|
||||||
Width = Parent.DrawSize.X + DrawHeight;
|
Width = Parent.DrawSize.X + DrawHeight;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
96
osu.Game.Rulesets.Taiko/Skinning/LegacyCirclePiece.cs
Normal file
96
osu.Game.Rulesets.Taiko/Skinning/LegacyCirclePiece.cs
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Animations;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Taiko.Objects.Drawables;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Skinning
|
||||||
|
{
|
||||||
|
public class LegacyCirclePiece : CompositeDrawable, IHasAccentColour
|
||||||
|
{
|
||||||
|
private Drawable backgroundLayer;
|
||||||
|
|
||||||
|
public LegacyCirclePiece()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(ISkinSource skin, DrawableHitObject drawableHitObject)
|
||||||
|
{
|
||||||
|
Drawable getDrawableFor(string lookup)
|
||||||
|
{
|
||||||
|
const string normal_hit = "taikohit";
|
||||||
|
const string big_hit = "taikobig";
|
||||||
|
|
||||||
|
string prefix = ((drawableHitObject as DrawableTaikoHitObject)?.HitObject.IsStrong ?? false) ? big_hit : normal_hit;
|
||||||
|
|
||||||
|
return skin.GetAnimation($"{prefix}{lookup}", true, false) ??
|
||||||
|
// fallback to regular size if "big" version doesn't exist.
|
||||||
|
skin.GetAnimation($"{normal_hit}{lookup}", true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// backgroundLayer is guaranteed to exist due to the pre-check in TaikoLegacySkinTransformer.
|
||||||
|
AddInternal(backgroundLayer = getDrawableFor("circle"));
|
||||||
|
|
||||||
|
var foregroundLayer = getDrawableFor("circleoverlay");
|
||||||
|
if (foregroundLayer != null)
|
||||||
|
AddInternal(foregroundLayer);
|
||||||
|
|
||||||
|
// Animations in taiko skins are used in a custom way (>150 combo and animating in time with beat).
|
||||||
|
// For now just stop at first frame for sanity.
|
||||||
|
foreach (var c in InternalChildren)
|
||||||
|
{
|
||||||
|
(c as IFramedAnimation)?.Stop();
|
||||||
|
|
||||||
|
c.Anchor = Anchor.Centre;
|
||||||
|
c.Origin = Anchor.Centre;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
updateAccentColour();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
// Not all skins (including the default osu-stable) have similar sizes for "hitcircle" and "hitcircleoverlay".
|
||||||
|
// This ensures they are scaled relative to each other but also match the expected DrawableHit size.
|
||||||
|
foreach (var c in InternalChildren)
|
||||||
|
c.Scale = new Vector2(DrawHeight / 128);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Color4 accentColour;
|
||||||
|
|
||||||
|
public Color4 AccentColour
|
||||||
|
{
|
||||||
|
get => accentColour;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == accentColour)
|
||||||
|
return;
|
||||||
|
|
||||||
|
accentColour = value;
|
||||||
|
if (IsLoaded)
|
||||||
|
updateAccentColour();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateAccentColour()
|
||||||
|
{
|
||||||
|
backgroundLayer.Colour = accentColour;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
83
osu.Game.Rulesets.Taiko/Skinning/LegacyDrumRoll.cs
Normal file
83
osu.Game.Rulesets.Taiko/Skinning/LegacyDrumRoll.cs
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Skinning
|
||||||
|
{
|
||||||
|
public class LegacyDrumRoll : CompositeDrawable, IHasAccentColour
|
||||||
|
{
|
||||||
|
private LegacyCirclePiece headCircle;
|
||||||
|
|
||||||
|
private Sprite body;
|
||||||
|
|
||||||
|
private Sprite end;
|
||||||
|
|
||||||
|
public LegacyDrumRoll()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(ISkinSource skin, OsuColour colours)
|
||||||
|
{
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
end = new Sprite
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreRight,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Texture = skin.GetTexture("taiko-roll-end"),
|
||||||
|
FillMode = FillMode.Fit,
|
||||||
|
},
|
||||||
|
body = new Sprite
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Texture = skin.GetTexture("taiko-roll-middle"),
|
||||||
|
},
|
||||||
|
headCircle = new LegacyCirclePiece
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
AccentColour = colours.YellowDark;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
updateAccentColour();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Color4 accentColour;
|
||||||
|
|
||||||
|
public Color4 AccentColour
|
||||||
|
{
|
||||||
|
get => accentColour;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == accentColour)
|
||||||
|
return;
|
||||||
|
|
||||||
|
accentColour = value;
|
||||||
|
if (IsLoaded)
|
||||||
|
updateAccentColour();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateAccentColour()
|
||||||
|
{
|
||||||
|
headCircle.AccentColour = accentColour;
|
||||||
|
body.Colour = accentColour;
|
||||||
|
end.Colour = accentColour;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,90 +2,25 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
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;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Skinning
|
namespace osu.Game.Rulesets.Taiko.Skinning
|
||||||
{
|
{
|
||||||
public class LegacyHit : CompositeDrawable, IHasAccentColour
|
public class LegacyHit : LegacyCirclePiece
|
||||||
{
|
{
|
||||||
private readonly TaikoSkinComponents component;
|
private readonly TaikoSkinComponents component;
|
||||||
|
|
||||||
private Drawable backgroundLayer;
|
|
||||||
|
|
||||||
public LegacyHit(TaikoSkinComponents component)
|
public LegacyHit(TaikoSkinComponents component)
|
||||||
{
|
{
|
||||||
this.component = component;
|
this.component = component;
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.Both;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[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
|
AccentColour = component == TaikoSkinComponents.CentreHit
|
||||||
? new Color4(235, 69, 44, 255)
|
? new Color4(235, 69, 44, 255)
|
||||||
: new Color4(67, 142, 172, 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
41
osu.Game.Rulesets.Taiko/Skinning/LegacyHitTarget.cs
Normal file
41
osu.Game.Rulesets.Taiko/Skinning/LegacyHitTarget.cs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using 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)
|
switch (taikoComponent.Component)
|
||||||
{
|
{
|
||||||
|
case TaikoSkinComponents.DrumRollBody:
|
||||||
|
if (GetTexture("taiko-roll-middle") != null)
|
||||||
|
return new LegacyDrumRoll();
|
||||||
|
|
||||||
|
return null;
|
||||||
|
|
||||||
case TaikoSkinComponents.InputDrum:
|
case TaikoSkinComponents.InputDrum:
|
||||||
if (GetTexture("taiko-bar-left") != null)
|
if (GetTexture("taiko-bar-left") != null)
|
||||||
return new LegacyInputDrum();
|
return new LegacyInputDrum();
|
||||||
@ -40,6 +46,15 @@ namespace osu.Game.Rulesets.Taiko.Skinning
|
|||||||
return new LegacyHit(taikoComponent.Component);
|
return new LegacyHit(taikoComponent.Component);
|
||||||
|
|
||||||
return null;
|
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);
|
return source.GetDrawableComponent(component);
|
||||||
|
@ -7,6 +7,10 @@ namespace osu.Game.Rulesets.Taiko
|
|||||||
{
|
{
|
||||||
InputDrum,
|
InputDrum,
|
||||||
CentreHit,
|
CentreHit,
|
||||||
RimHit
|
RimHit,
|
||||||
|
DrumRollBody,
|
||||||
|
DrumRollTick,
|
||||||
|
Swell,
|
||||||
|
HitTarget
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,8 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
|
|
||||||
public HitTarget()
|
public HitTarget()
|
||||||
{
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new Box
|
new Box
|
||||||
|
@ -17,6 +17,7 @@ using osu.Game.Rulesets.UI.Scrolling;
|
|||||||
using osu.Game.Rulesets.Taiko.Objects.Drawables;
|
using osu.Game.Rulesets.Taiko.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Taiko.Judgements;
|
using osu.Game.Rulesets.Taiko.Judgements;
|
||||||
using osu.Game.Rulesets.Taiko.Objects;
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
@ -42,7 +43,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
private readonly Container<HitExplosion> hitExplosionContainer;
|
private readonly Container<HitExplosion> hitExplosionContainer;
|
||||||
private readonly Container<KiaiHitExplosion> kiaiExplosionContainer;
|
private readonly Container<KiaiHitExplosion> kiaiExplosionContainer;
|
||||||
private readonly JudgementContainer<DrawableTaikoJudgement> judgementContainer;
|
private readonly JudgementContainer<DrawableTaikoJudgement> judgementContainer;
|
||||||
internal readonly HitTarget HitTarget;
|
internal readonly Drawable HitTarget;
|
||||||
|
|
||||||
private readonly ProxyContainer topLevelHitContainer;
|
private readonly ProxyContainer topLevelHitContainer;
|
||||||
private readonly ProxyContainer barlineContainer;
|
private readonly ProxyContainer barlineContainer;
|
||||||
@ -90,7 +91,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Padding = new MarginPadding { Left = HIT_TARGET_OFFSET },
|
Padding = new MarginPadding { Left = HIT_TARGET_OFFSET },
|
||||||
Masking = true,
|
Masking = true,
|
||||||
Children = new Drawable[]
|
Children = new[]
|
||||||
{
|
{
|
||||||
hitExplosionContainer = new Container<HitExplosion>
|
hitExplosionContainer = new Container<HitExplosion>
|
||||||
{
|
{
|
||||||
@ -98,7 +99,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
FillMode = FillMode.Fit,
|
FillMode = FillMode.Fit,
|
||||||
Blending = BlendingParameters.Additive,
|
Blending = BlendingParameters.Additive,
|
||||||
},
|
},
|
||||||
HitTarget = new HitTarget
|
HitTarget = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.HitTarget), _ => new HitTarget())
|
||||||
{
|
{
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
|
@ -241,6 +241,11 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
{
|
{
|
||||||
var controlPoints = decoder.Decode(stream).ControlPointInfo;
|
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(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(1500).SpeedMultiplier, Is.EqualTo(1.5).Within(0.1));
|
||||||
Assert.That(controlPoints.DifficultyPointAt(2500).SpeedMultiplier, Is.EqualTo(0.75).Within(0.1));
|
Assert.That(controlPoints.DifficultyPointAt(2500).SpeedMultiplier, Is.EqualTo(0.75).Within(0.1));
|
||||||
|
@ -1,14 +1,23 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Audio.Track;
|
||||||
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Beatmaps.Formats;
|
using osu.Game.Beatmaps.Formats;
|
||||||
using osu.Game.IO;
|
using osu.Game.IO;
|
||||||
using osu.Game.IO.Serialization;
|
using osu.Game.IO.Serialization;
|
||||||
|
using osu.Game.Rulesets.Catch;
|
||||||
|
using osu.Game.Rulesets.Mania;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Rulesets.Taiko;
|
||||||
using osu.Game.Tests.Resources;
|
using osu.Game.Tests.Resources;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Beatmaps.Formats
|
namespace osu.Game.Tests.Beatmaps.Formats
|
||||||
@ -16,39 +25,91 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class LegacyBeatmapEncoderTest
|
public class LegacyBeatmapEncoderTest
|
||||||
{
|
{
|
||||||
private const string normal = "Soleily - Renatus (Gamu) [Insane].osu";
|
|
||||||
|
|
||||||
private static IEnumerable<string> allBeatmaps => TestResources.GetStore().GetAvailableResources().Where(res => res.EndsWith(".osu"));
|
private static IEnumerable<string> allBeatmaps => TestResources.GetStore().GetAvailableResources().Where(res => res.EndsWith(".osu"));
|
||||||
|
|
||||||
[TestCaseSource(nameof(allBeatmaps))]
|
[TestCaseSource(nameof(allBeatmaps))]
|
||||||
public void TestDecodeEncodedBeatmap(string name)
|
public void TestBeatmap(string name)
|
||||||
{
|
{
|
||||||
var decoded = decode(normal, out var encoded);
|
var decoded = decode(name, out var encoded);
|
||||||
|
|
||||||
|
sort(decoded);
|
||||||
|
sort(encoded);
|
||||||
|
|
||||||
Assert.That(decoded.HitObjects.Count, Is.EqualTo(encoded.HitObjects.Count));
|
|
||||||
Assert.That(encoded.Serialize(), Is.EqualTo(decoded.Serialize()));
|
Assert.That(encoded.Serialize(), Is.EqualTo(decoded.Serialize()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Beatmap decode(string filename, out Beatmap encoded)
|
private void sort(IBeatmap beatmap)
|
||||||
{
|
{
|
||||||
using (var stream = TestResources.OpenResource(filename))
|
// Sort control points to ensure a sane ordering, as they may be parsed in different orders. This works because each group contains only uniquely-typed control points.
|
||||||
|
foreach (var g in beatmap.ControlPointInfo.Groups)
|
||||||
|
{
|
||||||
|
ArrayList.Adapter((IList)g.ControlPoints).Sort(
|
||||||
|
Comparer<ControlPoint>.Create((c1, c2) => string.Compare(c1.GetType().ToString(), c2.GetType().ToString(), StringComparison.Ordinal)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IBeatmap decode(string filename, out IBeatmap encoded)
|
||||||
|
{
|
||||||
|
using (var stream = TestResources.GetStore().GetStream(filename))
|
||||||
using (var sr = new LineBufferedReader(stream))
|
using (var sr = new LineBufferedReader(stream))
|
||||||
{
|
{
|
||||||
var legacyDecoded = new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(sr);
|
var legacyDecoded = convert(new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(sr));
|
||||||
|
|
||||||
using (var ms = new MemoryStream())
|
using (var ms = new MemoryStream())
|
||||||
using (var sw = new StreamWriter(ms))
|
using (var sw = new StreamWriter(ms))
|
||||||
using (var sr2 = new LineBufferedReader(ms))
|
using (var sr2 = new LineBufferedReader(ms, true))
|
||||||
{
|
{
|
||||||
new LegacyBeatmapEncoder(legacyDecoded).Encode(sw);
|
new LegacyBeatmapEncoder(legacyDecoded).Encode(sw);
|
||||||
sw.Flush();
|
|
||||||
|
|
||||||
|
sw.Flush();
|
||||||
ms.Position = 0;
|
ms.Position = 0;
|
||||||
|
|
||||||
encoded = new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(sr2);
|
encoded = convert(new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(sr2));
|
||||||
|
|
||||||
return legacyDecoded;
|
return legacyDecoded;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IBeatmap convert(IBeatmap beatmap)
|
||||||
|
{
|
||||||
|
switch (beatmap.BeatmapInfo.RulesetID)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
beatmap.BeatmapInfo.Ruleset = new OsuRuleset().RulesetInfo;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
beatmap.BeatmapInfo.Ruleset = new TaikoRuleset().RulesetInfo;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
beatmap.BeatmapInfo.Ruleset = new CatchRuleset().RulesetInfo;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
beatmap.BeatmapInfo.Ruleset = new ManiaRuleset().RulesetInfo;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TestWorkingBeatmap(beatmap).GetPlayableBeatmap(beatmap.BeatmapInfo.Ruleset);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestWorkingBeatmap : WorkingBeatmap
|
||||||
|
{
|
||||||
|
private readonly IBeatmap beatmap;
|
||||||
|
|
||||||
|
public TestWorkingBeatmap(IBeatmap beatmap)
|
||||||
|
: base(beatmap.BeatmapInfo, null)
|
||||||
|
{
|
||||||
|
this.beatmap = beatmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override IBeatmap GetBeatmap() => beatmap;
|
||||||
|
|
||||||
|
protected override Texture GetBackground() => throw new NotImplementedException();
|
||||||
|
|
||||||
|
protected override Track GetTrack() => throw new NotImplementedException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
61
osu.Game.Tests/Beatmaps/ToStringFormattingTest.cs
Normal file
61
osu.Game.Tests/Beatmaps/ToStringFormattingTest.cs
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Users;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Beatmaps
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class ToStringFormattingTest
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestArtistTitle()
|
||||||
|
{
|
||||||
|
var beatmap = new BeatmapInfo
|
||||||
|
{
|
||||||
|
Metadata = new BeatmapMetadata
|
||||||
|
{
|
||||||
|
Artist = "artist",
|
||||||
|
Title = "title"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Assert.That(beatmap.ToString(), Is.EqualTo("artist - title"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestArtistTitleCreator()
|
||||||
|
{
|
||||||
|
var beatmap = new BeatmapInfo
|
||||||
|
{
|
||||||
|
Metadata = new BeatmapMetadata
|
||||||
|
{
|
||||||
|
Artist = "artist",
|
||||||
|
Title = "title",
|
||||||
|
Author = new User { Username = "creator" }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Assert.That(beatmap.ToString(), Is.EqualTo("artist - title (creator)"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestArtistTitleCreatorDifficulty()
|
||||||
|
{
|
||||||
|
var beatmap = new BeatmapInfo
|
||||||
|
{
|
||||||
|
Metadata = new BeatmapMetadata
|
||||||
|
{
|
||||||
|
Artist = "artist",
|
||||||
|
Title = "title",
|
||||||
|
Author = new User { Username = "creator" }
|
||||||
|
},
|
||||||
|
Version = "difficulty"
|
||||||
|
};
|
||||||
|
|
||||||
|
Assert.That(beatmap.ToString(), Is.EqualTo("artist - title (creator) [difficulty]"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -29,11 +29,17 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
var cpi = new ControlPointInfo();
|
var cpi = new ControlPointInfo();
|
||||||
|
|
||||||
cpi.Add(0, new TimingControlPoint()); // is *not* redundant, special exception for first timing point.
|
cpi.Add(0, new TimingControlPoint()); // is *not* redundant, special exception for first timing point.
|
||||||
cpi.Add(1000, new TimingControlPoint()); // is redundant
|
cpi.Add(1000, new TimingControlPoint()); // is also not redundant, due to change of offset
|
||||||
|
|
||||||
Assert.That(cpi.Groups.Count, Is.EqualTo(1));
|
Assert.That(cpi.Groups.Count, Is.EqualTo(2));
|
||||||
Assert.That(cpi.TimingPoints.Count, Is.EqualTo(1));
|
Assert.That(cpi.TimingPoints.Count, Is.EqualTo(2));
|
||||||
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(1));
|
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(2));
|
||||||
|
|
||||||
|
cpi.Add(1000, new TimingControlPoint()); //is redundant
|
||||||
|
|
||||||
|
Assert.That(cpi.Groups.Count, Is.EqualTo(2));
|
||||||
|
Assert.That(cpi.TimingPoints.Count, Is.EqualTo(2));
|
||||||
|
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -86,11 +92,12 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
Assert.That(cpi.EffectPoints.Count, Is.EqualTo(0));
|
Assert.That(cpi.EffectPoints.Count, Is.EqualTo(0));
|
||||||
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(0));
|
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(0));
|
||||||
|
|
||||||
cpi.Add(1000, new EffectControlPoint { KiaiMode = true }); // is not redundant
|
cpi.Add(1000, new EffectControlPoint { KiaiMode = true, OmitFirstBarLine = true }); // is not redundant
|
||||||
|
cpi.Add(1400, new EffectControlPoint { KiaiMode = true, OmitFirstBarLine = true }); // same settings, but is not redundant
|
||||||
|
|
||||||
Assert.That(cpi.Groups.Count, Is.EqualTo(1));
|
Assert.That(cpi.Groups.Count, Is.EqualTo(2));
|
||||||
Assert.That(cpi.EffectPoints.Count, Is.EqualTo(1));
|
Assert.That(cpi.EffectPoints.Count, Is.EqualTo(2));
|
||||||
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(1));
|
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -5,27 +5,27 @@ osu file format v14
|
|||||||
255,193,1000,49,0,0:0:0:0:
|
255,193,1000,49,0,0:0:0:0:
|
||||||
// Combo index = 4
|
// Combo index = 4
|
||||||
|
|
||||||
// Slider with new combo followed by circle with no new combo
|
// Spinner with new combo followed by circle with no new combo
|
||||||
256,192,2000,12,0,2000,0:0:0:0:
|
256,192,2000,12,0,2000,0:0:0:0:
|
||||||
255,193,3000,1,0,0:0:0:0:
|
255,193,3000,1,0,0:0:0:0:
|
||||||
// Combo index = 5
|
// Combo index = 5
|
||||||
|
|
||||||
// Slider without new combo followed by circle with no new combo
|
// Spinner without new combo followed by circle with no new combo
|
||||||
256,192,4000,8,0,5000,0:0:0:0:
|
256,192,4000,8,0,5000,0:0:0:0:
|
||||||
255,193,6000,1,0,0:0:0:0:
|
255,193,6000,1,0,0:0:0:0:
|
||||||
// Combo index = 5
|
// Combo index = 5
|
||||||
|
|
||||||
// Slider without new combo followed by circle with new combo
|
// Spinner without new combo followed by circle with new combo
|
||||||
256,192,7000,8,0,8000,0:0:0:0:
|
256,192,7000,8,0,8000,0:0:0:0:
|
||||||
255,193,9000,5,0,0:0:0:0:
|
255,193,9000,5,0,0:0:0:0:
|
||||||
// Combo index = 6
|
// Combo index = 6
|
||||||
|
|
||||||
// Slider with new combo and offset (1) followed by circle with new combo and offset (3)
|
// Spinner with new combo and offset (1) followed by circle with new combo and offset (3)
|
||||||
256,192,10000,28,0,11000,0:0:0:0:
|
256,192,10000,28,0,11000,0:0:0:0:
|
||||||
255,193,12000,53,0,0:0:0:0:
|
255,193,12000,53,0,0:0:0:0:
|
||||||
// Combo index = 11
|
// Combo index = 11
|
||||||
|
|
||||||
// Slider with new combo and offset (2) followed by slider with no new combo followed by circle with no new combo
|
// Spinner with new combo and offset (2) followed by slider with no new combo followed by circle with no new combo
|
||||||
256,192,13000,44,0,14000,0:0:0:0:
|
256,192,13000,44,0,14000,0:0:0:0:
|
||||||
256,192,15000,8,0,16000,0:0:0:0:
|
256,192,15000,8,0,16000,0:0:0:0:
|
||||||
255,193,17000,1,0,0:0:0:0:
|
255,193,17000,1,0,0:0:0:0:
|
||||||
|
32
osu.Game.Tests/Resources/sample-beatmap-osu.osu
Normal file
32
osu.Game.Tests/Resources/sample-beatmap-osu.osu
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
osu file format v14
|
||||||
|
|
||||||
|
[General]
|
||||||
|
SampleSet: Normal
|
||||||
|
StackLeniency: 0.7
|
||||||
|
Mode: 0
|
||||||
|
|
||||||
|
[Difficulty]
|
||||||
|
HPDrainRate:3
|
||||||
|
CircleSize:5
|
||||||
|
OverallDifficulty:8
|
||||||
|
ApproachRate:8
|
||||||
|
SliderMultiplier:3.59999990463257
|
||||||
|
SliderTickRate:2
|
||||||
|
|
||||||
|
[TimingPoints]
|
||||||
|
24,352.941176470588,4,1,1,100,1,0
|
||||||
|
6376,-50,4,1,1,100,0,0
|
||||||
|
|
||||||
|
[HitObjects]
|
||||||
|
98,69,24,1,0,0:0:0:0:
|
||||||
|
419,72,200,1,2,0:0:0:0:
|
||||||
|
81,314,376,1,6,0:0:0:0:
|
||||||
|
423,321,553,1,12,0:0:0:0:
|
||||||
|
86,192,729,2,0,P|459:193|460:193,1,359.999990463257
|
||||||
|
86,192,1259,2,0,P|246:82|453:203,1,449.999988079071
|
||||||
|
86,192,1876,2,0,B|256:30|257:313|464:177,1,359.999990463257
|
||||||
|
86,55,2406,2,12,B|447:51|447:51|452:348|452:348|78:344,1,989.999973773957,14|2,0:0|0:0,0:0:0:0:
|
||||||
|
256,192,3553,12,0,4259,0:0:0:0:
|
||||||
|
67,57,4435,5,0,0:0:0:0:
|
||||||
|
440,52,4612,5,0,0:0:0:0:
|
||||||
|
86,181,4788,6,0,L|492:183,1,359.999990463257
|
@ -10,24 +10,22 @@ using osu.Game.Rulesets.Osu;
|
|||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
using osu.Game.Screens.Edit.Compose.Components.Timeline;
|
using osu.Game.Screens.Edit.Compose.Components.Timeline;
|
||||||
using osuTK.Input;
|
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Editor
|
namespace osu.Game.Tests.Visual.Editor
|
||||||
{
|
{
|
||||||
public class TestSceneEditorChangeStates : ScreenTestScene
|
public class TestSceneEditorChangeStates : ScreenTestScene
|
||||||
{
|
{
|
||||||
private EditorBeatmap editorBeatmap;
|
private EditorBeatmap editorBeatmap;
|
||||||
|
private TestEditor editor;
|
||||||
|
|
||||||
public override void SetUpSteps()
|
public override void SetUpSteps()
|
||||||
{
|
{
|
||||||
base.SetUpSteps();
|
base.SetUpSteps();
|
||||||
|
|
||||||
Screens.Edit.Editor editor = null;
|
|
||||||
|
|
||||||
AddStep("load editor", () =>
|
AddStep("load editor", () =>
|
||||||
{
|
{
|
||||||
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
|
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
|
||||||
LoadScreen(editor = new Screens.Edit.Editor());
|
LoadScreen(editor = new TestEditor());
|
||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("wait for editor to load", () => editor.ChildrenOfType<HitObjectComposer>().FirstOrDefault()?.IsLoaded == true
|
AddUntilStep("wait for editor to load", () => editor.ChildrenOfType<HitObjectComposer>().FirstOrDefault()?.IsLoaded == true
|
||||||
@ -160,36 +158,15 @@ namespace osu.Game.Tests.Visual.Editor
|
|||||||
AddAssert("no hitobject added", () => addedObject == null);
|
AddAssert("no hitobject added", () => addedObject == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addUndoSteps()
|
private void addUndoSteps() => AddStep("undo", () => editor.Undo());
|
||||||
|
|
||||||
|
private void addRedoSteps() => AddStep("redo", () => editor.Redo());
|
||||||
|
|
||||||
|
private class TestEditor : Screens.Edit.Editor
|
||||||
{
|
{
|
||||||
AddStep("press undo", () =>
|
public new void Undo() => base.Undo();
|
||||||
{
|
|
||||||
InputManager.PressKey(Key.LControl);
|
|
||||||
InputManager.PressKey(Key.Z);
|
|
||||||
});
|
|
||||||
|
|
||||||
AddStep("release keys", () =>
|
public new void Redo() => base.Redo();
|
||||||
{
|
|
||||||
InputManager.ReleaseKey(Key.LControl);
|
|
||||||
InputManager.ReleaseKey(Key.Z);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addRedoSteps()
|
|
||||||
{
|
|
||||||
AddStep("press redo", () =>
|
|
||||||
{
|
|
||||||
InputManager.PressKey(Key.LControl);
|
|
||||||
InputManager.PressKey(Key.LShift);
|
|
||||||
InputManager.PressKey(Key.Z);
|
|
||||||
});
|
|
||||||
|
|
||||||
AddStep("release keys", () =>
|
|
||||||
{
|
|
||||||
InputManager.ReleaseKey(Key.LControl);
|
|
||||||
InputManager.ReleaseKey(Key.LShift);
|
|
||||||
InputManager.ReleaseKey(Key.Z);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,133 @@
|
|||||||
|
// 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.Objects;
|
||||||
|
using osu.Game.Storyboards;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Gameplay
|
||||||
|
{
|
||||||
|
public class TestSceneCompletionCancellation : TestPlayerTestScene
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
|
||||||
|
[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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
98
osu.Game.Tests/Visual/Gameplay/TestSceneKeyBindings.cs
Normal file
98
osu.Game.Tests/Visual/Gameplay/TestSceneKeyBindings.cs
Normal file
@ -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 System.Collections.Generic;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Overlays.BeatmapListing;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Online
|
namespace osu.Game.Tests.Visual.Online
|
||||||
{
|
{
|
||||||
@ -13,6 +14,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||||
{
|
{
|
||||||
typeof(BeatmapListingOverlay),
|
typeof(BeatmapListingOverlay),
|
||||||
|
typeof(BeatmapListingFilterControl)
|
||||||
};
|
};
|
||||||
|
|
||||||
protected override bool UseOnlineAPI => true;
|
protected override bool UseOnlineAPI => true;
|
||||||
|
43
osu.Game.Tests/Visual/Online/TestSceneDashboardOverlay.cs
Normal file
43
osu.Game.Tests/Visual/Online/TestSceneDashboardOverlay.cs
Normal file
@ -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.Framework.Graphics;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Online;
|
using osu.Game.Online;
|
||||||
using osu.Game.Overlays.Direct;
|
using osu.Game.Overlays.BeatmapListing.Panels;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Tests.Resources;
|
using osu.Game.Tests.Resources;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -20,7 +20,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
{
|
{
|
||||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||||
{
|
{
|
||||||
typeof(PanelDownloadButton)
|
typeof(BeatmapPanelDownloadButton)
|
||||||
};
|
};
|
||||||
|
|
||||||
private TestDownloadButton downloadButton;
|
private TestDownloadButton downloadButton;
|
||||||
@ -143,7 +143,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
return beatmap;
|
return beatmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestDownloadButton : PanelDownloadButton
|
private class TestDownloadButton : BeatmapPanelDownloadButton
|
||||||
{
|
{
|
||||||
public new bool DownloadEnabled => base.DownloadEnabled;
|
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.Framework.Graphics.Containers;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Overlays.Direct;
|
using osu.Game.Overlays.BeatmapListing.Panels;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -20,8 +20,8 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
{
|
{
|
||||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||||
{
|
{
|
||||||
typeof(DirectGridPanel),
|
typeof(GridBeatmapPanel),
|
||||||
typeof(DirectListPanel),
|
typeof(ListBeatmapPanel),
|
||||||
typeof(IconPill)
|
typeof(IconPill)
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -126,12 +126,12 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
Spacing = new Vector2(5, 20),
|
Spacing = new Vector2(5, 20),
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new DirectGridPanel(normal),
|
new GridBeatmapPanel(normal),
|
||||||
new DirectGridPanel(undownloadable),
|
new GridBeatmapPanel(undownloadable),
|
||||||
new DirectGridPanel(manyDifficulties),
|
new GridBeatmapPanel(manyDifficulties),
|
||||||
new DirectListPanel(normal),
|
new ListBeatmapPanel(normal),
|
||||||
new DirectListPanel(undownloadable),
|
new ListBeatmapPanel(undownloadable),
|
||||||
new DirectListPanel(manyDifficulties),
|
new ListBeatmapPanel(manyDifficulties),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -10,6 +10,7 @@ using osu.Game.Users;
|
|||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Online.API;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Online
|
namespace osu.Game.Tests.Visual.Online
|
||||||
{
|
{
|
||||||
@ -27,7 +28,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
[Cached]
|
[Cached]
|
||||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
|
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
|
||||||
|
|
||||||
private FriendDisplay display;
|
private TestFriendDisplay display;
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public void Setup() => Schedule(() =>
|
public void Setup() => Schedule(() =>
|
||||||
@ -35,7 +36,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
Child = new BasicScrollContainer
|
Child = new BasicScrollContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Child = display = new FriendDisplay()
|
Child = display = new TestFriendDisplay()
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -83,5 +84,17 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
LastVisit = DateTimeOffset.Now
|
LastVisit = DateTimeOffset.Now
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private class TestFriendDisplay : FriendDisplay
|
||||||
|
{
|
||||||
|
public void Fetch()
|
||||||
|
{
|
||||||
|
base.APIStateChanged(API, APIState.Online);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void APIStateChanged(IAPIProvider api, APIState state)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
85
osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.cs
Normal file
85
osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.cs
Normal file
@ -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);
|
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]
|
[Test]
|
||||||
public void TestRulesetChangeResetsMods()
|
public void TestRulesetChangeResetsMods()
|
||||||
{
|
{
|
||||||
|
@ -47,8 +47,8 @@ namespace osu.Game.Tests.Visual
|
|||||||
typeof(IdleTracker),
|
typeof(IdleTracker),
|
||||||
typeof(OnScreenDisplay),
|
typeof(OnScreenDisplay),
|
||||||
typeof(NotificationOverlay),
|
typeof(NotificationOverlay),
|
||||||
typeof(DirectOverlay),
|
typeof(BeatmapListingOverlay),
|
||||||
typeof(SocialOverlay),
|
typeof(DashboardOverlay),
|
||||||
typeof(ChannelManager),
|
typeof(ChannelManager),
|
||||||
typeof(ChatOverlay),
|
typeof(ChatOverlay),
|
||||||
typeof(SettingsOverlay),
|
typeof(SettingsOverlay),
|
||||||
|
@ -15,25 +15,27 @@ using osuTK;
|
|||||||
|
|
||||||
namespace osu.Game.Tests.Visual.UserInterface
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
{
|
{
|
||||||
public class TestSceneBeatmapListingSearchSection : OsuTestScene
|
public class TestSceneBeatmapListingSearchControl : OsuTestScene
|
||||||
{
|
{
|
||||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||||
{
|
{
|
||||||
typeof(BeatmapListingSearchSection),
|
typeof(BeatmapListingSearchControl),
|
||||||
};
|
};
|
||||||
|
|
||||||
[Cached]
|
[Cached]
|
||||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
|
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
|
||||||
|
|
||||||
private readonly BeatmapListingSearchSection section;
|
private readonly BeatmapListingSearchControl control;
|
||||||
|
|
||||||
public TestSceneBeatmapListingSearchSection()
|
public TestSceneBeatmapListingSearchControl()
|
||||||
{
|
{
|
||||||
OsuSpriteText query;
|
OsuSpriteText query;
|
||||||
OsuSpriteText ruleset;
|
OsuSpriteText ruleset;
|
||||||
OsuSpriteText category;
|
OsuSpriteText category;
|
||||||
|
OsuSpriteText genre;
|
||||||
|
OsuSpriteText language;
|
||||||
|
|
||||||
Add(section = new BeatmapListingSearchSection
|
Add(control = new BeatmapListingSearchControl
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
@ -49,20 +51,24 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
query = new OsuSpriteText(),
|
query = new OsuSpriteText(),
|
||||||
ruleset = new OsuSpriteText(),
|
ruleset = new OsuSpriteText(),
|
||||||
category = new OsuSpriteText(),
|
category = new OsuSpriteText(),
|
||||||
|
genre = new OsuSpriteText(),
|
||||||
|
language = new OsuSpriteText(),
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
section.Query.BindValueChanged(q => query.Text = $"Query: {q.NewValue}", true);
|
control.Query.BindValueChanged(q => query.Text = $"Query: {q.NewValue}", true);
|
||||||
section.Ruleset.BindValueChanged(r => ruleset.Text = $"Ruleset: {r.NewValue}", true);
|
control.Ruleset.BindValueChanged(r => ruleset.Text = $"Ruleset: {r.NewValue}", true);
|
||||||
section.Category.BindValueChanged(c => category.Text = $"Category: {c.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]
|
[Test]
|
||||||
public void TestCovers()
|
public void TestCovers()
|
||||||
{
|
{
|
||||||
AddStep("Set beatmap", () => section.BeatmapSet = beatmap_set);
|
AddStep("Set beatmap", () => control.BeatmapSet = beatmap_set);
|
||||||
AddStep("Set beatmap (no cover)", () => section.BeatmapSet = no_cover_beatmap_set);
|
AddStep("Set beatmap (no cover)", () => control.BeatmapSet = no_cover_beatmap_set);
|
||||||
AddStep("Set null beatmap", () => section.BeatmapSet = null);
|
AddStep("Set null beatmap", () => control.BeatmapSet = null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly BeatmapSetInfo beatmap_set = new BeatmapSetInfo
|
private static readonly BeatmapSetInfo beatmap_set = new BeatmapSetInfo
|
@ -13,18 +13,17 @@ using osuTK;
|
|||||||
|
|
||||||
namespace osu.Game.Tests.Visual.UserInterface
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
{
|
{
|
||||||
public class TestSceneBeatmapListingSort : OsuTestScene
|
public class TestSceneBeatmapListingSortTabControl : OsuTestScene
|
||||||
{
|
{
|
||||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||||
{
|
{
|
||||||
typeof(BeatmapListingSortTabControl),
|
|
||||||
typeof(OverlaySortTabControl<>),
|
typeof(OverlaySortTabControl<>),
|
||||||
};
|
};
|
||||||
|
|
||||||
[Cached]
|
[Cached]
|
||||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
|
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
|
||||||
|
|
||||||
public TestSceneBeatmapListingSort()
|
public TestSceneBeatmapListingSortTabControl()
|
||||||
{
|
{
|
||||||
BeatmapListingSortTabControl control;
|
BeatmapListingSortTabControl control;
|
||||||
OsuSpriteText current;
|
OsuSpriteText current;
|
@ -8,7 +8,6 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Online.API.Requests;
|
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.BeatmapListing;
|
using osu.Game.Overlays.BeatmapListing;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -20,8 +19,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||||
{
|
{
|
||||||
typeof(BeatmapSearchFilterRow<>),
|
typeof(BeatmapSearchFilterRow<>),
|
||||||
typeof(BeatmapSearchRulesetFilterRow),
|
typeof(BeatmapSearchRulesetFilterRow)
|
||||||
typeof(BeatmapSearchSmallFilterRow<>),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
[Cached]
|
[Cached]
|
||||||
@ -42,8 +40,8 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new BeatmapSearchRulesetFilterRow(),
|
new BeatmapSearchRulesetFilterRow(),
|
||||||
new BeatmapSearchFilterRow<BeatmapSearchCategory>("Categories"),
|
new BeatmapSearchFilterRow<SearchCategory>("Categories"),
|
||||||
new BeatmapSearchSmallFilterRow<BeatmapSearchCategory>("Header Name")
|
new BeatmapSearchFilterRow<SearchCategory>("Header Name")
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -149,7 +149,12 @@ namespace osu.Game.Beatmaps
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => $"{Metadata} [{Version}]".Trim();
|
public override string ToString()
|
||||||
|
{
|
||||||
|
string version = string.IsNullOrEmpty(Version) ? string.Empty : $"[{Version}]";
|
||||||
|
|
||||||
|
return $"{Metadata} {version}".Trim();
|
||||||
|
}
|
||||||
|
|
||||||
public bool Equals(BeatmapInfo other)
|
public bool Equals(BeatmapInfo other)
|
||||||
{
|
{
|
||||||
|
@ -53,7 +53,11 @@ namespace osu.Game.Beatmaps
|
|||||||
public string AudioFile { get; set; }
|
public string AudioFile { get; set; }
|
||||||
public string BackgroundFile { get; set; }
|
public string BackgroundFile { get; set; }
|
||||||
|
|
||||||
public override string ToString() => $"{Artist} - {Title} ({Author})";
|
public override string ToString()
|
||||||
|
{
|
||||||
|
string author = Author == null ? string.Empty : $"({Author})";
|
||||||
|
return $"{Artist} - {Title} {author}".Trim();
|
||||||
|
}
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public string[] SearchableTerms => new[]
|
public string[] SearchableTerms => new[]
|
||||||
|
@ -5,7 +5,7 @@ using System;
|
|||||||
|
|
||||||
namespace osu.Game.Beatmaps.ControlPoints
|
namespace osu.Game.Beatmaps.ControlPoints
|
||||||
{
|
{
|
||||||
public abstract class ControlPoint : IComparable<ControlPoint>, IEquatable<ControlPoint>
|
public abstract class ControlPoint : IComparable<ControlPoint>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The time at which the control point takes effect.
|
/// The time at which the control point takes effect.
|
||||||
@ -19,12 +19,10 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
public int CompareTo(ControlPoint other) => Time.CompareTo(other.Time);
|
public int CompareTo(ControlPoint other) => Time.CompareTo(other.Time);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether this control point is equivalent to another, ignoring time.
|
/// Determines whether this <see cref="ControlPoint"/> results in a meaningful change when placed alongside another.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="other">Another control point to compare with.</param>
|
/// <param name="existing">An existing control point to compare with.</param>
|
||||||
/// <returns>Whether equivalent.</returns>
|
/// <returns>Whether this <see cref="ControlPoint"/> is redundant when placed alongside <paramref name="existing"/>.</returns>
|
||||||
public abstract bool EquivalentTo(ControlPoint other);
|
public abstract bool IsRedundant(ControlPoint existing);
|
||||||
|
|
||||||
public bool Equals(ControlPoint other) => Time == other?.Time && EquivalentTo(other);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,6 +56,7 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// All control points, of all types.
|
/// All control points, of all types.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[JsonIgnore]
|
||||||
public IEnumerable<ControlPoint> AllControlPoints => Groups.SelectMany(g => g.ControlPoints).ToArray();
|
public IEnumerable<ControlPoint> AllControlPoints => Groups.SelectMany(g => g.ControlPoints).ToArray();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -247,7 +248,7 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return existing?.EquivalentTo(newPoint) == true;
|
return newPoint?.IsRedundant(existing) == true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void groupItemAdded(ControlPoint controlPoint)
|
private void groupItemAdded(ControlPoint controlPoint)
|
||||||
|
@ -27,7 +27,8 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
set => SpeedMultiplierBindable.Value = value;
|
set => SpeedMultiplierBindable.Value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool EquivalentTo(ControlPoint other) =>
|
public override bool IsRedundant(ControlPoint existing)
|
||||||
other is DifficultyControlPoint otherTyped && otherTyped.SpeedMultiplier.Equals(SpeedMultiplier);
|
=> existing is DifficultyControlPoint existingDifficulty
|
||||||
|
&& SpeedMultiplier == existingDifficulty.SpeedMultiplier;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,8 +35,10 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
set => KiaiModeBindable.Value = value;
|
set => KiaiModeBindable.Value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool EquivalentTo(ControlPoint other) =>
|
public override bool IsRedundant(ControlPoint existing)
|
||||||
other is EffectControlPoint otherTyped &&
|
=> !OmitFirstBarLine
|
||||||
KiaiMode == otherTyped.KiaiMode && OmitFirstBarLine == otherTyped.OmitFirstBarLine;
|
&& existing is EffectControlPoint existingEffect
|
||||||
|
&& KiaiMode == existingEffect.KiaiMode
|
||||||
|
&& OmitFirstBarLine == existingEffect.OmitFirstBarLine;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -68,8 +68,9 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
return newSampleInfo;
|
return newSampleInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool EquivalentTo(ControlPoint other) =>
|
public override bool IsRedundant(ControlPoint existing)
|
||||||
other is SampleControlPoint otherTyped &&
|
=> existing is SampleControlPoint existingSample
|
||||||
SampleBank == otherTyped.SampleBank && SampleVolume == otherTyped.SampleVolume;
|
&& SampleBank == existingSample.SampleBank
|
||||||
|
&& SampleVolume == existingSample.SampleVolume;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,8 +48,7 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public double BPM => 60000 / BeatLength;
|
public double BPM => 60000 / BeatLength;
|
||||||
|
|
||||||
public override bool EquivalentTo(ControlPoint other) =>
|
// Timing points are never redundant as they can change the time signature.
|
||||||
other is TimingControlPoint otherTyped
|
public override bool IsRedundant(ControlPoint existing) => false;
|
||||||
&& TimeSignature == otherTyped.TimeSignature && BeatLength.Equals(otherTyped.BeatLength);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -386,17 +386,10 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
SampleVolume = sampleVolume,
|
SampleVolume = sampleVolume,
|
||||||
CustomSampleBank = customSampleBank,
|
CustomSampleBank = customSampleBank,
|
||||||
}, timingChange);
|
}, 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 List<ControlPoint> pendingControlPoints = new List<ControlPoint>();
|
||||||
|
private readonly HashSet<Type> pendingControlPointTypes = new HashSet<Type>();
|
||||||
private double pendingControlPointsTime;
|
private double pendingControlPointsTime;
|
||||||
|
|
||||||
private void addControlPoint(double time, ControlPoint point, bool timingChange)
|
private void addControlPoint(double time, ControlPoint point, bool timingChange)
|
||||||
@ -405,21 +398,28 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
flushPendingPoints();
|
flushPendingPoints();
|
||||||
|
|
||||||
if (timingChange)
|
if (timingChange)
|
||||||
{
|
pendingControlPoints.Insert(0, point);
|
||||||
beatmap.ControlPointInfo.Add(time, point);
|
else
|
||||||
return;
|
pendingControlPoints.Add(point);
|
||||||
}
|
|
||||||
|
|
||||||
pendingControlPoints.Add(point);
|
|
||||||
pendingControlPointsTime = time;
|
pendingControlPointsTime = time;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void flushPendingPoints()
|
private void flushPendingPoints()
|
||||||
{
|
{
|
||||||
foreach (var p in pendingControlPoints)
|
// 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).
|
||||||
beatmap.ControlPointInfo.Add(pendingControlPointsTime, p);
|
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();
|
pendingControlPoints.Clear();
|
||||||
|
pendingControlPointTypes.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleHitObject(string line)
|
private void handleHitObject(string line)
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@ -10,6 +11,7 @@ using osu.Game.Audio;
|
|||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Beatmaps.Legacy;
|
using osu.Game.Beatmaps.Legacy;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Legacy;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
|
||||||
namespace osu.Game.Beatmaps.Formats
|
namespace osu.Game.Beatmaps.Formats
|
||||||
@ -48,7 +50,7 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
handleEvents(writer);
|
handleEvents(writer);
|
||||||
|
|
||||||
writer.WriteLine();
|
writer.WriteLine();
|
||||||
handleTimingPoints(writer);
|
handleControlPoints(writer);
|
||||||
|
|
||||||
writer.WriteLine();
|
writer.WriteLine();
|
||||||
handleHitObjects(writer);
|
handleHitObjects(writer);
|
||||||
@ -58,7 +60,7 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
{
|
{
|
||||||
writer.WriteLine("[General]");
|
writer.WriteLine("[General]");
|
||||||
|
|
||||||
writer.WriteLine(FormattableString.Invariant($"AudioFilename: {Path.GetFileName(beatmap.Metadata.AudioFile)}"));
|
if (beatmap.Metadata.AudioFile != null) writer.WriteLine(FormattableString.Invariant($"AudioFilename: {Path.GetFileName(beatmap.Metadata.AudioFile)}"));
|
||||||
writer.WriteLine(FormattableString.Invariant($"AudioLeadIn: {beatmap.BeatmapInfo.AudioLeadIn}"));
|
writer.WriteLine(FormattableString.Invariant($"AudioLeadIn: {beatmap.BeatmapInfo.AudioLeadIn}"));
|
||||||
writer.WriteLine(FormattableString.Invariant($"PreviewTime: {beatmap.Metadata.PreviewTime}"));
|
writer.WriteLine(FormattableString.Invariant($"PreviewTime: {beatmap.Metadata.PreviewTime}"));
|
||||||
// Todo: Not all countdown types are supported by lazer yet
|
// Todo: Not all countdown types are supported by lazer yet
|
||||||
@ -103,15 +105,15 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
writer.WriteLine("[Metadata]");
|
writer.WriteLine("[Metadata]");
|
||||||
|
|
||||||
writer.WriteLine(FormattableString.Invariant($"Title: {beatmap.Metadata.Title}"));
|
writer.WriteLine(FormattableString.Invariant($"Title: {beatmap.Metadata.Title}"));
|
||||||
writer.WriteLine(FormattableString.Invariant($"TitleUnicode: {beatmap.Metadata.TitleUnicode}"));
|
if (beatmap.Metadata.TitleUnicode != null) writer.WriteLine(FormattableString.Invariant($"TitleUnicode: {beatmap.Metadata.TitleUnicode}"));
|
||||||
writer.WriteLine(FormattableString.Invariant($"Artist: {beatmap.Metadata.Artist}"));
|
writer.WriteLine(FormattableString.Invariant($"Artist: {beatmap.Metadata.Artist}"));
|
||||||
writer.WriteLine(FormattableString.Invariant($"ArtistUnicode: {beatmap.Metadata.ArtistUnicode}"));
|
if (beatmap.Metadata.ArtistUnicode != null) writer.WriteLine(FormattableString.Invariant($"ArtistUnicode: {beatmap.Metadata.ArtistUnicode}"));
|
||||||
writer.WriteLine(FormattableString.Invariant($"Creator: {beatmap.Metadata.AuthorString}"));
|
writer.WriteLine(FormattableString.Invariant($"Creator: {beatmap.Metadata.AuthorString}"));
|
||||||
writer.WriteLine(FormattableString.Invariant($"Version: {beatmap.BeatmapInfo.Version}"));
|
writer.WriteLine(FormattableString.Invariant($"Version: {beatmap.BeatmapInfo.Version}"));
|
||||||
writer.WriteLine(FormattableString.Invariant($"Source: {beatmap.Metadata.Source}"));
|
if (beatmap.Metadata.Source != null) writer.WriteLine(FormattableString.Invariant($"Source: {beatmap.Metadata.Source}"));
|
||||||
writer.WriteLine(FormattableString.Invariant($"Tags: {beatmap.Metadata.Tags}"));
|
if (beatmap.Metadata.Tags != null) writer.WriteLine(FormattableString.Invariant($"Tags: {beatmap.Metadata.Tags}"));
|
||||||
writer.WriteLine(FormattableString.Invariant($"BeatmapID: {beatmap.BeatmapInfo.OnlineBeatmapID ?? 0}"));
|
if (beatmap.BeatmapInfo.OnlineBeatmapID != null) writer.WriteLine(FormattableString.Invariant($"BeatmapID: {beatmap.BeatmapInfo.OnlineBeatmapID}"));
|
||||||
writer.WriteLine(FormattableString.Invariant($"BeatmapSetID: {beatmap.BeatmapInfo.BeatmapSet?.OnlineBeatmapSetID ?? -1}"));
|
if (beatmap.BeatmapInfo.BeatmapSet?.OnlineBeatmapSetID != null) writer.WriteLine(FormattableString.Invariant($"BeatmapSetID: {beatmap.BeatmapInfo.BeatmapSet.OnlineBeatmapSetID}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleDifficulty(TextWriter writer)
|
private void handleDifficulty(TextWriter writer)
|
||||||
@ -137,7 +139,7 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
writer.WriteLine(FormattableString.Invariant($"{(int)LegacyEventType.Break},{b.StartTime},{b.EndTime}"));
|
writer.WriteLine(FormattableString.Invariant($"{(int)LegacyEventType.Break},{b.StartTime},{b.EndTime}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleTimingPoints(TextWriter writer)
|
private void handleControlPoints(TextWriter writer)
|
||||||
{
|
{
|
||||||
if (beatmap.ControlPointInfo.Groups.Count == 0)
|
if (beatmap.ControlPointInfo.Groups.Count == 0)
|
||||||
return;
|
return;
|
||||||
@ -146,20 +148,30 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
|
|
||||||
foreach (var group in beatmap.ControlPointInfo.Groups)
|
foreach (var group in beatmap.ControlPointInfo.Groups)
|
||||||
{
|
{
|
||||||
var timingPoint = group.ControlPoints.OfType<TimingControlPoint>().FirstOrDefault();
|
var groupTimingPoint = group.ControlPoints.OfType<TimingControlPoint>().FirstOrDefault();
|
||||||
var difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(group.Time);
|
|
||||||
var samplePoint = beatmap.ControlPointInfo.SamplePointAt(group.Time);
|
|
||||||
var effectPoint = beatmap.ControlPointInfo.EffectPointAt(group.Time);
|
|
||||||
|
|
||||||
// Convert beat length the legacy format
|
// If the group contains a timing control point, it needs to be output separately.
|
||||||
double beatLength;
|
if (groupTimingPoint != null)
|
||||||
if (timingPoint != null)
|
{
|
||||||
beatLength = timingPoint.BeatLength;
|
writer.Write(FormattableString.Invariant($"{groupTimingPoint.Time},"));
|
||||||
else
|
writer.Write(FormattableString.Invariant($"{groupTimingPoint.BeatLength},"));
|
||||||
beatLength = -100 / difficultyPoint.SpeedMultiplier;
|
outputControlPointEffectsAt(groupTimingPoint.Time, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output any remaining effects as secondary non-timing control point.
|
||||||
|
var difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(group.Time);
|
||||||
|
writer.Write(FormattableString.Invariant($"{group.Time},"));
|
||||||
|
writer.Write(FormattableString.Invariant($"{-100 / difficultyPoint.SpeedMultiplier},"));
|
||||||
|
outputControlPointEffectsAt(group.Time, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void outputControlPointEffectsAt(double time, bool isTimingPoint)
|
||||||
|
{
|
||||||
|
var samplePoint = beatmap.ControlPointInfo.SamplePointAt(time);
|
||||||
|
var effectPoint = beatmap.ControlPointInfo.EffectPointAt(time);
|
||||||
|
|
||||||
// Apply the control point to a hit sample to uncover legacy properties (e.g. suffix)
|
// Apply the control point to a hit sample to uncover legacy properties (e.g. suffix)
|
||||||
HitSampleInfo tempHitSample = samplePoint.ApplyTo(new HitSampleInfo());
|
HitSampleInfo tempHitSample = samplePoint.ApplyTo(new ConvertHitObjectParser.LegacyHitSampleInfo());
|
||||||
|
|
||||||
// Convert effect flags to the legacy format
|
// Convert effect flags to the legacy format
|
||||||
LegacyEffectFlags effectFlags = LegacyEffectFlags.None;
|
LegacyEffectFlags effectFlags = LegacyEffectFlags.None;
|
||||||
@ -168,13 +180,11 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
if (effectPoint.OmitFirstBarLine)
|
if (effectPoint.OmitFirstBarLine)
|
||||||
effectFlags |= LegacyEffectFlags.OmitFirstBarLine;
|
effectFlags |= LegacyEffectFlags.OmitFirstBarLine;
|
||||||
|
|
||||||
writer.Write(FormattableString.Invariant($"{group.Time},"));
|
writer.Write(FormattableString.Invariant($"{(int)beatmap.ControlPointInfo.TimingPointAt(time).TimeSignature},"));
|
||||||
writer.Write(FormattableString.Invariant($"{beatLength},"));
|
|
||||||
writer.Write(FormattableString.Invariant($"{(int)beatmap.ControlPointInfo.TimingPointAt(group.Time).TimeSignature},"));
|
|
||||||
writer.Write(FormattableString.Invariant($"{(int)toLegacySampleBank(tempHitSample.Bank)},"));
|
writer.Write(FormattableString.Invariant($"{(int)toLegacySampleBank(tempHitSample.Bank)},"));
|
||||||
writer.Write(FormattableString.Invariant($"{toLegacyCustomSampleBank(tempHitSample.Suffix)},"));
|
writer.Write(FormattableString.Invariant($"{toLegacyCustomSampleBank(tempHitSample)},"));
|
||||||
writer.Write(FormattableString.Invariant($"{tempHitSample.Volume},"));
|
writer.Write(FormattableString.Invariant($"{tempHitSample.Volume},"));
|
||||||
writer.Write(FormattableString.Invariant($"{(timingPoint != null ? '1' : '0')},"));
|
writer.Write(FormattableString.Invariant($"{(isTimingPoint ? '1' : '0')},"));
|
||||||
writer.Write(FormattableString.Invariant($"{(int)effectFlags}"));
|
writer.Write(FormattableString.Invariant($"{(int)effectFlags}"));
|
||||||
writer.WriteLine();
|
writer.WriteLine();
|
||||||
}
|
}
|
||||||
@ -187,27 +197,13 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
|
|
||||||
writer.WriteLine("[HitObjects]");
|
writer.WriteLine("[HitObjects]");
|
||||||
|
|
||||||
|
// TODO: implement other legacy rulesets
|
||||||
switch (beatmap.BeatmapInfo.RulesetID)
|
switch (beatmap.BeatmapInfo.RulesetID)
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
foreach (var h in beatmap.HitObjects)
|
foreach (var h in beatmap.HitObjects)
|
||||||
handleOsuHitObject(writer, h);
|
handleOsuHitObject(writer, h);
|
||||||
break;
|
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -254,7 +250,7 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case IHasEndTime _:
|
case IHasEndTime _:
|
||||||
type |= LegacyHitObjectType.Spinner | LegacyHitObjectType.NewCombo;
|
type |= LegacyHitObjectType.Spinner;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -328,12 +324,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)
|
private string getSampleBank(IList<HitSampleInfo> samples, bool banksOnly = false, bool zeroBanks = false)
|
||||||
{
|
{
|
||||||
LegacySampleBank normalBank = toLegacySampleBank(samples.SingleOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL)?.Bank);
|
LegacySampleBank normalBank = toLegacySampleBank(samples.SingleOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL)?.Bank);
|
||||||
@ -346,7 +336,7 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
|
|
||||||
if (!banksOnly)
|
if (!banksOnly)
|
||||||
{
|
{
|
||||||
string customSampleBank = toLegacyCustomSampleBank(samples.FirstOrDefault(s => !string.IsNullOrEmpty(s.Name))?.Suffix);
|
string customSampleBank = toLegacyCustomSampleBank(samples.FirstOrDefault(s => !string.IsNullOrEmpty(s.Name)));
|
||||||
string sampleFilename = samples.FirstOrDefault(s => string.IsNullOrEmpty(s.Name))?.LookupNames.First() ?? string.Empty;
|
string sampleFilename = samples.FirstOrDefault(s => string.IsNullOrEmpty(s.Name))?.LookupNames.First() ?? string.Empty;
|
||||||
int volume = samples.FirstOrDefault()?.Volume ?? 100;
|
int volume = samples.FirstOrDefault()?.Volume ?? 100;
|
||||||
|
|
||||||
@ -402,6 +392,15 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string toLegacyCustomSampleBank(string sampleSuffix) => string.IsNullOrEmpty(sampleSuffix) ? "0" : sampleSuffix;
|
private string toLegacyCustomSampleBank(HitSampleInfo hitSampleInfo)
|
||||||
|
{
|
||||||
|
if (hitSampleInfo == null)
|
||||||
|
return "0";
|
||||||
|
|
||||||
|
if (hitSampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacy)
|
||||||
|
return legacy.CustomSampleBank.ToString(CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
|
return "0";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -150,7 +150,8 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
HitObjects,
|
HitObjects,
|
||||||
Variables,
|
Variables,
|
||||||
Fonts,
|
Fonts,
|
||||||
Mania
|
CatchTheBeat,
|
||||||
|
Mania,
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class LegacyDifficultyControlPoint : DifficultyControlPoint
|
internal class LegacyDifficultyControlPoint : DifficultyControlPoint
|
||||||
@ -178,9 +179,10 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
return baseInfo;
|
return baseInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool EquivalentTo(ControlPoint other) =>
|
public override bool IsRedundant(ControlPoint existing)
|
||||||
base.EquivalentTo(other) && other is LegacySampleControlPoint otherTyped &&
|
=> base.IsRedundant(existing)
|
||||||
CustomSampleBank == otherTyped.CustomSampleBank;
|
&& existing is LegacySampleControlPoint existingSample
|
||||||
|
&& CustomSampleBank == existingSample.CustomSampleBank;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -103,7 +103,7 @@ namespace osu.Game.Graphics.Containers
|
|||||||
|
|
||||||
TimeSinceLastBeat = beatLength - TimeUntilNextBeat;
|
TimeSinceLastBeat = beatLength - TimeUntilNextBeat;
|
||||||
|
|
||||||
if (timingPoint.Equals(lastTimingPoint) && beatIndex == lastBeat)
|
if (timingPoint == lastTimingPoint && beatIndex == lastBeat)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
using (BeginDelayedSequence(-TimeSinceLastBeat, true))
|
using (BeginDelayedSequence(-TimeSinceLastBeat, true))
|
||||||
|
@ -43,13 +43,13 @@ namespace osu.Game.IO.Serialization.Converters
|
|||||||
var list = new List<T>();
|
var list = new List<T>();
|
||||||
|
|
||||||
var obj = JObject.Load(reader);
|
var obj = JObject.Load(reader);
|
||||||
var lookupTable = serializer.Deserialize<List<string>>(obj["lookup_table"].CreateReader());
|
var lookupTable = serializer.Deserialize<List<string>>(obj["$lookup_table"].CreateReader());
|
||||||
|
|
||||||
foreach (var tok in obj["items"])
|
foreach (var tok in obj["$items"])
|
||||||
{
|
{
|
||||||
var itemReader = tok.CreateReader();
|
var itemReader = tok.CreateReader();
|
||||||
|
|
||||||
var typeName = lookupTable[(int)tok["type"]];
|
var typeName = lookupTable[(int)tok["$type"]];
|
||||||
var instance = (T)Activator.CreateInstance(Type.GetType(typeName));
|
var instance = (T)Activator.CreateInstance(Type.GetType(typeName));
|
||||||
serializer.Populate(itemReader, instance);
|
serializer.Populate(itemReader, instance);
|
||||||
|
|
||||||
@ -61,7 +61,7 @@ namespace osu.Game.IO.Serialization.Converters
|
|||||||
|
|
||||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||||
{
|
{
|
||||||
var list = (List<T>)value;
|
var list = (IEnumerable<T>)value;
|
||||||
|
|
||||||
var lookupTable = new List<string>();
|
var lookupTable = new List<string>();
|
||||||
var objects = new List<JObject>();
|
var objects = new List<JObject>();
|
||||||
@ -84,16 +84,16 @@ namespace osu.Game.IO.Serialization.Converters
|
|||||||
}
|
}
|
||||||
|
|
||||||
var itemObject = JObject.FromObject(item, serializer);
|
var itemObject = JObject.FromObject(item, serializer);
|
||||||
itemObject.AddFirst(new JProperty("type", typeId));
|
itemObject.AddFirst(new JProperty("$type", typeId));
|
||||||
objects.Add(itemObject);
|
objects.Add(itemObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.WriteStartObject();
|
writer.WriteStartObject();
|
||||||
|
|
||||||
writer.WritePropertyName("lookup_table");
|
writer.WritePropertyName("$lookup_table");
|
||||||
serializer.Serialize(writer, lookupTable);
|
serializer.Serialize(writer, lookupTable);
|
||||||
|
|
||||||
writer.WritePropertyName("items");
|
writer.WritePropertyName("$items");
|
||||||
serializer.Serialize(writer, objects);
|
serializer.Serialize(writer, objects);
|
||||||
|
|
||||||
writer.WriteEndObject();
|
writer.WriteEndObject();
|
||||||
|
@ -62,6 +62,14 @@ namespace osu.Game.Input.Bindings
|
|||||||
store.KeyBindingChanged -= ReloadMappings;
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,24 +18,32 @@ namespace osu.Game.Online.API
|
|||||||
|
|
||||||
public T Result { get; private set; }
|
public T Result { get; private set; }
|
||||||
|
|
||||||
protected APIRequest()
|
|
||||||
{
|
|
||||||
base.Success += () => TriggerSuccess(((OsuJsonWebRequest<T>)WebRequest)?.ResponseObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invoked on successful completion of an API request.
|
/// Invoked on successful completion of an API request.
|
||||||
/// This will be scheduled to the API's internal scheduler (run on update thread automatically).
|
/// This will be scheduled to the API's internal scheduler (run on update thread automatically).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public new event APISuccessHandler<T> Success;
|
public new event APISuccessHandler<T> Success;
|
||||||
|
|
||||||
|
protected override void PostProcess()
|
||||||
|
{
|
||||||
|
base.PostProcess();
|
||||||
|
Result = ((OsuJsonWebRequest<T>)WebRequest)?.ResponseObject;
|
||||||
|
}
|
||||||
|
|
||||||
internal void TriggerSuccess(T result)
|
internal void TriggerSuccess(T result)
|
||||||
{
|
{
|
||||||
if (Result != null)
|
if (Result != null)
|
||||||
throw new InvalidOperationException("Attempted to trigger success more than once");
|
throw new InvalidOperationException("Attempted to trigger success more than once");
|
||||||
|
|
||||||
Result = result;
|
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())
|
if (checkAndScheduleFailure())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
PostProcess();
|
||||||
|
|
||||||
API.Schedule(delegate
|
API.Schedule(delegate
|
||||||
{
|
{
|
||||||
if (cancelled) return;
|
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();
|
Success?.Invoke();
|
||||||
}
|
}
|
||||||
|
@ -1,30 +1,40 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.ComponentModel;
|
|
||||||
using osu.Framework.IO.Network;
|
using osu.Framework.IO.Network;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.Direct;
|
using osu.Game.Overlays.BeatmapListing;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
|
|
||||||
namespace osu.Game.Online.API.Requests
|
namespace osu.Game.Online.API.Requests
|
||||||
{
|
{
|
||||||
public class SearchBeatmapSetsRequest : APIRequest<SearchBeatmapSetsResponse>
|
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 string query;
|
||||||
private readonly RulesetInfo ruleset;
|
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.query = string.IsNullOrEmpty(query) ? string.Empty : System.Uri.EscapeDataString(query);
|
||||||
this.ruleset = ruleset;
|
this.ruleset = ruleset;
|
||||||
this.searchCategory = searchCategory;
|
|
||||||
this.sortCriteria = sortCriteria;
|
SearchCategory = SearchCategory.Any;
|
||||||
this.direction = direction;
|
SortCriteria = SortCriteria.Ranked;
|
||||||
|
SortDirection = SortDirection.Descending;
|
||||||
|
Genre = SearchGenre.Any;
|
||||||
|
Language = SearchLanguage.Any;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override WebRequest CreateWebRequest()
|
protected override WebRequest CreateWebRequest()
|
||||||
@ -35,31 +45,19 @@ namespace osu.Game.Online.API.Requests
|
|||||||
if (ruleset.ID.HasValue)
|
if (ruleset.ID.HasValue)
|
||||||
req.AddParameter("m", ruleset.ID.Value.ToString());
|
req.AddParameter("m", ruleset.ID.Value.ToString());
|
||||||
|
|
||||||
req.AddParameter("s", searchCategory.ToString().ToLowerInvariant());
|
req.AddParameter("s", SearchCategory.ToString().ToLowerInvariant());
|
||||||
req.AddParameter("sort", $"{sortCriteria.ToString().ToLowerInvariant()}_{directionString}");
|
|
||||||
|
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;
|
return req;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string Target => @"beatmapsets/search";
|
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,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ namespace osu.Game.Online.Chat
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Manages everything channel related
|
/// Manages everything channel related
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ChannelManager : PollingComponent
|
public class ChannelManager : PollingComponent, IChannelPostTarget
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The channels the player joins on startup
|
/// The channels the player joins on startup
|
||||||
@ -204,6 +204,10 @@ namespace osu.Game.Online.Chat
|
|||||||
|
|
||||||
switch (command)
|
switch (command)
|
||||||
{
|
{
|
||||||
|
case "np":
|
||||||
|
AddInternal(new NowPlayingCommand());
|
||||||
|
break;
|
||||||
|
|
||||||
case "me":
|
case "me":
|
||||||
if (string.IsNullOrWhiteSpace(content))
|
if (string.IsNullOrWhiteSpace(content))
|
||||||
{
|
{
|
||||||
@ -234,7 +238,7 @@ namespace osu.Game.Online.Chat
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case "help":
|
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;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
19
osu.Game/Online/Chat/IChannelPostTarget.cs
Normal file
19
osu.Game/Online/Chat/IChannelPostTarget.cs
Normal file
@ -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
osu.Game/Online/Chat/NowPlayingCommand.cs
Normal file
55
osu.Game/Online/Chat/NowPlayingCommand.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
|
|
||||||
namespace osu.Game.Online
|
namespace osu.Game.Online
|
||||||
@ -11,7 +11,7 @@ namespace osu.Game.Online
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// A component which requires a constant polling process.
|
/// A component which requires a constant polling process.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class PollingComponent : Component
|
public abstract class PollingComponent : CompositeDrawable // switch away from Component because InternalChildren are used in usages.
|
||||||
{
|
{
|
||||||
private double? lastTimePolled;
|
private double? lastTimePolled;
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user