Merge branch 'master' of github.com:ppy/osu into catch-hyperdash-catcher-colouring
@ -5,6 +5,6 @@
|
|||||||
"version": "3.1.100"
|
"version": "3.1.100"
|
||||||
},
|
},
|
||||||
"msbuild-sdks": {
|
"msbuild-sdks": {
|
||||||
"Microsoft.Build.Traversal": "2.0.32"
|
"Microsoft.Build.Traversal": "2.0.34"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -51,7 +51,7 @@
|
|||||||
<Reference Include="Java.Interop" />
|
<Reference Include="Java.Interop" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.403.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.412.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.403.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.417.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="BenchmarkDotNet" Version="0.12.0" />
|
<PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
|
||||||
<PackageReference Include="nunit" Version="3.12.0" />
|
<PackageReference Include="nunit" Version="3.12.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
|
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
21
osu.Game.Rulesets.Catch.Tests/CatchSkinnableTestScene.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using osu.Game.Rulesets.Catch.Skinning;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Tests
|
||||||
|
{
|
||||||
|
public abstract class CatchSkinnableTestScene : SkinnableTestScene
|
||||||
|
{
|
||||||
|
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||||
|
{
|
||||||
|
typeof(CatchRuleset),
|
||||||
|
typeof(CatchLegacySkinTransformer),
|
||||||
|
};
|
||||||
|
|
||||||
|
protected override Ruleset CreateRulesetForSkinProvider() => new CatchRuleset();
|
||||||
|
}
|
||||||
|
}
|
@ -4,21 +4,21 @@
|
|||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Game.Rulesets.Catch.UI;
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
using osu.Game.Tests.Visual;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Tests
|
namespace osu.Game.Rulesets.Catch.Tests
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestSceneCatcher : SkinnableTestScene
|
public class TestSceneCatcher : CatchSkinnableTestScene
|
||||||
{
|
{
|
||||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[]
|
||||||
{
|
{
|
||||||
typeof(CatcherArea),
|
typeof(CatcherArea),
|
||||||
typeof(CatcherSprite)
|
typeof(CatcherSprite)
|
||||||
};
|
}).ToList();
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
|
@ -17,12 +17,11 @@ using osu.Game.Rulesets.Catch.UI;
|
|||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Tests.Visual;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Tests
|
namespace osu.Game.Rulesets.Catch.Tests
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestSceneCatcherArea : SkinnableTestScene
|
public class TestSceneCatcherArea : CatchSkinnableTestScene
|
||||||
{
|
{
|
||||||
private RulesetInfo catchRuleset;
|
private RulesetInfo catchRuleset;
|
||||||
|
|
||||||
|
@ -3,20 +3,20 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
|
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
|
||||||
using osu.Game.Tests.Visual;
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Tests
|
namespace osu.Game.Rulesets.Catch.Tests
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestSceneFruitObjects : SkinnableTestScene
|
public class TestSceneFruitObjects : CatchSkinnableTestScene
|
||||||
{
|
{
|
||||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[]
|
||||||
{
|
{
|
||||||
typeof(CatchHitObject),
|
typeof(CatchHitObject),
|
||||||
typeof(Fruit),
|
typeof(Fruit),
|
||||||
@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
typeof(DrawableBanana),
|
typeof(DrawableBanana),
|
||||||
typeof(DrawableBananaShower),
|
typeof(DrawableBananaShower),
|
||||||
typeof(Pulp),
|
typeof(Pulp),
|
||||||
};
|
}).ToList();
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
|
@ -4,15 +4,10 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio.Sample;
|
|
||||||
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.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Graphics.Textures;
|
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Audio;
|
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
@ -32,29 +27,113 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
private SkinManager skins { get; set; }
|
private SkinManager skins { get; set; }
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestHyperDashCatcherColour()
|
public void TestDefaultCatcherColour()
|
||||||
|
{
|
||||||
|
var skin = new TestSkin();
|
||||||
|
|
||||||
|
checkHyperDashCatcherColour(skin, Catcher.DEFAULT_HYPER_DASH_COLOUR);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCustomCatcherColour()
|
||||||
|
{
|
||||||
|
var skin = new TestSkin
|
||||||
|
{
|
||||||
|
HyperDashColour = Color4.Goldenrod
|
||||||
|
};
|
||||||
|
|
||||||
|
checkHyperDashCatcherColour(skin, skin.HyperDashColour);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCustomEndGlowColour()
|
||||||
|
{
|
||||||
|
var skin = new TestSkin
|
||||||
|
{
|
||||||
|
HyperDashAfterImageColour = Color4.Lime
|
||||||
|
};
|
||||||
|
|
||||||
|
checkHyperDashCatcherColour(skin, Catcher.DEFAULT_HYPER_DASH_COLOUR, skin.HyperDashAfterImageColour);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCustomEndGlowColourPriority()
|
||||||
|
{
|
||||||
|
var skin = new TestSkin
|
||||||
|
{
|
||||||
|
HyperDashColour = Color4.Goldenrod,
|
||||||
|
HyperDashAfterImageColour = Color4.Lime
|
||||||
|
};
|
||||||
|
|
||||||
|
checkHyperDashCatcherColour(skin, skin.HyperDashColour, skin.HyperDashAfterImageColour);
|
||||||
|
}
|
||||||
|
|
||||||
|
[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 checkHyperDashCatcherColour(ISkin skin, Color4 expectedCatcherColour, Color4? expectedEndGlowColour = null)
|
||||||
{
|
{
|
||||||
CatcherArea catcherArea = null;
|
CatcherArea catcherArea = null;
|
||||||
|
|
||||||
AddStep("setup catcher", () =>
|
AddStep("create hyper-dashing catcher", () =>
|
||||||
{
|
{
|
||||||
Child = setupSkinHierarchy(catcherArea = new CatcherArea
|
Child = setupSkinHierarchy(catcherArea = new CatcherArea
|
||||||
{
|
{
|
||||||
RelativePositionAxes = Axes.None,
|
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Scale = new Vector2(4f),
|
Scale = new Vector2(4f),
|
||||||
}, false, false, false);
|
}, skin);
|
||||||
});
|
|
||||||
|
|
||||||
AddStep("start hyper-dashing", () =>
|
|
||||||
{
|
|
||||||
catcherArea.MovableCatcher.SetHyperDashState(2);
|
catcherArea.MovableCatcher.SetHyperDashState(2);
|
||||||
catcherArea.MovableCatcher.FinishTransforms();
|
catcherArea.MovableCatcher.FinishTransforms();
|
||||||
});
|
});
|
||||||
|
|
||||||
AddAssert("catcher has default hyper-dash colour", () => catcherArea.MovableCatcher.Colour == Color4.OrangeRed);
|
AddAssert("catcher colour is correct", () =>
|
||||||
AddAssert("catcher trails have default hyper-dash colour", () => catcherArea.OfType<Container<CatcherTrailSprite>>().Any(c => c.Colour == Catcher.DefaultHyperDashColour));
|
expectedCatcherColour == Catcher.DEFAULT_HYPER_DASH_COLOUR
|
||||||
|
? catcherArea.MovableCatcher.Colour == Catcher.DEFAULT_CATCHER_HYPER_DASH_COLOUR
|
||||||
|
: catcherArea.MovableCatcher.Colour == expectedCatcherColour);
|
||||||
|
|
||||||
|
AddAssert("catcher trails colours are correct", () => catcherArea.OfType<Container<CatcherTrailSprite>>().Any(c => c.Colour == expectedCatcherColour));
|
||||||
|
AddAssert("catcher end-glow colours are correct", () => catcherArea.OfType<Container<CatcherTrailSprite>>().Any(c => c.Colour == (expectedEndGlowColour ?? expectedCatcherColour)));
|
||||||
|
|
||||||
AddStep("finish hyper-dashing", () =>
|
AddStep("finish hyper-dashing", () =>
|
||||||
{
|
{
|
||||||
@ -62,111 +141,14 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
catcherArea.MovableCatcher.FinishTransforms();
|
catcherArea.MovableCatcher.FinishTransforms();
|
||||||
});
|
});
|
||||||
|
|
||||||
AddAssert("hyper-dash colour cleared from catcher", () => catcherArea.MovableCatcher.Colour == Color4.White);
|
AddAssert("catcher colour returned to white", () => catcherArea.MovableCatcher.Colour == Color4.White);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
private void checkHyperDashFruitColour(ISkin skin, Color4 expectedColour)
|
||||||
public void TestCustomHyperDashCatcherColour()
|
|
||||||
{
|
|
||||||
CatcherArea catcherArea = null;
|
|
||||||
|
|
||||||
AddStep("setup catcher", () =>
|
|
||||||
{
|
|
||||||
Child = setupSkinHierarchy(catcherArea = new CatcherArea
|
|
||||||
{
|
|
||||||
RelativePositionAxes = Axes.None,
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
Scale = new Vector2(4f),
|
|
||||||
}, true, false, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
AddStep("start hyper-dashing", () =>
|
|
||||||
{
|
|
||||||
catcherArea.MovableCatcher.SetHyperDashState(2);
|
|
||||||
catcherArea.MovableCatcher.FinishTransforms();
|
|
||||||
});
|
|
||||||
|
|
||||||
AddAssert("catcher use hyper-dash colour from skin", () => catcherArea.MovableCatcher.Colour == TestSkin.CustomHyperDashColour);
|
|
||||||
AddAssert("catcher trails use hyper-dash colour from skin", () => catcherArea.OfType<Container<CatcherTrailSprite>>().Any(c => c.Colour == TestSkin.CustomHyperDashColour));
|
|
||||||
|
|
||||||
AddStep("clear hyper-dash", () =>
|
|
||||||
{
|
|
||||||
catcherArea.MovableCatcher.SetHyperDashState(1);
|
|
||||||
catcherArea.MovableCatcher.FinishTransforms();
|
|
||||||
});
|
|
||||||
|
|
||||||
AddAssert("hyper-dash colour cleared from catcher", () => catcherArea.MovableCatcher.Colour == Color4.White);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestHyperDashCatcherEndGlowColour()
|
|
||||||
{
|
|
||||||
CatcherArea catcherArea = null;
|
|
||||||
|
|
||||||
AddStep("setup catcher", () =>
|
|
||||||
{
|
|
||||||
Child = setupSkinHierarchy(catcherArea = new CatcherArea
|
|
||||||
{
|
|
||||||
RelativePositionAxes = Axes.None,
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
Scale = new Vector2(4f),
|
|
||||||
}, false, false, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
AddStep("start hyper-dashing", () => catcherArea.MovableCatcher.SetHyperDashState(2));
|
|
||||||
AddAssert("end-glow catcher sprite has default hyper-dash colour", () => catcherArea.OfType<Container<CatcherTrailSprite>>().Any(c => c.Colour == Catcher.DefaultHyperDashColour));
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestCase(true)]
|
|
||||||
[TestCase(false)]
|
|
||||||
public void TestCustomHyperDashCatcherEndGlowColour(bool customHyperDashCatcherColour)
|
|
||||||
{
|
|
||||||
CatcherArea catcherArea = null;
|
|
||||||
|
|
||||||
AddStep("setup catcher", () =>
|
|
||||||
{
|
|
||||||
Child = setupSkinHierarchy(catcherArea = new CatcherArea
|
|
||||||
{
|
|
||||||
RelativePositionAxes = Axes.None,
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
Scale = new Vector2(4f),
|
|
||||||
}, customHyperDashCatcherColour, true, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
AddStep("start hyper-dashing", () => catcherArea.MovableCatcher.SetHyperDashState(2));
|
|
||||||
AddAssert("end-glow catcher sprite use its hyper-dash colour from skin", () => catcherArea.OfType<Container<CatcherTrailSprite>>().Any(c => c.Colour == TestSkin.CustomHyperDashAfterColour));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestCustomHyperDashCatcherEndGlowColourFallback()
|
|
||||||
{
|
|
||||||
CatcherArea catcherArea = null;
|
|
||||||
|
|
||||||
AddStep("setup catcher", () =>
|
|
||||||
{
|
|
||||||
Child = setupSkinHierarchy(catcherArea = new CatcherArea
|
|
||||||
{
|
|
||||||
RelativePositionAxes = Axes.None,
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
Scale = new Vector2(4f),
|
|
||||||
}, true, false, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
AddStep("start hyper-dashing", () => catcherArea.MovableCatcher.SetHyperDashState(2));
|
|
||||||
AddAssert("end-glow catcher sprite colour falls back to catcher colour from skin", () => catcherArea.OfType<Container<CatcherTrailSprite>>().Any(c => c.Colour == TestSkin.CustomHyperDashColour));
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestCase(false)]
|
|
||||||
[TestCase(true)]
|
|
||||||
public void TestHyperDashFruitColour(bool legacyFruit)
|
|
||||||
{
|
{
|
||||||
DrawableFruit drawableFruit = null;
|
DrawableFruit drawableFruit = null;
|
||||||
|
|
||||||
AddStep("setup hyper-dash fruit", () =>
|
AddStep("create hyper-dash fruit", () =>
|
||||||
{
|
{
|
||||||
var fruit = new Fruit { HyperDashTarget = new Banana() };
|
var fruit = new Fruit { HyperDashTarget = new Banana() };
|
||||||
fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||||
@ -176,130 +158,50 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Scale = new Vector2(4f),
|
Scale = new Vector2(4f),
|
||||||
}, false, false, false, legacyFruit);
|
}, skin);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddAssert("hyper-dash fruit has default colour", () =>
|
AddAssert("hyper-dash colour is correct", () => checkLegacyFruitHyperDashColour(drawableFruit, expectedColour));
|
||||||
legacyFruit
|
|
||||||
? checkLegacyFruitHyperDashColour(drawableFruit, Catcher.DefaultHyperDashColour)
|
|
||||||
: checkFruitHyperDashColour(drawableFruit, Catcher.DefaultHyperDashColour));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase(false, true)]
|
private Drawable setupSkinHierarchy(Drawable child, ISkin skin)
|
||||||
[TestCase(false, false)]
|
|
||||||
[TestCase(true, true)]
|
|
||||||
[TestCase(true, false)]
|
|
||||||
public void TestCustomHyperDashFruitColour(bool legacyFruit, bool customCatcherHyperDashColour)
|
|
||||||
{
|
{
|
||||||
DrawableFruit drawableFruit = null;
|
var legacySkinProvider = new SkinProvidingContainer(skins.GetSkin(DefaultLegacySkin.Info));
|
||||||
|
var testSkinProvider = new SkinProvidingContainer(skin);
|
||||||
|
var legacySkinTransformer = new SkinProvidingContainer(new CatchLegacySkinTransformer(testSkinProvider));
|
||||||
|
|
||||||
AddStep("setup hyper-dash fruit", () =>
|
return legacySkinProvider
|
||||||
{
|
.WithChild(testSkinProvider
|
||||||
var fruit = new Fruit { HyperDashTarget = new Banana() };
|
.WithChild(legacySkinTransformer
|
||||||
fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
.WithChild(child)));
|
||||||
|
|
||||||
Child = setupSkinHierarchy(drawableFruit = new DrawableFruit(fruit)
|
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
Scale = new Vector2(4f),
|
|
||||||
}, customCatcherHyperDashColour, false, true, legacyFruit);
|
|
||||||
});
|
|
||||||
|
|
||||||
AddAssert("hyper-dash fruit use fruit colour from skin", () =>
|
|
||||||
legacyFruit
|
|
||||||
? checkLegacyFruitHyperDashColour(drawableFruit, TestSkin.CustomHyperDashFruitColour)
|
|
||||||
: checkFruitHyperDashColour(drawableFruit, TestSkin.CustomHyperDashFruitColour));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase(false)]
|
|
||||||
[TestCase(true)]
|
|
||||||
public void TestCustomHyperDashFruitColourFallback(bool legacyFruit)
|
|
||||||
{
|
|
||||||
DrawableFruit drawableFruit = null;
|
|
||||||
|
|
||||||
AddStep("setup 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),
|
|
||||||
}, true, false, false, legacyFruit);
|
|
||||||
});
|
|
||||||
|
|
||||||
AddAssert("hyper-dash fruit colour falls back to catcher colour from skin", () =>
|
|
||||||
legacyFruit
|
|
||||||
? checkLegacyFruitHyperDashColour(drawableFruit, TestSkin.CustomHyperDashColour)
|
|
||||||
: checkFruitHyperDashColour(drawableFruit, TestSkin.CustomHyperDashColour));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Drawable setupSkinHierarchy(Drawable child, bool customCatcherColour, bool customAfterColour, bool customFruitColour, bool legacySkin = true)
|
|
||||||
{
|
|
||||||
var testSkinProvider = new SkinProvidingContainer(new TestSkin(customCatcherColour, customAfterColour, customFruitColour));
|
|
||||||
|
|
||||||
if (legacySkin)
|
|
||||||
{
|
|
||||||
var legacySkinProvider = new SkinProvidingContainer(skins.GetSkin(DefaultLegacySkin.Info));
|
|
||||||
var legacySkinTransformer = new SkinProvidingContainer(new CatchLegacySkinTransformer(testSkinProvider));
|
|
||||||
|
|
||||||
return legacySkinProvider
|
|
||||||
.WithChild(testSkinProvider
|
|
||||||
.WithChild(legacySkinTransformer
|
|
||||||
.WithChild(child)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return testSkinProvider.WithChild(child);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool checkFruitHyperDashColour(DrawableFruit fruit, Color4 expectedColour) =>
|
|
||||||
fruit.ChildrenOfType<SkinnableDrawable>().First().Drawable.ChildrenOfType<Circle>().Single(c => c.BorderColour == expectedColour).Any(d => d.Colour == expectedColour);
|
|
||||||
|
|
||||||
private bool checkLegacyFruitHyperDashColour(DrawableFruit fruit, Color4 expectedColour) =>
|
private bool checkLegacyFruitHyperDashColour(DrawableFruit fruit, Color4 expectedColour) =>
|
||||||
fruit.ChildrenOfType<SkinnableDrawable>().First().Drawable.ChildrenOfType<Sprite>().Any(c => c.Colour == expectedColour);
|
fruit.ChildrenOfType<SkinnableDrawable>().First().Drawable.ChildrenOfType<Sprite>().Any(c => c.Colour == expectedColour);
|
||||||
|
|
||||||
private class TestSkin : ISkin
|
private class TestSkin : LegacySkin
|
||||||
{
|
{
|
||||||
public static Color4 CustomHyperDashColour { get; } = Color4.Goldenrod;
|
public Color4 HyperDashColour
|
||||||
public static Color4 CustomHyperDashFruitColour { get; } = Color4.Cyan;
|
|
||||||
public static Color4 CustomHyperDashAfterColour { get; } = Color4.Lime;
|
|
||||||
|
|
||||||
private readonly bool customCatcherColour;
|
|
||||||
private readonly bool customAfterColour;
|
|
||||||
private readonly bool customFruitColour;
|
|
||||||
|
|
||||||
public TestSkin(bool customCatcherColour, bool customAfterColour, bool customFruitColour)
|
|
||||||
{
|
{
|
||||||
this.customCatcherColour = customCatcherColour;
|
get => Configuration.CustomColours[CatchSkinColour.HyperDash.ToString()];
|
||||||
this.customAfterColour = customAfterColour;
|
set => Configuration.CustomColours[CatchSkinColour.HyperDash.ToString()] = value;
|
||||||
this.customFruitColour = customFruitColour;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Drawable GetDrawableComponent(ISkinComponent component) => null;
|
public Color4 HyperDashAfterImageColour
|
||||||
|
|
||||||
public Texture GetTexture(string componentName) => null;
|
|
||||||
|
|
||||||
public SampleChannel GetSample(ISampleInfo sampleInfo) => null;
|
|
||||||
|
|
||||||
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
|
|
||||||
{
|
{
|
||||||
if (lookup is CatchSkinColour config)
|
get => Configuration.CustomColours[CatchSkinColour.HyperDashAfterImage.ToString()];
|
||||||
{
|
set => Configuration.CustomColours[CatchSkinColour.HyperDashAfterImage.ToString()] = value;
|
||||||
if (config == CatchSkinColour.HyperDash && customCatcherColour)
|
}
|
||||||
return SkinUtils.As<TValue>(new Bindable<Color4>(CustomHyperDashColour));
|
|
||||||
|
|
||||||
if (config == CatchSkinColour.HyperDashFruit && customFruitColour)
|
public Color4 HyperDashFruitColour
|
||||||
return SkinUtils.As<TValue>(new Bindable<Color4>(CustomHyperDashFruitColour));
|
{
|
||||||
|
get => Configuration.CustomColours[CatchSkinColour.HyperDashFruit.ToString()];
|
||||||
|
set => Configuration.CustomColours[CatchSkinColour.HyperDashFruit.ToString()] = value;
|
||||||
|
}
|
||||||
|
|
||||||
if (config == CatchSkinColour.HyperDashAfterImage && customAfterColour)
|
public TestSkin()
|
||||||
return SkinUtils.As<TValue>(new Bindable<Color4>(CustomHyperDashAfterColour));
|
: base(new SkinInfo(), null, null, string.Empty)
|
||||||
}
|
{
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -70,6 +70,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
|
|
||||||
public float DisplayRadius => DrawSize.X / 2 * Scale.X * HitObject.Scale;
|
public float DisplayRadius => DrawSize.X / 2 * Scale.X * HitObject.Scale;
|
||||||
|
|
||||||
|
protected override float SamplePlaybackPosition => HitObject.X;
|
||||||
|
|
||||||
protected DrawableCatchHitObject(CatchHitObject hitObject)
|
protected DrawableCatchHitObject(CatchHitObject hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
{
|
{
|
||||||
|
@ -7,10 +7,8 @@ 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.Skinning;
|
|
||||||
using osu.Game.Rulesets.Catch.UI;
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Skinning;
|
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||||
@ -34,7 +32,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(DrawableHitObject drawableObject, ISkinSource skin)
|
private void load(DrawableHitObject drawableObject)
|
||||||
{
|
{
|
||||||
DrawableCatchHitObject drawableCatchObject = (DrawableCatchHitObject)drawableObject;
|
DrawableCatchHitObject drawableCatchObject = (DrawableCatchHitObject)drawableObject;
|
||||||
hitObject = drawableCatchObject.HitObject;
|
hitObject = drawableCatchObject.HitObject;
|
||||||
@ -63,10 +61,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
var hyperDashColour =
|
|
||||||
skin.GetHyperDashFruitColour()?.Value ??
|
|
||||||
Catcher.DefaultHyperDashColour;
|
|
||||||
|
|
||||||
if (hitObject.HyperDash)
|
if (hitObject.HyperDash)
|
||||||
{
|
{
|
||||||
AddInternal(new Circle
|
AddInternal(new Circle
|
||||||
@ -74,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 = hyperDashColour,
|
BorderColour = Catcher.DEFAULT_HYPER_DASH_COLOUR,
|
||||||
BorderThickness = 12f * RADIUS_ADJUST,
|
BorderThickness = 12f * RADIUS_ADJUST,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
@ -84,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 = hyperDashColour,
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,12 +6,12 @@ namespace osu.Game.Rulesets.Catch.Skinning
|
|||||||
public enum CatchSkinColour
|
public enum CatchSkinColour
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The colour to be used for the catcher while on hyper-dashing state.
|
/// The colour to be used for the catcher while in hyper-dashing state.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
HyperDash,
|
HyperDash,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The colour to be used for hyper-dash fruits.
|
/// The colour to be used for fruits that grant the catcher the ability to hyper-dash.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
HyperDashFruit,
|
HyperDashFruit,
|
||||||
|
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Game.Skinning;
|
|
||||||
using osuTK.Graphics;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Skinning
|
|
||||||
{
|
|
||||||
internal static class CatchSkinExtensions
|
|
||||||
{
|
|
||||||
public static IBindable<Color4> GetHyperDashCatcherColour(this ISkin skin)
|
|
||||||
=> skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDash);
|
|
||||||
|
|
||||||
public static IBindable<Color4> GetHyperDashCatcherAfterImageColour(this ISkin skin)
|
|
||||||
=> skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDashAfterImage) ??
|
|
||||||
skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDash);
|
|
||||||
|
|
||||||
public static IBindable<Color4> GetHyperDashFruitColour(this ISkin skin)
|
|
||||||
=> skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDashFruit) ??
|
|
||||||
skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDash);
|
|
||||||
}
|
|
||||||
}
|
|
@ -56,14 +56,16 @@ namespace osu.Game.Rulesets.Catch.Skinning
|
|||||||
{
|
{
|
||||||
var hyperDash = new Sprite
|
var hyperDash = new Sprite
|
||||||
{
|
{
|
||||||
Texture = skin.GetTexture(lookupName),
|
|
||||||
Colour = skin.GetHyperDashFruitColour()?.Value ?? Catcher.DefaultHyperDashColour,
|
|
||||||
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);
|
||||||
|
@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
{
|
{
|
||||||
public class Catcher : SkinReloadableDrawable, IKeyBindingHandler<CatchAction>
|
public class Catcher : SkinReloadableDrawable, IKeyBindingHandler<CatchAction>
|
||||||
{
|
{
|
||||||
public static Color4 DefaultHyperDashColour { get; } = Color4.Red;
|
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.
|
||||||
@ -92,8 +92,8 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
private CatcherSprite currentCatcher;
|
private CatcherSprite currentCatcher;
|
||||||
|
|
||||||
private Color4 hyperDashColour = DefaultHyperDashColour;
|
private Color4 hyperDashColour = DEFAULT_HYPER_DASH_COLOUR;
|
||||||
private Color4 hyperDashEndGlowColour = DefaultHyperDashColour;
|
private Color4 hyperDashEndGlowColour = DEFAULT_HYPER_DASH_COLOUR;
|
||||||
|
|
||||||
private int currentDirection;
|
private int currentDirection;
|
||||||
|
|
||||||
@ -390,8 +390,14 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
{
|
{
|
||||||
base.SkinChanged(skin, allowFallback);
|
base.SkinChanged(skin, allowFallback);
|
||||||
|
|
||||||
hyperDashColour = skin.GetHyperDashCatcherColour()?.Value ?? DefaultHyperDashColour;
|
hyperDashColour =
|
||||||
hyperDashEndGlowColour = skin.GetHyperDashCatcherAfterImageColour()?.Value ?? DefaultHyperDashColour;
|
skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDash)?.Value ??
|
||||||
|
DEFAULT_HYPER_DASH_COLOUR;
|
||||||
|
|
||||||
|
hyperDashEndGlowColour =
|
||||||
|
skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDashAfterImage)?.Value ??
|
||||||
|
hyperDashColour;
|
||||||
|
|
||||||
updateCatcherColour(HyperDashing);
|
updateCatcherColour(HyperDashing);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
51
osu.Game.Rulesets.Mania.Tests/ManiaLegacyReplayTest.cs
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Mania.Replays;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Tests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class ManiaLegacyReplayTest
|
||||||
|
{
|
||||||
|
[TestCase(ManiaAction.Key1)]
|
||||||
|
[TestCase(ManiaAction.Key1, ManiaAction.Key2)]
|
||||||
|
[TestCase(ManiaAction.Special1)]
|
||||||
|
[TestCase(ManiaAction.Key8)]
|
||||||
|
public void TestEncodeDecodeSingleStage(params ManiaAction[] actions)
|
||||||
|
{
|
||||||
|
var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 9 });
|
||||||
|
|
||||||
|
var frame = new ManiaReplayFrame(0, actions);
|
||||||
|
var legacyFrame = frame.ToLegacy(beatmap);
|
||||||
|
|
||||||
|
var decodedFrame = new ManiaReplayFrame();
|
||||||
|
decodedFrame.FromLegacy(legacyFrame, beatmap);
|
||||||
|
|
||||||
|
Assert.That(decodedFrame.Actions, Is.EquivalentTo(frame.Actions));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase(ManiaAction.Key1)]
|
||||||
|
[TestCase(ManiaAction.Key1, ManiaAction.Key2)]
|
||||||
|
[TestCase(ManiaAction.Special1)]
|
||||||
|
[TestCase(ManiaAction.Special2)]
|
||||||
|
[TestCase(ManiaAction.Special1, ManiaAction.Special2)]
|
||||||
|
[TestCase(ManiaAction.Special1, ManiaAction.Key5)]
|
||||||
|
[TestCase(ManiaAction.Key8)]
|
||||||
|
public void TestEncodeDecodeDualStage(params ManiaAction[] actions)
|
||||||
|
{
|
||||||
|
var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 5 });
|
||||||
|
beatmap.Stages.Add(new StageDefinition { Columns = 5 });
|
||||||
|
|
||||||
|
var frame = new ManiaReplayFrame(0, actions);
|
||||||
|
var legacyFrame = frame.ToLegacy(beatmap);
|
||||||
|
|
||||||
|
var decodedFrame = new ManiaReplayFrame();
|
||||||
|
decodedFrame.FromLegacy(legacyFrame, beatmap);
|
||||||
|
|
||||||
|
Assert.That(decodedFrame.Actions, Is.EquivalentTo(frame.Actions));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 1.9 KiB |
@ -1,12 +1,15 @@
|
|||||||
// 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 NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Rulesets.Mania.Skinning;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
using osu.Game.Rulesets.UI.Scrolling.Algorithms;
|
using osu.Game.Rulesets.UI.Scrolling.Algorithms;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
@ -24,6 +27,15 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
|||||||
[Cached(Type = typeof(IScrollingInfo))]
|
[Cached(Type = typeof(IScrollingInfo))]
|
||||||
private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo();
|
private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo();
|
||||||
|
|
||||||
|
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||||
|
{
|
||||||
|
typeof(ManiaRuleset),
|
||||||
|
typeof(ManiaLegacySkinTransformer),
|
||||||
|
typeof(ManiaSettingsSubsection)
|
||||||
|
};
|
||||||
|
|
||||||
|
protected override Ruleset CreateRulesetForSkinProvider() => new ManiaRuleset();
|
||||||
|
|
||||||
protected ManiaSkinnableTestScene()
|
protected ManiaSkinnableTestScene()
|
||||||
{
|
{
|
||||||
scrollingInfo.Direction.Value = ScrollingDirection.Down;
|
scrollingInfo.Direction.Value = ScrollingDirection.Down;
|
||||||
|
@ -10,11 +10,10 @@ using osu.Game.Rulesets.Judgements;
|
|||||||
using osu.Game.Rulesets.Mania.UI;
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Tests.Visual;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||||
{
|
{
|
||||||
public class TestSceneDrawableJudgement : SkinnableTestScene
|
public class TestSceneDrawableJudgement : ManiaSkinnableTestScene
|
||||||
{
|
{
|
||||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||||
{
|
{
|
||||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
|||||||
|
|
||||||
return new ManiaInputManager(new ManiaRuleset().RulesetInfo, 4)
|
return new ManiaInputManager(new ManiaRuleset().RulesetInfo, 4)
|
||||||
{
|
{
|
||||||
Child = new ManiaStage(0, new StageDefinition { Columns = 4 }, ref normalAction, ref specialAction)
|
Child = new Stage(0, new StageDefinition { Columns = 4 }, ref normalAction, ref specialAction)
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
// 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.Rulesets.Mania.Skinning;
|
||||||
|
using osu.Game.Rulesets.Mania.UI.Components;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||||
|
{
|
||||||
|
public class TestSceneStageBackground : ManiaSkinnableTestScene
|
||||||
|
{
|
||||||
|
public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[]
|
||||||
|
{
|
||||||
|
typeof(DefaultStageBackground),
|
||||||
|
typeof(LegacyStageBackground),
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
SetContents(() => new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageBackground), _ => new DefaultStageBackground())
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Width = 0.5f,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
// 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.Rulesets.Mania.Skinning;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||||
|
{
|
||||||
|
public class TestSceneStageForeground : ManiaSkinnableTestScene
|
||||||
|
{
|
||||||
|
public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[]
|
||||||
|
{
|
||||||
|
typeof(LegacyStageForeground),
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
SetContents(() => new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageForeground), _ => null)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Width = 0.5f,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -28,7 +28,9 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
{
|
{
|
||||||
typeof(Column),
|
typeof(Column),
|
||||||
typeof(ColumnBackground),
|
typeof(ColumnBackground),
|
||||||
typeof(ColumnHitObjectArea)
|
typeof(ColumnHitObjectArea),
|
||||||
|
typeof(DefaultKeyArea),
|
||||||
|
typeof(DefaultHitTarget)
|
||||||
};
|
};
|
||||||
|
|
||||||
[Cached(typeof(IReadOnlyList<Mod>))]
|
[Cached(typeof(IReadOnlyList<Mod>))]
|
||||||
|
@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
[Cached(typeof(IReadOnlyList<Mod>))]
|
[Cached(typeof(IReadOnlyList<Mod>))]
|
||||||
private IReadOnlyList<Mod> mods { get; set; } = Array.Empty<Mod>();
|
private IReadOnlyList<Mod> mods { get; set; } = Array.Empty<Mod>();
|
||||||
|
|
||||||
private readonly List<ManiaStage> stages = new List<ManiaStage>();
|
private readonly List<Stage> stages = new List<Stage>();
|
||||||
|
|
||||||
private FillFlowContainer<ScrollingTestContainer> fill;
|
private FillFlowContainer<ScrollingTestContainer> fill;
|
||||||
|
|
||||||
@ -81,9 +81,9 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
AddAssert("check bar anchors", () => barsInStageAreAnchored(stages[1], Anchor.TopCentre));
|
AddAssert("check bar anchors", () => barsInStageAreAnchored(stages[1], Anchor.TopCentre));
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool notesInStageAreAnchored(ManiaStage stage, Anchor anchor) => stage.Columns.SelectMany(c => c.AllHitObjects).All(o => o.Anchor == anchor);
|
private bool notesInStageAreAnchored(Stage stage, Anchor anchor) => stage.Columns.SelectMany(c => c.AllHitObjects).All(o => o.Anchor == anchor);
|
||||||
|
|
||||||
private bool barsInStageAreAnchored(ManiaStage stage, Anchor anchor) => stage.AllHitObjects.Where(obj => obj is DrawableBarLine).All(o => o.Anchor == anchor);
|
private bool barsInStageAreAnchored(Stage stage, Anchor anchor) => stage.AllHitObjects.Where(obj => obj is DrawableBarLine).All(o => o.Anchor == anchor);
|
||||||
|
|
||||||
private void createNote()
|
private void createNote()
|
||||||
{
|
{
|
||||||
@ -133,7 +133,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
{
|
{
|
||||||
var specialAction = ManiaAction.Special1;
|
var specialAction = ManiaAction.Special1;
|
||||||
|
|
||||||
var stage = new ManiaStage(0, new StageDefinition { Columns = 2 }, ref action, ref specialAction);
|
var stage = new Stage(0, new StageDefinition { Columns = 2 }, ref action, ref specialAction);
|
||||||
stages.Add(stage);
|
stages.Add(stage);
|
||||||
|
|
||||||
return new ScrollingTestContainer(direction)
|
return new ScrollingTestContainer(direction)
|
||||||
|
@ -45,6 +45,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
new Container
|
new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
BorderThickness = 1,
|
BorderThickness = 1,
|
||||||
BorderColour = colours.Yellow,
|
BorderColour = colours.Yellow,
|
||||||
Child = new Box
|
Child = new Box
|
||||||
|
@ -39,6 +39,8 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
HoldNoteHead,
|
HoldNoteHead,
|
||||||
HoldNoteTail,
|
HoldNoteTail,
|
||||||
HoldNoteBody,
|
HoldNoteBody,
|
||||||
HitExplosion
|
HitExplosion,
|
||||||
|
StageBackground,
|
||||||
|
StageForeground,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||||
{
|
{
|
||||||
@ -24,6 +25,20 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
|
|
||||||
protected readonly IBindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>();
|
protected readonly IBindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>();
|
||||||
|
|
||||||
|
[Resolved(canBeNull: true)]
|
||||||
|
private ManiaPlayfield playfield { get; set; }
|
||||||
|
|
||||||
|
protected override float SamplePlaybackPosition
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (playfield == null)
|
||||||
|
return base.SamplePlaybackPosition;
|
||||||
|
|
||||||
|
return (float)HitObject.Column / playfield.TotalColumns;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected DrawableManiaHitObject(ManiaHitObject hitObject)
|
protected DrawableManiaHitObject(ManiaHitObject hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
{
|
{
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
// 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 System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Replays.Legacy;
|
using osu.Game.Replays.Legacy;
|
||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Mania.Replays
|
|||||||
|
|
||||||
while (activeColumns > 0)
|
while (activeColumns > 0)
|
||||||
{
|
{
|
||||||
var isSpecial = maniaBeatmap.Stages.First().IsSpecialColumn(counter);
|
bool isSpecial = isColumnAtIndexSpecial(maniaBeatmap, counter);
|
||||||
|
|
||||||
if ((activeColumns & 1) > 0)
|
if ((activeColumns & 1) > 0)
|
||||||
Actions.Add(isSpecial ? specialAction : normalAction);
|
Actions.Add(isSpecial ? specialAction : normalAction);
|
||||||
@ -58,33 +58,87 @@ namespace osu.Game.Rulesets.Mania.Replays
|
|||||||
|
|
||||||
int keys = 0;
|
int keys = 0;
|
||||||
|
|
||||||
var specialColumns = new List<int>();
|
|
||||||
|
|
||||||
for (int i = 0; i < maniaBeatmap.TotalColumns; i++)
|
|
||||||
{
|
|
||||||
if (maniaBeatmap.Stages.First().IsSpecialColumn(i))
|
|
||||||
specialColumns.Add(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var action in Actions)
|
foreach (var action in Actions)
|
||||||
{
|
{
|
||||||
switch (action)
|
switch (action)
|
||||||
{
|
{
|
||||||
case ManiaAction.Special1:
|
case ManiaAction.Special1:
|
||||||
keys |= 1 << specialColumns[0];
|
keys |= 1 << getSpecialColumnIndex(maniaBeatmap, 0);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ManiaAction.Special2:
|
case ManiaAction.Special2:
|
||||||
keys |= 1 << specialColumns[1];
|
keys |= 1 << getSpecialColumnIndex(maniaBeatmap, 1);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
keys |= 1 << (action - ManiaAction.Key1);
|
// the index in lazer, which doesn't include special keys.
|
||||||
|
int nonSpecialKeyIndex = action - ManiaAction.Key1;
|
||||||
|
|
||||||
|
// the index inclusive of special keys.
|
||||||
|
int overallIndex = 0;
|
||||||
|
|
||||||
|
// iterate to find the index including special keys.
|
||||||
|
for (; overallIndex < maniaBeatmap.TotalColumns; overallIndex++)
|
||||||
|
{
|
||||||
|
// skip over special columns.
|
||||||
|
if (isColumnAtIndexSpecial(maniaBeatmap, overallIndex))
|
||||||
|
continue;
|
||||||
|
// found a non-special column to use.
|
||||||
|
if (nonSpecialKeyIndex == 0)
|
||||||
|
break;
|
||||||
|
// found a non-special column but not ours.
|
||||||
|
nonSpecialKeyIndex--;
|
||||||
|
}
|
||||||
|
|
||||||
|
keys |= 1 << overallIndex;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new LegacyReplayFrame(Time, keys, null, ReplayButtonState.None);
|
return new LegacyReplayFrame(Time, keys, null, ReplayButtonState.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Find the overall index (across all stages) for a specified special key.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="maniaBeatmap">The beatmap.</param>
|
||||||
|
/// <param name="specialOffset">The special key offset (0 is S1).</param>
|
||||||
|
/// <returns>The overall index for the special column.</returns>
|
||||||
|
private int getSpecialColumnIndex(ManiaBeatmap maniaBeatmap, int specialOffset)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < maniaBeatmap.TotalColumns; i++)
|
||||||
|
{
|
||||||
|
if (isColumnAtIndexSpecial(maniaBeatmap, i))
|
||||||
|
{
|
||||||
|
if (specialOffset == 0)
|
||||||
|
return i;
|
||||||
|
|
||||||
|
specialOffset--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ArgumentException("Special key index is too high.", nameof(specialOffset));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check whether the column at an overall index (across all stages) is a special column.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="beatmap">The beatmap.</param>
|
||||||
|
/// <param name="index">The overall index to check.</param>
|
||||||
|
private bool isColumnAtIndexSpecial(ManiaBeatmap beatmap, int index)
|
||||||
|
{
|
||||||
|
foreach (var stage in beatmap.Stages)
|
||||||
|
{
|
||||||
|
if (index >= stage.Columns)
|
||||||
|
{
|
||||||
|
index -= stage.Columns;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return stage.IsSpecialColumn(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ArgumentException("Column index is too high.", nameof(index));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,12 +50,18 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
|||||||
Color4 lineColour = GetManiaSkinConfig<Color4>(skin, LegacyManiaSkinConfigurationLookups.ColumnLineColour)?.Value
|
Color4 lineColour = GetManiaSkinConfig<Color4>(skin, LegacyManiaSkinConfigurationLookups.ColumnLineColour)?.Value
|
||||||
?? Color4.White;
|
?? Color4.White;
|
||||||
|
|
||||||
|
Color4 backgroundColour = GetManiaSkinConfig<Color4>(skin, LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour)?.Value
|
||||||
|
?? Color4.Black;
|
||||||
|
|
||||||
|
Color4 lightColour = GetManiaSkinConfig<Color4>(skin, LegacyManiaSkinConfigurationLookups.ColumnLightColour)?.Value
|
||||||
|
?? Color4.White;
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
new Box
|
new Box
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = Color4.Black
|
Colour = backgroundColour
|
||||||
},
|
},
|
||||||
new Box
|
new Box
|
||||||
{
|
{
|
||||||
@ -82,6 +88,7 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
|||||||
{
|
{
|
||||||
Anchor = Anchor.BottomCentre,
|
Anchor = Anchor.BottomCentre,
|
||||||
Origin = Anchor.BottomCentre,
|
Origin = Anchor.BottomCentre,
|
||||||
|
Colour = lightColour,
|
||||||
Texture = skin.GetTexture(lightImage),
|
Texture = skin.GetTexture(lightImage),
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Width = 1,
|
Width = 1,
|
||||||
|
@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
|||||||
if (tmp is IFramedAnimation tmpAnimation && tmpAnimation.FrameCount > 0)
|
if (tmp is IFramedAnimation tmpAnimation && tmpAnimation.FrameCount > 0)
|
||||||
frameLength = Math.Max(1000 / 60.0, 170.0 / tmpAnimation.FrameCount);
|
frameLength = Math.Max(1000 / 60.0, 170.0 / tmpAnimation.FrameCount);
|
||||||
|
|
||||||
explosion = skin.GetAnimation(imageName, true, false, startAtCurrentTime: true, frameLength: frameLength).With(d =>
|
explosion = skin.GetAnimation(imageName, true, false, frameLength: frameLength).With(d =>
|
||||||
{
|
{
|
||||||
if (d == null)
|
if (d == null)
|
||||||
return;
|
return;
|
||||||
|
@ -10,6 +10,7 @@ using osu.Framework.Graphics.Sprites;
|
|||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Skinning
|
namespace osu.Game.Rulesets.Mania.Skinning
|
||||||
{
|
{
|
||||||
@ -33,6 +34,9 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
|||||||
bool showJudgementLine = GetManiaSkinConfig<bool>(skin, LegacyManiaSkinConfigurationLookups.ShowJudgementLine)?.Value
|
bool showJudgementLine = GetManiaSkinConfig<bool>(skin, LegacyManiaSkinConfigurationLookups.ShowJudgementLine)?.Value
|
||||||
?? true;
|
?? true;
|
||||||
|
|
||||||
|
Color4 lineColour = GetManiaSkinConfig<Color4>(skin, LegacyManiaSkinConfigurationLookups.JudgementLineColour)?.Value
|
||||||
|
?? Color4.White;
|
||||||
|
|
||||||
InternalChild = directionContainer = new Container
|
InternalChild = directionContainer = new Container
|
||||||
{
|
{
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
@ -52,6 +56,7 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
|||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Height = 1,
|
Height = 1,
|
||||||
|
Colour = lineColour,
|
||||||
Alpha = showJudgementLine ? 0.9f : 0
|
Alpha = showJudgementLine ? 0.9f : 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,8 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
|||||||
private Container directionContainer;
|
private Container directionContainer;
|
||||||
private Sprite noteSprite;
|
private Sprite noteSprite;
|
||||||
|
|
||||||
|
private float? minimumColumnWidth;
|
||||||
|
|
||||||
public LegacyNotePiece()
|
public LegacyNotePiece()
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
@ -29,6 +31,8 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(ISkinSource skin, IScrollingInfo scrollingInfo)
|
private void load(ISkinSource skin, IScrollingInfo scrollingInfo)
|
||||||
{
|
{
|
||||||
|
minimumColumnWidth = skin.GetConfig<ManiaSkinConfigurationLookup, float>(new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.MinimumColumnWidth))?.Value;
|
||||||
|
|
||||||
InternalChild = directionContainer = new Container
|
InternalChild = directionContainer = new Container
|
||||||
{
|
{
|
||||||
Origin = Anchor.BottomCentre,
|
Origin = Anchor.BottomCentre,
|
||||||
@ -47,8 +51,10 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
|||||||
|
|
||||||
if (noteSprite.Texture != null)
|
if (noteSprite.Texture != null)
|
||||||
{
|
{
|
||||||
var scale = DrawWidth / noteSprite.Texture.DisplayWidth;
|
// The height is scaled to the minimum column width, if provided.
|
||||||
noteSprite.Scale = new Vector2(scale);
|
float minimumWidth = minimumColumnWidth ?? DrawWidth;
|
||||||
|
|
||||||
|
noteSprite.Scale = Vector2.Divide(new Vector2(DrawWidth, minimumWidth), noteSprite.Texture.DisplayWidth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
61
osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.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 osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Skinning
|
||||||
|
{
|
||||||
|
public class LegacyStageBackground : LegacyManiaElement
|
||||||
|
{
|
||||||
|
private Drawable leftSprite;
|
||||||
|
private Drawable rightSprite;
|
||||||
|
|
||||||
|
public LegacyStageBackground()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(ISkinSource skin)
|
||||||
|
{
|
||||||
|
string leftImage = GetManiaSkinConfig<string>(skin, LegacyManiaSkinConfigurationLookups.LeftStageImage)?.Value
|
||||||
|
?? "mania-stage-left";
|
||||||
|
|
||||||
|
string rightImage = GetManiaSkinConfig<string>(skin, LegacyManiaSkinConfigurationLookups.RightStageImage)?.Value
|
||||||
|
?? "mania-stage-right";
|
||||||
|
|
||||||
|
InternalChildren = new[]
|
||||||
|
{
|
||||||
|
leftSprite = new Sprite
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopLeft,
|
||||||
|
Origin = Anchor.TopRight,
|
||||||
|
X = 0.05f,
|
||||||
|
Texture = skin.GetTexture(leftImage),
|
||||||
|
},
|
||||||
|
rightSprite = new Sprite
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopRight,
|
||||||
|
Origin = Anchor.TopLeft,
|
||||||
|
X = -0.05f,
|
||||||
|
Texture = skin.GetTexture(rightImage)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
if (leftSprite?.Height > 0)
|
||||||
|
leftSprite.Scale = new Vector2(DrawHeight / leftSprite.Height);
|
||||||
|
|
||||||
|
if (rightSprite?.Height > 0)
|
||||||
|
rightSprite.Scale = new Vector2(DrawHeight / rightSprite.Height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
56
osu.Game.Rulesets.Mania/Skinning/LegacyStageForeground.cs
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Skinning
|
||||||
|
{
|
||||||
|
public class LegacyStageForeground : LegacyManiaElement
|
||||||
|
{
|
||||||
|
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
||||||
|
|
||||||
|
private Drawable sprite;
|
||||||
|
|
||||||
|
public LegacyStageForeground()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(ISkinSource skin, IScrollingInfo scrollingInfo)
|
||||||
|
{
|
||||||
|
string bottomImage = GetManiaSkinConfig<string>(skin, LegacyManiaSkinConfigurationLookups.BottomStageImage)?.Value
|
||||||
|
?? "mania-stage-bottom";
|
||||||
|
|
||||||
|
sprite = skin.GetAnimation(bottomImage, true, true)?.With(d =>
|
||||||
|
{
|
||||||
|
if (d == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
d.Scale = new Vector2(1.6f);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (sprite != null)
|
||||||
|
InternalChild = sprite;
|
||||||
|
|
||||||
|
direction.BindTo(scrollingInfo.Direction);
|
||||||
|
direction.BindValueChanged(onDirectionChanged, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
|
||||||
|
{
|
||||||
|
if (sprite == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (direction.NewValue == ScrollingDirection.Up)
|
||||||
|
sprite.Anchor = sprite.Origin = Anchor.TopCentre;
|
||||||
|
else
|
||||||
|
sprite.Anchor = sprite.Origin = Anchor.BottomCentre;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
|||||||
{
|
{
|
||||||
isLegacySkin = new Lazy<bool>(() => source.GetConfig<LegacySkinConfiguration.LegacySetting, decimal>(LegacySkinConfiguration.LegacySetting.Version) != null);
|
isLegacySkin = new Lazy<bool>(() => source.GetConfig<LegacySkinConfiguration.LegacySetting, decimal>(LegacySkinConfiguration.LegacySetting.Version) != null);
|
||||||
hasKeyTexture = new Lazy<bool>(() => source.GetAnimation(
|
hasKeyTexture = new Lazy<bool>(() => source.GetAnimation(
|
||||||
source.GetConfig<ManiaSkinConfigurationLookup, string>(
|
GetConfig<ManiaSkinConfigurationLookup, string>(
|
||||||
new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.KeyImage, 0))?.Value
|
new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.KeyImage, 0))?.Value
|
||||||
?? "mania-key1", true, true) != null);
|
?? "mania-key1", true, true) != null);
|
||||||
}
|
}
|
||||||
@ -81,6 +81,12 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
|||||||
|
|
||||||
case ManiaSkinComponents.HitExplosion:
|
case ManiaSkinComponents.HitExplosion:
|
||||||
return new LegacyHitExplosion();
|
return new LegacyHitExplosion();
|
||||||
|
|
||||||
|
case ManiaSkinComponents.StageBackground:
|
||||||
|
return new LegacyStageBackground();
|
||||||
|
|
||||||
|
case ManiaSkinComponents.StageForeground:
|
||||||
|
return new LegacyStageForeground();
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
@ -42,6 +42,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
Index = index;
|
Index = index;
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.Y;
|
RelativeSizeAxes = Axes.Y;
|
||||||
|
Width = COLUMN_WIDTH;
|
||||||
|
|
||||||
Drawable background = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground, Index), _ => new DefaultColumnBackground())
|
Drawable background = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground, Index), _ => new DefaultColumnBackground())
|
||||||
{
|
{
|
||||||
@ -138,6 +139,6 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
|
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos)
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos)
|
||||||
// This probably shouldn't exist as is, but the columns in the stage are separated by a 1px border
|
// This probably shouldn't exist as is, but the columns in the stage are separated by a 1px border
|
||||||
=> DrawRectangle.Inflate(new Vector2(ManiaStage.COLUMN_SPACING / 2, 0)).Contains(ToLocalSpace(screenSpacePos));
|
=> DrawRectangle.Inflate(new Vector2(Stage.COLUMN_SPACING / 2, 0)).Contains(ToLocalSpace(screenSpacePos));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components
|
|||||||
InternalChild = directionContainer = new Container
|
InternalChild = directionContainer = new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Height = ManiaStage.HIT_TARGET_POSITION,
|
Height = Stage.HIT_TARGET_POSITION,
|
||||||
Children = new[]
|
Children = new[]
|
||||||
{
|
{
|
||||||
gradient = new Box
|
gradient = new Box
|
||||||
@ -53,9 +53,8 @@ namespace osu.Game.Rulesets.Mania.UI.Components
|
|||||||
keyIcon = new Container
|
keyIcon = new Container
|
||||||
{
|
{
|
||||||
Name = "Key icon",
|
Name = "Key icon",
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
Size = new Vector2(key_icon_size),
|
Size = new Vector2(key_icon_size),
|
||||||
|
Origin = Anchor.Centre,
|
||||||
Masking = true,
|
Masking = true,
|
||||||
CornerRadius = key_icon_corner_radius,
|
CornerRadius = key_icon_corner_radius,
|
||||||
BorderThickness = 2,
|
BorderThickness = 2,
|
||||||
@ -88,11 +87,15 @@ namespace osu.Game.Rulesets.Mania.UI.Components
|
|||||||
{
|
{
|
||||||
if (direction.NewValue == ScrollingDirection.Up)
|
if (direction.NewValue == ScrollingDirection.Up)
|
||||||
{
|
{
|
||||||
|
keyIcon.Anchor = Anchor.BottomCentre;
|
||||||
|
keyIcon.Y = -20;
|
||||||
directionContainer.Anchor = directionContainer.Origin = Anchor.TopLeft;
|
directionContainer.Anchor = directionContainer.Origin = Anchor.TopLeft;
|
||||||
gradient.Colour = ColourInfo.GradientVertical(Color4.Black, Color4.Black.Opacity(0));
|
gradient.Colour = ColourInfo.GradientVertical(Color4.Black, Color4.Black.Opacity(0));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
keyIcon.Anchor = Anchor.TopCentre;
|
||||||
|
keyIcon.Y = 20;
|
||||||
directionContainer.Anchor = directionContainer.Origin = Anchor.BottomLeft;
|
directionContainer.Anchor = directionContainer.Origin = Anchor.BottomLeft;
|
||||||
gradient.Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0), Color4.Black);
|
gradient.Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0), Color4.Black);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
// 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.Shapes;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.UI.Components
|
||||||
|
{
|
||||||
|
public class DefaultStageBackground : CompositeDrawable
|
||||||
|
{
|
||||||
|
public DefaultStageBackground()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
InternalChild = new Box
|
||||||
|
{
|
||||||
|
Name = "Background",
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = Color4.Black
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components
|
|||||||
{
|
{
|
||||||
float hitPosition = CurrentSkin.GetConfig<ManiaSkinConfigurationLookup, float>(
|
float hitPosition = CurrentSkin.GetConfig<ManiaSkinConfigurationLookup, float>(
|
||||||
new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.HitPosition))?.Value
|
new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.HitPosition))?.Value
|
||||||
?? ManiaStage.HIT_TARGET_POSITION;
|
?? Stage.HIT_TARGET_POSITION;
|
||||||
|
|
||||||
Padding = Direction.Value == ScrollingDirection.Up
|
Padding = Direction.Value == ScrollingDirection.Up
|
||||||
? new MarginPadding { Top = hitPosition }
|
? new MarginPadding { Top = hitPosition }
|
||||||
|
@ -64,6 +64,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
{
|
{
|
||||||
// Mania doesn't care about global velocity
|
// Mania doesn't care about global velocity
|
||||||
p.Velocity = 1;
|
p.Velocity = 1;
|
||||||
|
p.BaseBeatLength *= Beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier;
|
||||||
|
|
||||||
// For non-mania beatmap, speed changes should only happen through timing points
|
// For non-mania beatmap, speed changes should only happen through timing points
|
||||||
if (!isForCurrentRuleset)
|
if (!isForCurrentRuleset)
|
||||||
|
@ -6,6 +6,7 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
@ -14,9 +15,10 @@ using osuTK;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.UI
|
namespace osu.Game.Rulesets.Mania.UI
|
||||||
{
|
{
|
||||||
|
[Cached]
|
||||||
public class ManiaPlayfield : ScrollingPlayfield
|
public class ManiaPlayfield : ScrollingPlayfield
|
||||||
{
|
{
|
||||||
private readonly List<ManiaStage> stages = new List<ManiaStage>();
|
private readonly List<Stage> stages = new List<Stage>();
|
||||||
|
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => stages.Any(s => s.ReceivePositionalInputAt(screenSpacePos));
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => stages.Any(s => s.ReceivePositionalInputAt(screenSpacePos));
|
||||||
|
|
||||||
@ -41,7 +43,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
|
|
||||||
for (int i = 0; i < stageDefinitions.Count; i++)
|
for (int i = 0; i < stageDefinitions.Count; i++)
|
||||||
{
|
{
|
||||||
var newStage = new ManiaStage(firstColumnIndex, stageDefinitions[i], ref normalColumnAction, ref specialColumnAction);
|
var newStage = new Stage(firstColumnIndex, stageDefinitions[i], ref normalColumnAction, ref specialColumnAction);
|
||||||
|
|
||||||
playfieldGrid.Content[0][i] = newStage;
|
playfieldGrid.Content[0][i] = newStage;
|
||||||
|
|
||||||
@ -90,7 +92,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public int TotalColumns => stages.Sum(s => s.Columns.Count);
|
public int TotalColumns => stages.Sum(s => s.Columns.Count);
|
||||||
|
|
||||||
private ManiaStage getStageByColumn(int column)
|
private Stage getStageByColumn(int column)
|
||||||
{
|
{
|
||||||
int sum = 0;
|
int sum = 0;
|
||||||
|
|
||||||
|
@ -6,7 +6,6 @@ using System.Linq;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
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.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
@ -25,11 +24,11 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// A collection of <see cref="Column"/>s.
|
/// A collection of <see cref="Column"/>s.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ManiaStage : ScrollingPlayfield
|
public class Stage : ScrollingPlayfield
|
||||||
{
|
{
|
||||||
public const float COLUMN_SPACING = 1;
|
public const float COLUMN_SPACING = 1;
|
||||||
|
|
||||||
public const float HIT_TARGET_POSITION = 50;
|
public const float HIT_TARGET_POSITION = 110;
|
||||||
|
|
||||||
public IReadOnlyList<Column> Columns => columnFlow.Children;
|
public IReadOnlyList<Column> Columns => columnFlow.Children;
|
||||||
private readonly FillFlowContainer<Column> columnFlow;
|
private readonly FillFlowContainer<Column> columnFlow;
|
||||||
@ -51,7 +50,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
|
|
||||||
private readonly int firstColumnIndex;
|
private readonly int firstColumnIndex;
|
||||||
|
|
||||||
public ManiaStage(int firstColumnIndex, StageDefinition definition, ref ManiaAction normalColumnStartAction, ref ManiaAction specialColumnStartAction)
|
public Stage(int firstColumnIndex, StageDefinition definition, ref ManiaAction normalColumnStartAction, ref ManiaAction specialColumnStartAction)
|
||||||
{
|
{
|
||||||
this.firstColumnIndex = firstColumnIndex;
|
this.firstColumnIndex = firstColumnIndex;
|
||||||
|
|
||||||
@ -72,11 +71,9 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
AutoSizeAxes = Axes.X,
|
AutoSizeAxes = Axes.X,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new Box
|
new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageBackground), _ => new DefaultStageBackground())
|
||||||
{
|
{
|
||||||
Name = "Background",
|
RelativeSizeAxes = Axes.Both
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Colour = Color4.Black
|
|
||||||
},
|
},
|
||||||
columnFlow = new FillFlowContainer<Column>
|
columnFlow = new FillFlowContainer<Column>
|
||||||
{
|
{
|
||||||
@ -103,6 +100,10 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
RelativeSizeAxes = Axes.Y,
|
RelativeSizeAxes = Axes.Y,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageForeground), _ => null)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
|
},
|
||||||
judgements = new JudgementContainer<DrawableManiaJudgement>
|
judgements = new JudgementContainer<DrawableManiaJudgement>
|
||||||
{
|
{
|
||||||
Anchor = Anchor.TopCentre,
|
Anchor = Anchor.TopCentre,
|
106
osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||||
|
{
|
||||||
|
public class TestSceneOsuModHidden : ModTestScene
|
||||||
|
{
|
||||||
|
public TestSceneOsuModHidden()
|
||||||
|
: base(new OsuRuleset())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDefaultBeatmapTest() => CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new OsuModHidden(),
|
||||||
|
Autoplay = true,
|
||||||
|
PassCondition = checkSomeHit
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void FirstCircleAfterTwoSpinners() => CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new OsuModHidden(),
|
||||||
|
Autoplay = true,
|
||||||
|
Beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new Spinner
|
||||||
|
{
|
||||||
|
Position = new Vector2(256, 192),
|
||||||
|
EndTime = 1000,
|
||||||
|
},
|
||||||
|
new Spinner
|
||||||
|
{
|
||||||
|
Position = new Vector2(256, 192),
|
||||||
|
StartTime = 1200,
|
||||||
|
EndTime = 2200,
|
||||||
|
},
|
||||||
|
new HitCircle
|
||||||
|
{
|
||||||
|
Position = new Vector2(300, 192),
|
||||||
|
StartTime = 3200,
|
||||||
|
},
|
||||||
|
new HitCircle
|
||||||
|
{
|
||||||
|
Position = new Vector2(384, 192),
|
||||||
|
StartTime = 4200,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
PassCondition = checkSomeHit
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void FirstSliderAfterTwoSpinners() => CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new OsuModHidden(),
|
||||||
|
Autoplay = true,
|
||||||
|
Beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new Spinner
|
||||||
|
{
|
||||||
|
Position = new Vector2(256, 192),
|
||||||
|
EndTime = 1000,
|
||||||
|
},
|
||||||
|
new Spinner
|
||||||
|
{
|
||||||
|
Position = new Vector2(256, 192),
|
||||||
|
StartTime = 1200,
|
||||||
|
EndTime = 2200,
|
||||||
|
},
|
||||||
|
new Slider
|
||||||
|
{
|
||||||
|
StartTime = 3200,
|
||||||
|
Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(100, 0), })
|
||||||
|
},
|
||||||
|
new Slider
|
||||||
|
{
|
||||||
|
StartTime = 5200,
|
||||||
|
Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(100, 0), })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
PassCondition = checkSomeHit
|
||||||
|
});
|
||||||
|
|
||||||
|
private bool checkSomeHit()
|
||||||
|
{
|
||||||
|
return Player.ScoreProcessor.JudgedHits >= 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using osu.Game.Rulesets.Osu.Skinning;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests
|
||||||
|
{
|
||||||
|
public abstract class OsuSkinnableTestScene : SkinnableTestScene
|
||||||
|
{
|
||||||
|
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||||
|
{
|
||||||
|
typeof(OsuRuleset),
|
||||||
|
typeof(OsuLegacySkinTransformer),
|
||||||
|
};
|
||||||
|
|
||||||
|
protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset();
|
||||||
|
}
|
||||||
|
}
|
@ -10,17 +10,16 @@ using osu.Game.Rulesets.Judgements;
|
|||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Tests.Visual;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Tests
|
namespace osu.Game.Rulesets.Osu.Tests
|
||||||
{
|
{
|
||||||
public class TestSceneDrawableJudgement : SkinnableTestScene
|
public class TestSceneDrawableJudgement : OsuSkinnableTestScene
|
||||||
{
|
{
|
||||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[]
|
||||||
{
|
{
|
||||||
typeof(DrawableJudgement),
|
typeof(DrawableJudgement),
|
||||||
typeof(DrawableOsuJudgement)
|
typeof(DrawableOsuJudgement)
|
||||||
};
|
}).ToList();
|
||||||
|
|
||||||
public TestSceneDrawableJudgement()
|
public TestSceneDrawableJudgement()
|
||||||
{
|
{
|
||||||
|
@ -3,26 +3,32 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Testing.Input;
|
using osu.Framework.Testing.Input;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Rulesets.Osu.Skinning;
|
||||||
using osu.Game.Rulesets.Osu.UI.Cursor;
|
using osu.Game.Rulesets.Osu.UI.Cursor;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Tests.Visual;
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Tests
|
namespace osu.Game.Rulesets.Osu.Tests
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestSceneGameplayCursor : SkinnableTestScene
|
public class TestSceneGameplayCursor : OsuSkinnableTestScene
|
||||||
{
|
{
|
||||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[]
|
||||||
{
|
{
|
||||||
|
typeof(GameplayCursorContainer),
|
||||||
typeof(OsuCursorContainer),
|
typeof(OsuCursorContainer),
|
||||||
|
typeof(OsuCursor),
|
||||||
|
typeof(LegacyCursor),
|
||||||
|
typeof(LegacyCursorTrail),
|
||||||
typeof(CursorTrail)
|
typeof(CursorTrail)
|
||||||
};
|
}).ToList();
|
||||||
|
|
||||||
[Cached]
|
[Cached]
|
||||||
private GameplayBeatmap gameplayBeatmap;
|
private GameplayBeatmap gameplayBeatmap;
|
||||||
|
@ -14,12 +14,11 @@ using osu.Game.Rulesets.Mods;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Tests.Visual;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Tests
|
namespace osu.Game.Rulesets.Osu.Tests
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestSceneHitCircle : SkinnableTestScene
|
public class TestSceneHitCircle : OsuSkinnableTestScene
|
||||||
{
|
{
|
||||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||||
{
|
{
|
||||||
|
453
osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs
Normal file
@ -0,0 +1,453 @@
|
|||||||
|
// 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.Extensions.TypeExtensions;
|
||||||
|
using osu.Framework.Screens;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Replays;
|
||||||
|
using osu.Game.Rulesets.Judgements;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Replays;
|
||||||
|
using osu.Game.Rulesets.Replays;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Screens.Play;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests
|
||||||
|
{
|
||||||
|
public class TestSceneOutOfOrderHits : RateAdjustedBeatmapTestScene
|
||||||
|
{
|
||||||
|
private const double early_miss_window = 1000; // time after -1000 to -500 is considered a miss
|
||||||
|
private const double late_miss_window = 500; // time after +500 is considered a miss
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests clicking a future circle before the first circle's start time, while the first circle HAS NOT been judged.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestClickSecondCircleBeforeFirstCircleTime()
|
||||||
|
{
|
||||||
|
const double time_first_circle = 1500;
|
||||||
|
const double time_second_circle = 1600;
|
||||||
|
Vector2 positionFirstCircle = Vector2.Zero;
|
||||||
|
Vector2 positionSecondCircle = new Vector2(80);
|
||||||
|
|
||||||
|
var hitObjects = new List<OsuHitObject>
|
||||||
|
{
|
||||||
|
new TestHitCircle
|
||||||
|
{
|
||||||
|
StartTime = time_first_circle,
|
||||||
|
Position = positionFirstCircle
|
||||||
|
},
|
||||||
|
new TestHitCircle
|
||||||
|
{
|
||||||
|
StartTime = time_second_circle,
|
||||||
|
Position = positionSecondCircle
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
performTest(hitObjects, new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new OsuReplayFrame { Time = time_first_circle - 100, Position = positionSecondCircle, Actions = { OsuAction.LeftButton } }
|
||||||
|
});
|
||||||
|
|
||||||
|
addJudgementAssert(hitObjects[0], HitResult.Miss);
|
||||||
|
addJudgementAssert(hitObjects[1], HitResult.Miss);
|
||||||
|
addJudgementOffsetAssert(hitObjects[0], late_miss_window);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests clicking a future circle at the first circle's start time, while the first circle HAS NOT been judged.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestClickSecondCircleAtFirstCircleTime()
|
||||||
|
{
|
||||||
|
const double time_first_circle = 1500;
|
||||||
|
const double time_second_circle = 1600;
|
||||||
|
Vector2 positionFirstCircle = Vector2.Zero;
|
||||||
|
Vector2 positionSecondCircle = new Vector2(80);
|
||||||
|
|
||||||
|
var hitObjects = new List<OsuHitObject>
|
||||||
|
{
|
||||||
|
new TestHitCircle
|
||||||
|
{
|
||||||
|
StartTime = time_first_circle,
|
||||||
|
Position = positionFirstCircle
|
||||||
|
},
|
||||||
|
new TestHitCircle
|
||||||
|
{
|
||||||
|
StartTime = time_second_circle,
|
||||||
|
Position = positionSecondCircle
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
performTest(hitObjects, new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new OsuReplayFrame { Time = time_first_circle, Position = positionSecondCircle, Actions = { OsuAction.LeftButton } }
|
||||||
|
});
|
||||||
|
|
||||||
|
addJudgementAssert(hitObjects[0], HitResult.Miss);
|
||||||
|
addJudgementAssert(hitObjects[1], HitResult.Great);
|
||||||
|
addJudgementOffsetAssert(hitObjects[0], 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests clicking a future circle after the first circle's start time, while the first circle HAS NOT been judged.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestClickSecondCircleAfterFirstCircleTime()
|
||||||
|
{
|
||||||
|
const double time_first_circle = 1500;
|
||||||
|
const double time_second_circle = 1600;
|
||||||
|
Vector2 positionFirstCircle = Vector2.Zero;
|
||||||
|
Vector2 positionSecondCircle = new Vector2(80);
|
||||||
|
|
||||||
|
var hitObjects = new List<OsuHitObject>
|
||||||
|
{
|
||||||
|
new TestHitCircle
|
||||||
|
{
|
||||||
|
StartTime = time_first_circle,
|
||||||
|
Position = positionFirstCircle
|
||||||
|
},
|
||||||
|
new TestHitCircle
|
||||||
|
{
|
||||||
|
StartTime = time_second_circle,
|
||||||
|
Position = positionSecondCircle
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
performTest(hitObjects, new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new OsuReplayFrame { Time = time_first_circle + 100, Position = positionSecondCircle, Actions = { OsuAction.LeftButton } }
|
||||||
|
});
|
||||||
|
|
||||||
|
addJudgementAssert(hitObjects[0], HitResult.Miss);
|
||||||
|
addJudgementAssert(hitObjects[1], HitResult.Great);
|
||||||
|
addJudgementOffsetAssert(hitObjects[0], 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests clicking a future circle before the first circle's start time, while the first circle HAS been judged.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestClickSecondCircleBeforeFirstCircleTimeWithFirstCircleJudged()
|
||||||
|
{
|
||||||
|
const double time_first_circle = 1500;
|
||||||
|
const double time_second_circle = 1600;
|
||||||
|
Vector2 positionFirstCircle = Vector2.Zero;
|
||||||
|
Vector2 positionSecondCircle = new Vector2(80);
|
||||||
|
|
||||||
|
var hitObjects = new List<OsuHitObject>
|
||||||
|
{
|
||||||
|
new TestHitCircle
|
||||||
|
{
|
||||||
|
StartTime = time_first_circle,
|
||||||
|
Position = positionFirstCircle
|
||||||
|
},
|
||||||
|
new TestHitCircle
|
||||||
|
{
|
||||||
|
StartTime = time_second_circle,
|
||||||
|
Position = positionSecondCircle
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
performTest(hitObjects, new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new OsuReplayFrame { Time = time_first_circle - 200, Position = positionFirstCircle, Actions = { OsuAction.LeftButton } },
|
||||||
|
new OsuReplayFrame { Time = time_first_circle - 100, Position = positionSecondCircle, Actions = { OsuAction.RightButton } }
|
||||||
|
});
|
||||||
|
|
||||||
|
addJudgementAssert(hitObjects[0], HitResult.Great);
|
||||||
|
addJudgementAssert(hitObjects[1], HitResult.Great);
|
||||||
|
addJudgementOffsetAssert(hitObjects[0], -200); // time_first_circle - 200
|
||||||
|
addJudgementOffsetAssert(hitObjects[0], -200); // time_second_circle - first_circle_time - 100
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests clicking a future circle after a slider's start time, but hitting all slider ticks.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestMissSliderHeadAndHitAllSliderTicks()
|
||||||
|
{
|
||||||
|
const double time_slider = 1500;
|
||||||
|
const double time_circle = 1510;
|
||||||
|
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_slider, Position = positionCircle, Actions = { OsuAction.LeftButton } },
|
||||||
|
new OsuReplayFrame { Time = time_slider + 10, Position = positionSlider, Actions = { OsuAction.RightButton } }
|
||||||
|
});
|
||||||
|
|
||||||
|
addJudgementAssert(hitObjects[0], HitResult.Great);
|
||||||
|
addJudgementAssert(hitObjects[1], HitResult.Great);
|
||||||
|
addJudgementAssert("slider head", () => ((Slider)hitObjects[1]).HeadCircle, HitResult.Miss);
|
||||||
|
addJudgementAssert("slider tick", () => ((Slider)hitObjects[1]).NestedHitObjects[1] as SliderTick, HitResult.Great);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests clicking hitting future slider ticks before a circle.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestHitSliderTicksBeforeCircle()
|
||||||
|
{
|
||||||
|
const double time_slider = 1500;
|
||||||
|
const double time_circle = 1510;
|
||||||
|
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_slider, Position = positionSlider, Actions = { OsuAction.LeftButton } },
|
||||||
|
new OsuReplayFrame { Time = time_circle + late_miss_window - 100, Position = positionCircle, Actions = { OsuAction.RightButton } },
|
||||||
|
new OsuReplayFrame { Time = time_circle + late_miss_window - 90, Position = positionSlider, Actions = { OsuAction.LeftButton } },
|
||||||
|
});
|
||||||
|
|
||||||
|
addJudgementAssert(hitObjects[0], HitResult.Great);
|
||||||
|
addJudgementAssert(hitObjects[1], HitResult.Great);
|
||||||
|
addJudgementAssert("slider head", () => ((Slider)hitObjects[1]).HeadCircle, HitResult.Great);
|
||||||
|
addJudgementAssert("slider tick", () => ((Slider)hitObjects[1]).NestedHitObjects[1] as SliderTick, HitResult.Great);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests clicking a future circle before a spinner.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestHitCircleBeforeSpinner()
|
||||||
|
{
|
||||||
|
const double time_spinner = 1500;
|
||||||
|
const double time_circle = 1800;
|
||||||
|
Vector2 positionCircle = Vector2.Zero;
|
||||||
|
|
||||||
|
var hitObjects = new List<OsuHitObject>
|
||||||
|
{
|
||||||
|
new TestSpinner
|
||||||
|
{
|
||||||
|
StartTime = time_spinner,
|
||||||
|
Position = new Vector2(256, 192),
|
||||||
|
EndTime = time_spinner + 1000,
|
||||||
|
},
|
||||||
|
new TestHitCircle
|
||||||
|
{
|
||||||
|
StartTime = time_circle,
|
||||||
|
Position = positionCircle
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
performTest(hitObjects, new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new OsuReplayFrame { Time = time_spinner - 100, Position = positionCircle, Actions = { OsuAction.LeftButton } },
|
||||||
|
new OsuReplayFrame { Time = time_spinner + 10, Position = new Vector2(236, 192), Actions = { OsuAction.RightButton } },
|
||||||
|
new OsuReplayFrame { Time = time_spinner + 20, Position = new Vector2(256, 172), Actions = { OsuAction.RightButton } },
|
||||||
|
new OsuReplayFrame { Time = time_spinner + 30, Position = new Vector2(276, 192), Actions = { OsuAction.RightButton } },
|
||||||
|
new OsuReplayFrame { Time = time_spinner + 40, Position = new Vector2(256, 212), Actions = { OsuAction.RightButton } },
|
||||||
|
new OsuReplayFrame { Time = time_spinner + 50, Position = new Vector2(236, 192), Actions = { OsuAction.RightButton } },
|
||||||
|
});
|
||||||
|
|
||||||
|
addJudgementAssert(hitObjects[0], 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)
|
||||||
|
{
|
||||||
|
AddAssert($"({hitObject.GetType().ReadableName()} @ {hitObject.StartTime}) judgement is {result}",
|
||||||
|
() => judgementResults.Single(r => r.HitObject == hitObject).Type == result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addJudgementAssert(string name, Func<OsuHitObject> hitObject, HitResult result)
|
||||||
|
{
|
||||||
|
AddAssert($"{name} judgement is {result}",
|
||||||
|
() => judgementResults.Single(r => r.HitObject == hitObject()).Type == result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addJudgementOffsetAssert(OsuHitObject hitObject, double offset)
|
||||||
|
{
|
||||||
|
AddAssert($"({hitObject.GetType().ReadableName()} @ {hitObject.StartTime}) judged at {offset}",
|
||||||
|
() => Precision.AlmostEquals(judgementResults.Single(r => r.HitObject == hitObject).TimeOffset, offset, 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
private ScoreAccessibleReplayPlayer currentPlayer;
|
||||||
|
private List<JudgementResult> judgementResults;
|
||||||
|
private bool allJudgedFired;
|
||||||
|
|
||||||
|
private void performTest(List<OsuHitObject> hitObjects, List<ReplayFrame> frames)
|
||||||
|
{
|
||||||
|
AddStep("load player", () =>
|
||||||
|
{
|
||||||
|
Beatmap.Value = CreateWorkingBeatmap(new Beatmap<OsuHitObject>
|
||||||
|
{
|
||||||
|
HitObjects = hitObjects,
|
||||||
|
BeatmapInfo =
|
||||||
|
{
|
||||||
|
BaseDifficulty = new BeatmapDifficulty { SliderTickRate = 3 },
|
||||||
|
Ruleset = new OsuRuleset().RulesetInfo
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Beatmap.Value.Beatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 0.1f });
|
||||||
|
|
||||||
|
var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });
|
||||||
|
|
||||||
|
p.OnLoadComplete += _ =>
|
||||||
|
{
|
||||||
|
p.ScoreProcessor.NewJudgement += result =>
|
||||||
|
{
|
||||||
|
if (currentPlayer == p) judgementResults.Add(result);
|
||||||
|
};
|
||||||
|
p.ScoreProcessor.AllJudged += () =>
|
||||||
|
{
|
||||||
|
if (currentPlayer == p) allJudgedFired = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
LoadScreen(currentPlayer = p);
|
||||||
|
allJudgedFired = false;
|
||||||
|
judgementResults = new List<JudgementResult>();
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
|
||||||
|
AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
|
||||||
|
AddUntilStep("Wait for all judged", () => allJudgedFired);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestHitCircle : HitCircle
|
||||||
|
{
|
||||||
|
protected override HitWindows CreateHitWindows() => new TestHitWindows();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestSlider : Slider
|
||||||
|
{
|
||||||
|
public TestSlider()
|
||||||
|
{
|
||||||
|
DefaultsApplied += () =>
|
||||||
|
{
|
||||||
|
HeadCircle.HitWindows = new TestHitWindows();
|
||||||
|
TailCircle.HitWindows = new TestHitWindows();
|
||||||
|
|
||||||
|
HeadCircle.HitWindows.SetDifficulty(0);
|
||||||
|
TailCircle.HitWindows.SetDifficulty(0);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestSpinner : Spinner
|
||||||
|
{
|
||||||
|
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
|
||||||
|
{
|
||||||
|
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
||||||
|
SpinsRequired = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestHitWindows : HitWindows
|
||||||
|
{
|
||||||
|
private static readonly DifficultyRange[] ranges =
|
||||||
|
{
|
||||||
|
new DifficultyRange(HitResult.Great, 500, 500, 500),
|
||||||
|
new DifficultyRange(HitResult.Miss, early_miss_window, early_miss_window, early_miss_window),
|
||||||
|
};
|
||||||
|
|
||||||
|
public override bool IsHitResultAllowed(HitResult result) => result == HitResult.Great || result == HitResult.Miss;
|
||||||
|
|
||||||
|
protected override DifficultyRange[] GetRanges() => ranges;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ScoreAccessibleReplayPlayer : ReplayPlayer
|
||||||
|
{
|
||||||
|
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
|
||||||
|
|
||||||
|
protected override bool PauseOnFocusLost => false;
|
||||||
|
|
||||||
|
public ScoreAccessibleReplayPlayer(Score score)
|
||||||
|
: base(score, false, false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Humanizer;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests
|
||||||
|
{
|
||||||
|
public class TestScenePathControlPointVisualiser : OsuTestScene
|
||||||
|
{
|
||||||
|
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||||
|
{
|
||||||
|
typeof(StringHumanizeExtensions),
|
||||||
|
typeof(PathControlPointPiece),
|
||||||
|
typeof(PathControlPointConnectionPiece)
|
||||||
|
};
|
||||||
|
|
||||||
|
private Slider slider;
|
||||||
|
private PathControlPointVisualiser visualiser;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup() => Schedule(() =>
|
||||||
|
{
|
||||||
|
slider = new Slider();
|
||||||
|
slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAddOverlappingControlPoints()
|
||||||
|
{
|
||||||
|
createVisualiser(true);
|
||||||
|
|
||||||
|
addControlPointStep(new Vector2(200));
|
||||||
|
addControlPointStep(new Vector2(300));
|
||||||
|
addControlPointStep(new Vector2(300));
|
||||||
|
addControlPointStep(new Vector2(500, 300));
|
||||||
|
|
||||||
|
AddAssert("last connection displayed", () =>
|
||||||
|
{
|
||||||
|
var lastConnection = visualiser.Connections.Last(c => c.ControlPoint.Position.Value == new Vector2(300));
|
||||||
|
return lastConnection.DrawWidth > 50;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createVisualiser(bool allowSelection) => AddStep("create visualiser", () => Child = visualiser = new PathControlPointVisualiser(slider, allowSelection)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre
|
||||||
|
});
|
||||||
|
|
||||||
|
private void addControlPointStep(Vector2 position) => AddStep($"add control point {position}", () => slider.Path.ControlPoints.Add(new PathControlPoint(position)));
|
||||||
|
}
|
||||||
|
}
|
@ -22,12 +22,11 @@ using osu.Game.Rulesets.Objects;
|
|||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
|
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
|
||||||
using osu.Game.Tests.Visual;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Tests
|
namespace osu.Game.Rulesets.Osu.Tests
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestSceneSlider : SkinnableTestScene
|
public class TestSceneSlider : OsuSkinnableTestScene
|
||||||
{
|
{
|
||||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||||
{
|
{
|
||||||
|
@ -1,18 +1,285 @@
|
|||||||
// 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 NUnit.Framework;
|
||||||
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
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.Objects.Types;
|
||||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders;
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Tests
|
namespace osu.Game.Rulesets.Osu.Tests
|
||||||
{
|
{
|
||||||
public class TestSceneSliderPlacementBlueprint : PlacementBlueprintTestScene
|
public class TestSceneSliderPlacementBlueprint : PlacementBlueprintTestScene
|
||||||
{
|
{
|
||||||
|
[SetUp]
|
||||||
|
public void Setup() => Schedule(() =>
|
||||||
|
{
|
||||||
|
HitObjectContainer.Clear();
|
||||||
|
ResetPlacement();
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBeginPlacementWithoutFinishing()
|
||||||
|
{
|
||||||
|
addMovementStep(new Vector2(200));
|
||||||
|
addClickStep(MouseButton.Left);
|
||||||
|
|
||||||
|
assertPlaced(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPlaceWithoutMovingMouse()
|
||||||
|
{
|
||||||
|
addMovementStep(new Vector2(200));
|
||||||
|
addClickStep(MouseButton.Left);
|
||||||
|
addClickStep(MouseButton.Right);
|
||||||
|
|
||||||
|
assertPlaced(true);
|
||||||
|
assertLength(0);
|
||||||
|
assertControlPointType(0, PathType.Linear);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPlaceWithMouseMovement()
|
||||||
|
{
|
||||||
|
addMovementStep(new Vector2(200));
|
||||||
|
addClickStep(MouseButton.Left);
|
||||||
|
|
||||||
|
addMovementStep(new Vector2(400, 200));
|
||||||
|
addClickStep(MouseButton.Right);
|
||||||
|
|
||||||
|
assertPlaced(true);
|
||||||
|
assertLength(200);
|
||||||
|
assertControlPointCount(2);
|
||||||
|
assertControlPointType(0, PathType.Linear);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPlaceNormalControlPoint()
|
||||||
|
{
|
||||||
|
addMovementStep(new Vector2(200));
|
||||||
|
addClickStep(MouseButton.Left);
|
||||||
|
|
||||||
|
addMovementStep(new Vector2(300, 200));
|
||||||
|
addClickStep(MouseButton.Left);
|
||||||
|
|
||||||
|
addMovementStep(new Vector2(300));
|
||||||
|
addClickStep(MouseButton.Right);
|
||||||
|
|
||||||
|
assertPlaced(true);
|
||||||
|
assertControlPointCount(3);
|
||||||
|
assertControlPointPosition(1, new Vector2(100, 0));
|
||||||
|
assertControlPointType(0, PathType.PerfectCurve);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPlaceTwoNormalControlPoints()
|
||||||
|
{
|
||||||
|
addMovementStep(new Vector2(200));
|
||||||
|
addClickStep(MouseButton.Left);
|
||||||
|
|
||||||
|
addMovementStep(new Vector2(300, 200));
|
||||||
|
addClickStep(MouseButton.Left);
|
||||||
|
|
||||||
|
addMovementStep(new Vector2(300));
|
||||||
|
addClickStep(MouseButton.Left);
|
||||||
|
|
||||||
|
addMovementStep(new Vector2(400, 300));
|
||||||
|
addClickStep(MouseButton.Right);
|
||||||
|
|
||||||
|
assertPlaced(true);
|
||||||
|
assertControlPointCount(4);
|
||||||
|
assertControlPointPosition(1, new Vector2(100, 0));
|
||||||
|
assertControlPointPosition(2, new Vector2(100, 100));
|
||||||
|
assertControlPointType(0, PathType.Bezier);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPlaceSegmentControlPoint()
|
||||||
|
{
|
||||||
|
addMovementStep(new Vector2(200));
|
||||||
|
addClickStep(MouseButton.Left);
|
||||||
|
|
||||||
|
addMovementStep(new Vector2(300, 200));
|
||||||
|
addClickStep(MouseButton.Left);
|
||||||
|
addClickStep(MouseButton.Left);
|
||||||
|
|
||||||
|
addMovementStep(new Vector2(300));
|
||||||
|
addClickStep(MouseButton.Right);
|
||||||
|
|
||||||
|
assertPlaced(true);
|
||||||
|
assertControlPointCount(3);
|
||||||
|
assertControlPointPosition(1, new Vector2(100, 0));
|
||||||
|
assertControlPointType(0, PathType.Linear);
|
||||||
|
assertControlPointType(1, PathType.Linear);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMoveToPerfectCurveThenPlaceLinear()
|
||||||
|
{
|
||||||
|
addMovementStep(new Vector2(200));
|
||||||
|
addClickStep(MouseButton.Left);
|
||||||
|
|
||||||
|
addMovementStep(new Vector2(300, 200));
|
||||||
|
addClickStep(MouseButton.Left);
|
||||||
|
|
||||||
|
addMovementStep(new Vector2(300));
|
||||||
|
addMovementStep(new Vector2(300, 200));
|
||||||
|
addClickStep(MouseButton.Right);
|
||||||
|
|
||||||
|
assertPlaced(true);
|
||||||
|
assertControlPointCount(2);
|
||||||
|
assertControlPointType(0, PathType.Linear);
|
||||||
|
assertLength(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMoveToBezierThenPlacePerfectCurve()
|
||||||
|
{
|
||||||
|
addMovementStep(new Vector2(200));
|
||||||
|
addClickStep(MouseButton.Left);
|
||||||
|
|
||||||
|
addMovementStep(new Vector2(300, 200));
|
||||||
|
addClickStep(MouseButton.Left);
|
||||||
|
|
||||||
|
addMovementStep(new Vector2(300));
|
||||||
|
addClickStep(MouseButton.Left);
|
||||||
|
|
||||||
|
addMovementStep(new Vector2(400, 300));
|
||||||
|
addMovementStep(new Vector2(300));
|
||||||
|
addClickStep(MouseButton.Right);
|
||||||
|
|
||||||
|
assertPlaced(true);
|
||||||
|
assertControlPointCount(3);
|
||||||
|
assertControlPointType(0, PathType.PerfectCurve);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMoveToFourthOrderBezierThenPlaceThirdOrderBezier()
|
||||||
|
{
|
||||||
|
addMovementStep(new Vector2(200));
|
||||||
|
addClickStep(MouseButton.Left);
|
||||||
|
|
||||||
|
addMovementStep(new Vector2(300, 200));
|
||||||
|
addClickStep(MouseButton.Left);
|
||||||
|
|
||||||
|
addMovementStep(new Vector2(300));
|
||||||
|
addClickStep(MouseButton.Left);
|
||||||
|
|
||||||
|
addMovementStep(new Vector2(400, 300));
|
||||||
|
addClickStep(MouseButton.Left);
|
||||||
|
|
||||||
|
addMovementStep(new Vector2(400));
|
||||||
|
addMovementStep(new Vector2(400, 300));
|
||||||
|
addClickStep(MouseButton.Right);
|
||||||
|
|
||||||
|
assertPlaced(true);
|
||||||
|
assertControlPointCount(4);
|
||||||
|
assertControlPointType(0, PathType.Bezier);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPlaceLinearSegmentThenPlaceLinearSegment()
|
||||||
|
{
|
||||||
|
addMovementStep(new Vector2(200));
|
||||||
|
addClickStep(MouseButton.Left);
|
||||||
|
|
||||||
|
addMovementStep(new Vector2(300, 200));
|
||||||
|
addClickStep(MouseButton.Left);
|
||||||
|
addClickStep(MouseButton.Left);
|
||||||
|
|
||||||
|
addMovementStep(new Vector2(300, 300));
|
||||||
|
addClickStep(MouseButton.Right);
|
||||||
|
|
||||||
|
assertPlaced(true);
|
||||||
|
assertControlPointCount(3);
|
||||||
|
assertControlPointPosition(1, new Vector2(100, 0));
|
||||||
|
assertControlPointPosition(2, new Vector2(100));
|
||||||
|
assertControlPointType(0, PathType.Linear);
|
||||||
|
assertControlPointType(1, PathType.Linear);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPlaceLinearSegmentThenPlacePerfectCurveSegment()
|
||||||
|
{
|
||||||
|
addMovementStep(new Vector2(200));
|
||||||
|
addClickStep(MouseButton.Left);
|
||||||
|
|
||||||
|
addMovementStep(new Vector2(300, 200));
|
||||||
|
addClickStep(MouseButton.Left);
|
||||||
|
addClickStep(MouseButton.Left);
|
||||||
|
|
||||||
|
addMovementStep(new Vector2(300, 300));
|
||||||
|
addClickStep(MouseButton.Left);
|
||||||
|
|
||||||
|
addMovementStep(new Vector2(400, 300));
|
||||||
|
addClickStep(MouseButton.Right);
|
||||||
|
|
||||||
|
assertPlaced(true);
|
||||||
|
assertControlPointCount(4);
|
||||||
|
assertControlPointPosition(1, new Vector2(100, 0));
|
||||||
|
assertControlPointPosition(2, new Vector2(100));
|
||||||
|
assertControlPointType(0, PathType.Linear);
|
||||||
|
assertControlPointType(1, PathType.PerfectCurve);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPlacePerfectCurveSegmentThenPlacePerfectCurveSegment()
|
||||||
|
{
|
||||||
|
addMovementStep(new Vector2(200));
|
||||||
|
addClickStep(MouseButton.Left);
|
||||||
|
|
||||||
|
addMovementStep(new Vector2(300, 200));
|
||||||
|
addClickStep(MouseButton.Left);
|
||||||
|
|
||||||
|
addMovementStep(new Vector2(300, 300));
|
||||||
|
addClickStep(MouseButton.Left);
|
||||||
|
addClickStep(MouseButton.Left);
|
||||||
|
|
||||||
|
addMovementStep(new Vector2(400, 300));
|
||||||
|
addClickStep(MouseButton.Left);
|
||||||
|
|
||||||
|
addMovementStep(new Vector2(400));
|
||||||
|
addClickStep(MouseButton.Right);
|
||||||
|
|
||||||
|
assertPlaced(true);
|
||||||
|
assertControlPointCount(5);
|
||||||
|
assertControlPointPosition(1, new Vector2(100, 0));
|
||||||
|
assertControlPointPosition(2, new Vector2(100));
|
||||||
|
assertControlPointPosition(3, new Vector2(200, 100));
|
||||||
|
assertControlPointPosition(4, new Vector2(200));
|
||||||
|
assertControlPointType(0, PathType.PerfectCurve);
|
||||||
|
assertControlPointType(2, PathType.PerfectCurve);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addMovementStep(Vector2 position) => AddStep($"move mouse to {position}", () => InputManager.MoveMouseTo(InputManager.ToScreenSpace(position)));
|
||||||
|
|
||||||
|
private void addClickStep(MouseButton button)
|
||||||
|
{
|
||||||
|
AddStep($"press {button}", () => InputManager.PressButton(button));
|
||||||
|
AddStep($"release {button}", () => InputManager.ReleaseButton(button));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertPlaced(bool expected) => AddAssert($"slider {(expected ? "placed" : "not placed")}", () => (getSlider() != null) == expected);
|
||||||
|
|
||||||
|
private void assertLength(double expected) => AddAssert($"slider length is {expected}", () => Precision.AlmostEquals(expected, getSlider().Distance, 1));
|
||||||
|
|
||||||
|
private void assertControlPointCount(int expected) => AddAssert($"has {expected} control points", () => getSlider().Path.ControlPoints.Count == expected);
|
||||||
|
|
||||||
|
private void assertControlPointType(int index, PathType type) => AddAssert($"control point {index} is {type}", () => getSlider().Path.ControlPoints[index].Type.Value == type);
|
||||||
|
|
||||||
|
private void assertControlPointPosition(int index, Vector2 position) =>
|
||||||
|
AddAssert($"control point {index} at {position}", () => Precision.AlmostEquals(position, getSlider().Path.ControlPoints[index].Position.Value, 1));
|
||||||
|
|
||||||
|
private Slider getSlider() => HitObjectContainer.Count > 0 ? (Slider)((DrawableSlider)HitObjectContainer[0]).HitObject : null;
|
||||||
|
|
||||||
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableSlider((Slider)hitObject);
|
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableSlider((Slider)hitObject);
|
||||||
protected override PlacementBlueprint CreateBlueprint() => new SliderPlacementBlueprint();
|
protected override PlacementBlueprint CreateBlueprint() => new SliderPlacementBlueprint();
|
||||||
}
|
}
|
||||||
|
253
osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs
Normal file
@ -0,0 +1,253 @@
|
|||||||
|
// 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 Humanizer;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Timing;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osu.Game.Rulesets.Osu.Configuration;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
|
||||||
|
using osu.Game.Storyboards;
|
||||||
|
using osuTK;
|
||||||
|
using static osu.Game.Tests.Visual.OsuTestScene.ClockBackedTestWorkingBeatmap;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class TestSceneSliderSnaking : TestSceneOsuPlayer
|
||||||
|
{
|
||||||
|
[Resolved]
|
||||||
|
private AudioManager audioManager { get; set; }
|
||||||
|
|
||||||
|
private TrackVirtualManual track;
|
||||||
|
|
||||||
|
protected override bool Autoplay => autoplay;
|
||||||
|
private bool autoplay;
|
||||||
|
|
||||||
|
private readonly BindableBool snakingIn = new BindableBool();
|
||||||
|
private readonly BindableBool snakingOut = new BindableBool();
|
||||||
|
|
||||||
|
private const double duration_of_span = 3605;
|
||||||
|
private const double fade_in_modifier = -1200;
|
||||||
|
|
||||||
|
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
|
||||||
|
{
|
||||||
|
var working = new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
|
||||||
|
track = (TrackVirtualManual)working.Track;
|
||||||
|
return working;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(RulesetConfigCache configCache)
|
||||||
|
{
|
||||||
|
var config = (OsuRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance());
|
||||||
|
config.BindWith(OsuRulesetSetting.SnakingInSliders, snakingIn);
|
||||||
|
config.BindWith(OsuRulesetSetting.SnakingOutSliders, snakingOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DrawableSlider slider;
|
||||||
|
|
||||||
|
[SetUpSteps]
|
||||||
|
public override void SetUpSteps() { }
|
||||||
|
|
||||||
|
[TestCase(0)]
|
||||||
|
[TestCase(1)]
|
||||||
|
[TestCase(2)]
|
||||||
|
public void TestSnakingEnabled(int sliderIndex)
|
||||||
|
{
|
||||||
|
AddStep("enable autoplay", () => autoplay = true);
|
||||||
|
base.SetUpSteps();
|
||||||
|
AddUntilStep("wait for track to start running", () => track.IsRunning);
|
||||||
|
|
||||||
|
double startTime = hitObjects[sliderIndex].StartTime;
|
||||||
|
retrieveDrawableSlider(sliderIndex);
|
||||||
|
setSnaking(true);
|
||||||
|
|
||||||
|
ensureSnakingIn(startTime + fade_in_modifier);
|
||||||
|
|
||||||
|
for (int i = 0; i < sliderIndex; i++)
|
||||||
|
{
|
||||||
|
// non-final repeats should not snake out
|
||||||
|
ensureNoSnakingOut(startTime, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// final repeat should snake out
|
||||||
|
ensureSnakingOut(startTime, sliderIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase(0)]
|
||||||
|
[TestCase(1)]
|
||||||
|
[TestCase(2)]
|
||||||
|
public void TestSnakingDisabled(int sliderIndex)
|
||||||
|
{
|
||||||
|
AddStep("have autoplay", () => autoplay = true);
|
||||||
|
base.SetUpSteps();
|
||||||
|
AddUntilStep("wait for track to start running", () => track.IsRunning);
|
||||||
|
|
||||||
|
double startTime = hitObjects[sliderIndex].StartTime;
|
||||||
|
retrieveDrawableSlider(sliderIndex);
|
||||||
|
setSnaking(false);
|
||||||
|
|
||||||
|
ensureNoSnakingIn(startTime + fade_in_modifier);
|
||||||
|
|
||||||
|
for (int i = 0; i <= sliderIndex; i++)
|
||||||
|
{
|
||||||
|
// no snaking out ever, including final repeat
|
||||||
|
ensureNoSnakingOut(startTime, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRepeatArrowDoesNotMoveWhenHit()
|
||||||
|
{
|
||||||
|
AddStep("enable autoplay", () => autoplay = true);
|
||||||
|
setSnaking(true);
|
||||||
|
base.SetUpSteps();
|
||||||
|
|
||||||
|
// repeat might have a chance to update its position depending on where in the frame its hit,
|
||||||
|
// so some leniency is allowed here instead of checking strict equality
|
||||||
|
checkPositionChange(16600, sliderRepeat, positionAlmostSame);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRepeatArrowMovesWhenNotHit()
|
||||||
|
{
|
||||||
|
AddStep("disable autoplay", () => autoplay = false);
|
||||||
|
setSnaking(true);
|
||||||
|
base.SetUpSteps();
|
||||||
|
|
||||||
|
checkPositionChange(16600, sliderRepeat, positionDecreased);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void retrieveDrawableSlider(int index) => AddStep($"retrieve {(index + 1).ToOrdinalWords()} slider", () =>
|
||||||
|
{
|
||||||
|
slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.ElementAt(index);
|
||||||
|
});
|
||||||
|
|
||||||
|
private void ensureSnakingIn(double startTime) => checkPositionChange(startTime, sliderEnd, positionIncreased);
|
||||||
|
private void ensureNoSnakingIn(double startTime) => checkPositionChange(startTime, sliderEnd, positionRemainsSame);
|
||||||
|
|
||||||
|
private void ensureSnakingOut(double startTime, int repeatIndex)
|
||||||
|
{
|
||||||
|
var repeatTime = timeAtRepeat(startTime, repeatIndex);
|
||||||
|
|
||||||
|
if (repeatIndex % 2 == 0)
|
||||||
|
checkPositionChange(repeatTime, sliderStart, positionIncreased);
|
||||||
|
else
|
||||||
|
checkPositionChange(repeatTime, sliderEnd, positionDecreased);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureNoSnakingOut(double startTime, int repeatIndex) =>
|
||||||
|
checkPositionChange(timeAtRepeat(startTime, repeatIndex), positionAtRepeat(repeatIndex), positionRemainsSame);
|
||||||
|
|
||||||
|
private double timeAtRepeat(double startTime, int repeatIndex) => startTime + 100 + duration_of_span * repeatIndex;
|
||||||
|
private Func<Vector2> positionAtRepeat(int repeatIndex) => repeatIndex % 2 == 0 ? (Func<Vector2>)sliderStart : sliderEnd;
|
||||||
|
|
||||||
|
private List<Vector2> sliderCurve => ((PlaySliderBody)slider.Body.Drawable).CurrentCurve;
|
||||||
|
private Vector2 sliderStart() => sliderCurve.First();
|
||||||
|
private Vector2 sliderEnd() => sliderCurve.Last();
|
||||||
|
|
||||||
|
private Vector2 sliderRepeat()
|
||||||
|
{
|
||||||
|
var drawable = Player.DrawableRuleset.Playfield.AllHitObjects.ElementAt(1);
|
||||||
|
var repeat = drawable.ChildrenOfType<Container<DrawableSliderRepeat>>().First().Children.First();
|
||||||
|
return repeat.Position;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool positionRemainsSame(Vector2 previous, Vector2 current) => previous == current;
|
||||||
|
private bool positionIncreased(Vector2 previous, Vector2 current) => current.X > previous.X && current.Y > previous.Y;
|
||||||
|
private bool positionDecreased(Vector2 previous, Vector2 current) => current.X < previous.X && current.Y < previous.Y;
|
||||||
|
private bool positionAlmostSame(Vector2 previous, Vector2 current) => Precision.AlmostEquals(previous, current, 1);
|
||||||
|
|
||||||
|
private void checkPositionChange(double startTime, Func<Vector2> positionToCheck, Func<Vector2, Vector2, bool> positionAssertion)
|
||||||
|
{
|
||||||
|
Vector2 previousPosition = Vector2.Zero;
|
||||||
|
|
||||||
|
string positionDescription = positionToCheck.Method.Name.Humanize(LetterCasing.LowerCase);
|
||||||
|
string assertionDescription = positionAssertion.Method.Name.Humanize(LetterCasing.LowerCase);
|
||||||
|
|
||||||
|
addSeekStep(startTime);
|
||||||
|
AddStep($"save {positionDescription} position", () => previousPosition = positionToCheck.Invoke());
|
||||||
|
addSeekStep(startTime + 100);
|
||||||
|
AddAssert($"{positionDescription} {assertionDescription}", () =>
|
||||||
|
{
|
||||||
|
var currentPosition = positionToCheck.Invoke();
|
||||||
|
return positionAssertion.Invoke(previousPosition, currentPosition);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setSnaking(bool value)
|
||||||
|
{
|
||||||
|
AddStep($"{(value ? "enable" : "disable")} snaking", () =>
|
||||||
|
{
|
||||||
|
snakingIn.Value = value;
|
||||||
|
snakingOut.Value = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addSeekStep(double time)
|
||||||
|
{
|
||||||
|
AddStep($"seek to {time}", () => track.Seek(time));
|
||||||
|
|
||||||
|
AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap
|
||||||
|
{
|
||||||
|
HitObjects = hitObjects
|
||||||
|
};
|
||||||
|
|
||||||
|
private readonly List<HitObject> hitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new Slider
|
||||||
|
{
|
||||||
|
StartTime = 3000,
|
||||||
|
Position = new Vector2(100, 100),
|
||||||
|
Path = new SliderPath(PathType.PerfectCurve, new[]
|
||||||
|
{
|
||||||
|
Vector2.Zero,
|
||||||
|
new Vector2(300, 200)
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
new Slider
|
||||||
|
{
|
||||||
|
StartTime = 13000,
|
||||||
|
Position = new Vector2(100, 100),
|
||||||
|
Path = new SliderPath(PathType.PerfectCurve, new[]
|
||||||
|
{
|
||||||
|
Vector2.Zero,
|
||||||
|
new Vector2(300, 200)
|
||||||
|
}),
|
||||||
|
RepeatCount = 1,
|
||||||
|
},
|
||||||
|
new Slider
|
||||||
|
{
|
||||||
|
StartTime = 23000,
|
||||||
|
Position = new Vector2(100, 100),
|
||||||
|
Path = new SliderPath(PathType.PerfectCurve, new[]
|
||||||
|
{
|
||||||
|
Vector2.Zero,
|
||||||
|
new Vector2(300, 200)
|
||||||
|
}),
|
||||||
|
RepeatCount = 2,
|
||||||
|
},
|
||||||
|
new HitCircle
|
||||||
|
{
|
||||||
|
StartTime = 199999,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -16,22 +16,25 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class PathControlPointConnectionPiece : CompositeDrawable
|
public class PathControlPointConnectionPiece : CompositeDrawable
|
||||||
{
|
{
|
||||||
public PathControlPoint ControlPoint;
|
public readonly PathControlPoint ControlPoint;
|
||||||
|
|
||||||
private readonly Path path;
|
private readonly Path path;
|
||||||
private readonly Slider slider;
|
private readonly Slider slider;
|
||||||
|
private readonly int controlPointIndex;
|
||||||
|
|
||||||
private IBindable<Vector2> sliderPosition;
|
private IBindable<Vector2> sliderPosition;
|
||||||
private IBindable<int> pathVersion;
|
private IBindable<int> pathVersion;
|
||||||
|
|
||||||
public PathControlPointConnectionPiece(Slider slider, PathControlPoint controlPoint)
|
public PathControlPointConnectionPiece(Slider slider, int controlPointIndex)
|
||||||
{
|
{
|
||||||
this.slider = slider;
|
this.slider = slider;
|
||||||
ControlPoint = controlPoint;
|
this.controlPointIndex = controlPointIndex;
|
||||||
|
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
AutoSizeAxes = Axes.Both;
|
AutoSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
ControlPoint = slider.Path.ControlPoints[controlPointIndex];
|
||||||
|
|
||||||
InternalChild = path = new SmoothPath
|
InternalChild = path = new SmoothPath
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
@ -61,13 +64,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
|
|
||||||
path.ClearVertices();
|
path.ClearVertices();
|
||||||
|
|
||||||
int index = slider.Path.ControlPoints.IndexOf(ControlPoint) + 1;
|
int nextIndex = controlPointIndex + 1;
|
||||||
|
if (nextIndex == 0 || nextIndex >= slider.Path.ControlPoints.Count)
|
||||||
if (index == 0 || index == slider.Path.ControlPoints.Count)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
path.AddVertex(Vector2.Zero);
|
path.AddVertex(Vector2.Zero);
|
||||||
path.AddVertex(slider.Path.ControlPoints[index].Position.Value - ControlPoint.Position.Value);
|
path.AddVertex(slider.Path.ControlPoints[nextIndex].Position.Value - ControlPoint.Position.Value);
|
||||||
|
|
||||||
path.OriginPosition = path.PositionInBoundingBox(Vector2.Zero);
|
path.OriginPosition = path.PositionInBoundingBox(Vector2.Zero);
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
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;
|
||||||
@ -12,6 +13,7 @@ using osu.Game.Graphics;
|
|||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Screens.Edit;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
@ -33,6 +35,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
private readonly Container marker;
|
private readonly Container marker;
|
||||||
private readonly Drawable markerRing;
|
private readonly Drawable markerRing;
|
||||||
|
|
||||||
|
[Resolved(CanBeNull = true)]
|
||||||
|
private IEditorChangeHandler changeHandler { get; set; }
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
private IDistanceSnapProvider snapProvider { get; set; }
|
private IDistanceSnapProvider snapProvider { get; set; }
|
||||||
|
|
||||||
@ -47,6 +52,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
this.slider = slider;
|
this.slider = slider;
|
||||||
ControlPoint = controlPoint;
|
ControlPoint = controlPoint;
|
||||||
|
|
||||||
|
controlPoint.Type.BindValueChanged(_ => updateMarkerDisplay());
|
||||||
|
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
AutoSizeAxes = Axes.Both;
|
AutoSizeAxes = Axes.Both;
|
||||||
|
|
||||||
@ -137,7 +144,16 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
|
|
||||||
protected override bool OnClick(ClickEvent e) => RequestSelection != null;
|
protected override bool OnClick(ClickEvent e) => RequestSelection != null;
|
||||||
|
|
||||||
protected override bool OnDragStart(DragStartEvent e) => e.Button == MouseButton.Left;
|
protected override bool OnDragStart(DragStartEvent e)
|
||||||
|
{
|
||||||
|
if (e.Button == MouseButton.Left)
|
||||||
|
{
|
||||||
|
changeHandler?.BeginChange();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
protected override void OnDrag(DragEvent e)
|
protected override void OnDrag(DragEvent e)
|
||||||
{
|
{
|
||||||
@ -158,6 +174,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
ControlPoint.Position.Value += e.Delta;
|
ControlPoint.Position.Value += e.Delta;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnDragEnd(DragEndEvent e) => changeHandler?.EndChange();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the state of the circular control point marker.
|
/// Updates the state of the circular control point marker.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -168,8 +186,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
markerRing.Alpha = IsSelected.Value ? 1 : 0;
|
markerRing.Alpha = IsSelected.Value ? 1 : 0;
|
||||||
|
|
||||||
Color4 colour = ControlPoint.Type.Value != null ? colours.Red : colours.Yellow;
|
Color4 colour = ControlPoint.Type.Value != null ? colours.Red : colours.Yellow;
|
||||||
|
|
||||||
if (IsHovered || IsSelected.Value)
|
if (IsHovered || IsSelected.Value)
|
||||||
colour = Color4.White;
|
colour = colour.Lighten(1);
|
||||||
|
|
||||||
marker.Colour = colour;
|
marker.Colour = colour;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Specialized;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
@ -24,17 +25,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
public class PathControlPointVisualiser : CompositeDrawable, IKeyBindingHandler<PlatformAction>, IHasContextMenu
|
public class PathControlPointVisualiser : CompositeDrawable, IKeyBindingHandler<PlatformAction>, IHasContextMenu
|
||||||
{
|
{
|
||||||
internal readonly Container<PathControlPointPiece> Pieces;
|
internal readonly Container<PathControlPointPiece> Pieces;
|
||||||
|
internal readonly Container<PathControlPointConnectionPiece> Connections;
|
||||||
|
|
||||||
private readonly Container<PathControlPointConnectionPiece> connections;
|
private readonly IBindableList<PathControlPoint> controlPoints = new BindableList<PathControlPoint>();
|
||||||
|
|
||||||
private readonly Slider slider;
|
private readonly Slider slider;
|
||||||
|
|
||||||
private readonly bool allowSelection;
|
private readonly bool allowSelection;
|
||||||
|
|
||||||
private InputManager inputManager;
|
private InputManager inputManager;
|
||||||
|
|
||||||
private IBindableList<PathControlPoint> controlPoints;
|
|
||||||
|
|
||||||
public Action<List<PathControlPoint>> RemoveControlPointsRequested;
|
public Action<List<PathControlPoint>> RemoveControlPointsRequested;
|
||||||
|
|
||||||
public PathControlPointVisualiser(Slider slider, bool allowSelection)
|
public PathControlPointVisualiser(Slider slider, bool allowSelection)
|
||||||
@ -46,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
connections = new Container<PathControlPointConnectionPiece> { RelativeSizeAxes = Axes.Both },
|
Connections = new Container<PathControlPointConnectionPiece> { RelativeSizeAxes = Axes.Both },
|
||||||
Pieces = new Container<PathControlPointPiece> { RelativeSizeAxes = Axes.Both }
|
Pieces = new Container<PathControlPointPiece> { RelativeSizeAxes = Axes.Both }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -57,33 +55,38 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
|
|
||||||
inputManager = GetContainingInputManager();
|
inputManager = GetContainingInputManager();
|
||||||
|
|
||||||
controlPoints = slider.Path.ControlPoints.GetBoundCopy();
|
controlPoints.CollectionChanged += onControlPointsChanged;
|
||||||
controlPoints.ItemsAdded += addControlPoints;
|
controlPoints.BindTo(slider.Path.ControlPoints);
|
||||||
controlPoints.ItemsRemoved += removeControlPoints;
|
|
||||||
|
|
||||||
addControlPoints(controlPoints);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addControlPoints(IEnumerable<PathControlPoint> controlPoints)
|
private void onControlPointsChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||||
{
|
{
|
||||||
foreach (var point in controlPoints)
|
switch (e.Action)
|
||||||
{
|
{
|
||||||
Pieces.Add(new PathControlPointPiece(slider, point).With(d =>
|
case NotifyCollectionChangedAction.Add:
|
||||||
{
|
for (int i = 0; i < e.NewItems.Count; i++)
|
||||||
if (allowSelection)
|
{
|
||||||
d.RequestSelection = selectPiece;
|
var point = (PathControlPoint)e.NewItems[i];
|
||||||
}));
|
|
||||||
|
|
||||||
connections.Add(new PathControlPointConnectionPiece(slider, point));
|
Pieces.Add(new PathControlPointPiece(slider, point).With(d =>
|
||||||
}
|
{
|
||||||
}
|
if (allowSelection)
|
||||||
|
d.RequestSelection = selectPiece;
|
||||||
|
}));
|
||||||
|
|
||||||
private void removeControlPoints(IEnumerable<PathControlPoint> controlPoints)
|
Connections.Add(new PathControlPointConnectionPiece(slider, e.NewStartingIndex + i));
|
||||||
{
|
}
|
||||||
foreach (var point in controlPoints)
|
|
||||||
{
|
break;
|
||||||
Pieces.RemoveAll(p => p.ControlPoint == point);
|
|
||||||
connections.RemoveAll(c => c.ControlPoint == point);
|
case NotifyCollectionChangedAction.Remove:
|
||||||
|
foreach (var point in e.OldItems.Cast<PathControlPoint>())
|
||||||
|
{
|
||||||
|
Pieces.RemoveAll(p => p.ControlPoint == point);
|
||||||
|
Connections.RemoveAll(c => c.ControlPoint == point);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +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 System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
@ -23,6 +26,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
private SliderBodyPiece bodyPiece;
|
private SliderBodyPiece bodyPiece;
|
||||||
private HitCirclePiece headCirclePiece;
|
private HitCirclePiece headCirclePiece;
|
||||||
private HitCirclePiece tailCirclePiece;
|
private HitCirclePiece tailCirclePiece;
|
||||||
|
private PathControlPointVisualiser controlPointVisualiser;
|
||||||
|
|
||||||
private InputManager inputManager;
|
private InputManager inputManager;
|
||||||
|
|
||||||
@ -51,7 +55,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
bodyPiece = new SliderBodyPiece(),
|
bodyPiece = new SliderBodyPiece(),
|
||||||
headCirclePiece = new HitCirclePiece(),
|
headCirclePiece = new HitCirclePiece(),
|
||||||
tailCirclePiece = new HitCirclePiece(),
|
tailCirclePiece = new HitCirclePiece(),
|
||||||
new PathControlPointVisualiser(HitObject, false)
|
controlPointVisualiser = new PathControlPointVisualiser(HitObject, false)
|
||||||
};
|
};
|
||||||
|
|
||||||
setState(PlacementState.Initial);
|
setState(PlacementState.Initial);
|
||||||
@ -73,11 +77,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case PlacementState.Body:
|
case PlacementState.Body:
|
||||||
ensureCursor();
|
updateCursor();
|
||||||
|
|
||||||
// The given screen-space position may have been externally snapped, but the unsnapped position from the input manager
|
|
||||||
// is used instead since snapping control points doesn't make much sense
|
|
||||||
cursor.Position.Value = ToLocalSpace(inputManager.CurrentState.Mouse.Position) - HitObject.Position;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -91,17 +91,27 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case PlacementState.Body:
|
case PlacementState.Body:
|
||||||
switch (e.Button)
|
if (e.Button != MouseButton.Left)
|
||||||
{
|
break;
|
||||||
case MouseButton.Left:
|
|
||||||
ensureCursor();
|
|
||||||
|
|
||||||
// Detatch the cursor
|
if (canPlaceNewControlPoint(out var lastPoint))
|
||||||
cursor = null;
|
{
|
||||||
break;
|
// Place a new point by detatching the current cursor.
|
||||||
|
updateCursor();
|
||||||
|
cursor = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Transform the last point into a new segment.
|
||||||
|
Debug.Assert(lastPoint != null);
|
||||||
|
|
||||||
|
segmentStart = lastPoint;
|
||||||
|
segmentStart.Type.Value = PathType.Linear;
|
||||||
|
|
||||||
|
currentSegmentLength = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -114,16 +124,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
base.OnMouseUp(e);
|
base.OnMouseUp(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnDoubleClick(DoubleClickEvent e)
|
|
||||||
{
|
|
||||||
// Todo: This should all not occur on double click, but rather if the previous control point is hovered.
|
|
||||||
segmentStart = HitObject.Path.ControlPoints[^1];
|
|
||||||
segmentStart.Type.Value = PathType.Linear;
|
|
||||||
|
|
||||||
currentSegmentLength = 1;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void beginCurve()
|
private void beginCurve()
|
||||||
{
|
{
|
||||||
BeginPlacement(commitStart: true);
|
BeginPlacement(commitStart: true);
|
||||||
@ -161,17 +161,50 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ensureCursor()
|
private void updateCursor()
|
||||||
{
|
{
|
||||||
if (cursor == null)
|
if (canPlaceNewControlPoint(out _))
|
||||||
{
|
{
|
||||||
HitObject.Path.ControlPoints.Add(cursor = new PathControlPoint { Position = { Value = Vector2.Zero } });
|
// The cursor does not overlap a previous control point, so it can be added if not already existing.
|
||||||
currentSegmentLength++;
|
if (cursor == null)
|
||||||
|
{
|
||||||
|
HitObject.Path.ControlPoints.Add(cursor = new PathControlPoint { Position = { Value = Vector2.Zero } });
|
||||||
|
|
||||||
|
// The path type should be adjusted in the progression of updatePathType() (Linear -> PC -> Bezier).
|
||||||
|
currentSegmentLength++;
|
||||||
|
updatePathType();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the cursor position.
|
||||||
|
cursor.Position.Value = ToLocalSpace(inputManager.CurrentState.Mouse.Position) - HitObject.Position;
|
||||||
|
}
|
||||||
|
else if (cursor != null)
|
||||||
|
{
|
||||||
|
// The cursor overlaps a previous control point, so it's removed.
|
||||||
|
HitObject.Path.ControlPoints.Remove(cursor);
|
||||||
|
cursor = null;
|
||||||
|
|
||||||
|
// The path type should be adjusted in the reverse progression of updatePathType() (Bezier -> PC -> Linear).
|
||||||
|
currentSegmentLength--;
|
||||||
updatePathType();
|
updatePathType();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether a new control point can be placed at the current mouse position.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lastPoint">The last-placed control point. May be null, but is not null if <c>false</c> is returned.</param>
|
||||||
|
/// <returns>Whether a new control point can be placed at the current position.</returns>
|
||||||
|
private bool canPlaceNewControlPoint([CanBeNull] out PathControlPoint lastPoint)
|
||||||
|
{
|
||||||
|
// We cannot rely on the ordering of drawable pieces, so find the respective drawable piece by searching for the last non-cursor control point.
|
||||||
|
var last = HitObject.Path.ControlPoints.LastOrDefault(p => p != cursor);
|
||||||
|
var lastPiece = controlPointVisualiser.Pieces.Single(p => p.ControlPoint == last);
|
||||||
|
|
||||||
|
lastPoint = last;
|
||||||
|
return lastPiece?.IsHovered != true;
|
||||||
|
}
|
||||||
|
|
||||||
private void updateSlider()
|
private void updateSlider()
|
||||||
{
|
{
|
||||||
HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance;
|
HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance;
|
||||||
|
@ -15,6 +15,7 @@ using osu.Game.Rulesets.Objects;
|
|||||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
using osu.Game.Screens.Edit;
|
||||||
using osu.Game.Screens.Edit.Compose;
|
using osu.Game.Screens.Edit.Compose;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
@ -34,6 +35,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
private IPlacementHandler placementHandler { get; set; }
|
private IPlacementHandler placementHandler { get; set; }
|
||||||
|
|
||||||
|
[Resolved(CanBeNull = true)]
|
||||||
|
private EditorBeatmap editorBeatmap { get; set; }
|
||||||
|
|
||||||
|
[Resolved(CanBeNull = true)]
|
||||||
|
private IEditorChangeHandler changeHandler { get; set; }
|
||||||
|
|
||||||
public SliderSelectionBlueprint(DrawableSlider slider)
|
public SliderSelectionBlueprint(DrawableSlider slider)
|
||||||
: base(slider)
|
: base(slider)
|
||||||
{
|
{
|
||||||
@ -88,7 +95,16 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
|
|
||||||
private int? placementControlPointIndex;
|
private int? placementControlPointIndex;
|
||||||
|
|
||||||
protected override bool OnDragStart(DragStartEvent e) => placementControlPointIndex != null;
|
protected override bool OnDragStart(DragStartEvent e)
|
||||||
|
{
|
||||||
|
if (placementControlPointIndex != null)
|
||||||
|
{
|
||||||
|
changeHandler?.BeginChange();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
protected override void OnDrag(DragEvent e)
|
protected override void OnDrag(DragEvent e)
|
||||||
{
|
{
|
||||||
@ -99,7 +115,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
|
|
||||||
protected override void OnDragEnd(DragEndEvent e)
|
protected override void OnDragEnd(DragEndEvent e)
|
||||||
{
|
{
|
||||||
placementControlPointIndex = null;
|
if (placementControlPointIndex != null)
|
||||||
|
{
|
||||||
|
placementControlPointIndex = null;
|
||||||
|
changeHandler?.EndChange();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private BindableList<PathControlPoint> controlPoints => HitObject.Path.ControlPoints;
|
private BindableList<PathControlPoint> controlPoints => HitObject.Path.ControlPoints;
|
||||||
@ -162,7 +182,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
private void updatePath()
|
private void updatePath()
|
||||||
{
|
{
|
||||||
HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance;
|
HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance;
|
||||||
UpdateHitObject();
|
editorBeatmap?.UpdateHitObject(HitObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override MenuItem[] ContextMenuItems => new MenuItem[]
|
public override MenuItem[] ContextMenuItems => new MenuItem[]
|
||||||
|
@ -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;
|
||||||
|
@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
|||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Alpha = 0.5f,
|
Alpha = 0.5f,
|
||||||
}
|
}
|
||||||
}, confineMode: ConfineMode.NoScaling);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public double AnimationStartTime { get; set; }
|
public double AnimationStartTime { get; set; }
|
||||||
|
@ -120,7 +120,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
|
|
||||||
var result = HitObject.HitWindows.ResultFor(timeOffset);
|
var result = HitObject.HitWindows.ResultFor(timeOffset);
|
||||||
|
|
||||||
if (result == HitResult.None)
|
if (result == HitResult.None || CheckHittable?.Invoke(this, Time.Current) == false)
|
||||||
{
|
{
|
||||||
Shake(Math.Abs(timeOffset) - HitObject.HitWindows.WindowFor(HitResult.Miss));
|
Shake(Math.Abs(timeOffset) - HitObject.HitWindows.WindowFor(HitResult.Miss));
|
||||||
return;
|
return;
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
// 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 osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Osu.Judgements;
|
using osu.Game.Rulesets.Osu.Judgements;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||||
{
|
{
|
||||||
@ -16,6 +19,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
// Must be set to update IsHovered as it's used in relax mdo to detect osu hit objects.
|
// Must be set to update IsHovered as it's used in relax mdo to detect osu hit objects.
|
||||||
public override bool HandlePositionalInput => true;
|
public override bool HandlePositionalInput => true;
|
||||||
|
|
||||||
|
protected override float SamplePlaybackPosition => HitObject.X / OsuPlayfield.BASE_SIZE.X;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether this <see cref="DrawableOsuHitObject"/> can be hit.
|
||||||
|
/// If non-null, judgements will be ignored (resulting in a shake) whilst the function returns false.
|
||||||
|
/// </summary>
|
||||||
|
public Func<DrawableHitObject, double, bool> CheckHittable;
|
||||||
|
|
||||||
protected DrawableOsuHitObject(OsuHitObject hitObject)
|
protected DrawableOsuHitObject(OsuHitObject hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
{
|
{
|
||||||
@ -54,6 +65,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Causes this <see cref="DrawableOsuHitObject"/> to get missed, disregarding all conditions in implementations of <see cref="DrawableHitObject.CheckForResult"/>.
|
||||||
|
/// </summary>
|
||||||
|
public void MissForcefully() => ApplyResult(r => r.Type = HitResult.Miss);
|
||||||
|
|
||||||
protected override JudgementResult CreateResult(Judgement judgement) => new OsuJudgementResult(HitObject, judgement);
|
protected override JudgementResult CreateResult(Judgement judgement) => new OsuJudgementResult(HitObject, judgement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -124,8 +124,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
case SliderTailCircle tail:
|
case SliderTailCircle tail:
|
||||||
return new DrawableSliderTail(slider, tail);
|
return new DrawableSliderTail(slider, tail);
|
||||||
|
|
||||||
case HitCircle 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 };
|
||||||
@ -186,7 +190,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
base.ApplySkin(skin, allowFallback);
|
base.ApplySkin(skin, allowFallback);
|
||||||
|
|
||||||
bool allowBallTint = skin.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.AllowSliderBallTint)?.Value ?? false;
|
bool allowBallTint = skin.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.AllowSliderBallTint)?.Value ?? false;
|
||||||
Ball.Colour = allowBallTint ? AccentColour.Value : Color4.White;
|
Ball.AccentColour = allowBallTint ? AccentColour.Value : Color4.White;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||||
|
@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
|
|
||||||
private readonly Slider slider;
|
private readonly Slider slider;
|
||||||
|
|
||||||
public DrawableSliderHead(Slider slider, HitCircle h)
|
public DrawableSliderHead(Slider slider, SliderHeadCircle h)
|
||||||
: base(h)
|
: base(h)
|
||||||
{
|
{
|
||||||
this.slider = slider;
|
this.slider = slider;
|
||||||
|
@ -31,7 +31,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
|
|
||||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
||||||
|
|
||||||
Blending = BlendingParameters.Additive;
|
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
InternalChild = scaleContainer = new ReverseArrowPiece();
|
InternalChild = scaleContainer = new ReverseArrowPiece();
|
||||||
|
@ -8,11 +8,16 @@ using osu.Framework.Graphics.Sprites;
|
|||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||||
{
|
{
|
||||||
public class ReverseArrowPiece : BeatSyncedContainer
|
public class ReverseArrowPiece : BeatSyncedContainer
|
||||||
{
|
{
|
||||||
|
[Resolved]
|
||||||
|
private DrawableHitObject drawableRepeat { get; set; }
|
||||||
|
|
||||||
public ReverseArrowPiece()
|
public ReverseArrowPiece()
|
||||||
{
|
{
|
||||||
Divisor = 2;
|
Divisor = 2;
|
||||||
@ -21,13 +26,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
|||||||
Anchor = Anchor.Centre;
|
Anchor = Anchor.Centre;
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
Blending = BlendingParameters.Additive;
|
|
||||||
|
|
||||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
||||||
|
|
||||||
Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.ReverseArrow), _ => new SpriteIcon
|
Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.ReverseArrow), _ => new SpriteIcon
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
Icon = FontAwesome.Solid.ChevronRight,
|
Icon = FontAwesome.Solid.ChevronRight,
|
||||||
Size = new Vector2(0.35f)
|
Size = new Vector2(0.35f)
|
||||||
})
|
})
|
||||||
@ -37,7 +41,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) =>
|
protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes)
|
||||||
Child.ScaleTo(1.3f).ScaleTo(1f, timingPoint.BeatLength, Easing.Out);
|
{
|
||||||
|
if (!drawableRepeat.IsHit)
|
||||||
|
Child.ScaleTo(1.3f).ScaleTo(1f, timingPoint.BeatLength, Easing.Out);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
|||||||
this.drawableSlider = drawableSlider;
|
this.drawableSlider = drawableSlider;
|
||||||
this.slider = slider;
|
this.slider = slider;
|
||||||
|
|
||||||
Blending = BlendingParameters.Additive;
|
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
||||||
@ -241,6 +240,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
|||||||
Scale = new Vector2(radius / OsuHitObject.OBJECT_RADIUS),
|
Scale = new Vector2(radius / OsuHitObject.OBJECT_RADIUS),
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
BorderThickness = 10,
|
BorderThickness = 10,
|
||||||
BorderColour = Color4.White,
|
BorderColour = Color4.White,
|
||||||
Alpha = 1,
|
Alpha = 1,
|
||||||
|
@ -155,7 +155,7 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case SliderEventType.Head:
|
case SliderEventType.Head:
|
||||||
AddNested(HeadCircle = new SliderCircle
|
AddNested(HeadCircle = new SliderHeadCircle
|
||||||
{
|
{
|
||||||
StartTime = e.Time,
|
StartTime = e.Time,
|
||||||
Position = Position,
|
Position = Position,
|
||||||
|
9
osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Objects
|
||||||
|
{
|
||||||
|
public class SliderHeadCircle : HitCircle
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
|||||||
switch (osuComponent.Component)
|
switch (osuComponent.Component)
|
||||||
{
|
{
|
||||||
case OsuSkinComponents.FollowPoint:
|
case OsuSkinComponents.FollowPoint:
|
||||||
return this.GetAnimation(component.LookupName, true, false, true);
|
return this.GetAnimation(component.LookupName, true, false, true, startAtCurrentTime: false);
|
||||||
|
|
||||||
case OsuSkinComponents.SliderFollowCircle:
|
case OsuSkinComponents.SliderFollowCircle:
|
||||||
var followCircle = this.GetAnimation("sliderfollowcircle", true, true, true);
|
var followCircle = this.GetAnimation("sliderfollowcircle", true, true, true);
|
||||||
@ -132,6 +132,12 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
|||||||
return SkinUtils.As<TValue>(new BindableFloat(LEGACY_CIRCLE_RADIUS));
|
return SkinUtils.As<TValue>(new BindableFloat(LEGACY_CIRCLE_RADIUS));
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case OsuSkinConfiguration.HitCircleOverlayAboveNumber:
|
||||||
|
// See https://osu.ppy.sh/help/wiki/Skinning/skin.ini#%5Bgeneral%5D
|
||||||
|
// HitCircleOverlayAboveNumer (with typo) should still be supported for now.
|
||||||
|
return source.GetConfig<OsuSkinConfiguration, TValue>(OsuSkinConfiguration.HitCircleOverlayAboveNumber) ??
|
||||||
|
source.GetConfig<OsuSkinConfiguration, TValue>(OsuSkinConfiguration.HitCircleOverlayAboveNumer);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
@ -12,6 +12,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
|||||||
AllowSliderBallTint,
|
AllowSliderBallTint,
|
||||||
CursorExpand,
|
CursorExpand,
|
||||||
CursorRotate,
|
CursorRotate,
|
||||||
HitCircleOverlayAboveNumber
|
HitCircleOverlayAboveNumber,
|
||||||
|
HitCircleOverlayAboveNumer // Some old skins will have this typo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
106
osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.UI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Ensures that <see cref="HitObject"/>s are hit in-order. Affectionately known as "note lock".
|
||||||
|
/// If a <see cref="HitObject"/> is hit out of order:
|
||||||
|
/// <list type="number">
|
||||||
|
/// <item><description>The hit is blocked if it occurred earlier than the previous <see cref="HitObject"/>'s start time.</description></item>
|
||||||
|
/// <item><description>The hit causes all previous <see cref="HitObject"/>s to missed otherwise.</description></item>
|
||||||
|
/// </list>
|
||||||
|
/// </summary>
|
||||||
|
public class OrderedHitPolicy
|
||||||
|
{
|
||||||
|
private readonly HitObjectContainer hitObjectContainer;
|
||||||
|
|
||||||
|
public OrderedHitPolicy(HitObjectContainer hitObjectContainer)
|
||||||
|
{
|
||||||
|
this.hitObjectContainer = hitObjectContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether a <see cref="DrawableHitObject"/> can be hit at a point in time.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hitObject">The <see cref="DrawableHitObject"/> to check.</param>
|
||||||
|
/// <param name="time">The time to check.</param>
|
||||||
|
/// <returns>Whether <paramref name="hitObject"/> can be hit at the given <paramref name="time"/>.</returns>
|
||||||
|
public bool IsHittable(DrawableHitObject hitObject, double time)
|
||||||
|
{
|
||||||
|
DrawableHitObject blockingObject = null;
|
||||||
|
|
||||||
|
foreach (var obj in enumerateHitObjectsUpTo(hitObject.HitObject.StartTime))
|
||||||
|
{
|
||||||
|
if (hitObjectCanBlockFutureHits(obj))
|
||||||
|
blockingObject = obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is no previous hitobject, allow the hit.
|
||||||
|
if (blockingObject == null)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// A hit is allowed if:
|
||||||
|
// 1. The last blocking hitobject has been judged.
|
||||||
|
// 2. The current time is after the last hitobject's start time.
|
||||||
|
// Hits at exactly the same time as the blocking hitobject are allowed for maps that contain simultaneous hitobjects (e.g. /b/372245).
|
||||||
|
return blockingObject.Judged || time >= blockingObject.HitObject.StartTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles a <see cref="HitObject"/> being hit to potentially miss all earlier <see cref="HitObject"/>s.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hitObject">The <see cref="HitObject"/> that was hit.</param>
|
||||||
|
public void HandleHit(DrawableHitObject hitObject)
|
||||||
|
{
|
||||||
|
// Hitobjects which themselves don't block future hitobjects don't cause misses (e.g. slider ticks, spinners).
|
||||||
|
if (!hitObjectCanBlockFutureHits(hitObject))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!IsHittable(hitObject, hitObject.HitObject.StartTime + hitObject.Result.TimeOffset))
|
||||||
|
throw new InvalidOperationException($"A {hitObject} was hit before it became hittable!");
|
||||||
|
|
||||||
|
foreach (var obj in enumerateHitObjectsUpTo(hitObject.HitObject.StartTime))
|
||||||
|
{
|
||||||
|
if (obj.Judged)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (hitObjectCanBlockFutureHits(obj))
|
||||||
|
((DrawableOsuHitObject)obj).MissForcefully();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether a <see cref="HitObject"/> blocks hits on future <see cref="HitObject"/>s until its start time is reached.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hitObject">The <see cref="HitObject"/> to test.</param>
|
||||||
|
private static bool hitObjectCanBlockFutureHits(DrawableHitObject hitObject)
|
||||||
|
=> hitObject is DrawableHitCircle;
|
||||||
|
|
||||||
|
private IEnumerable<DrawableHitObject> enumerateHitObjectsUpTo(double targetTime)
|
||||||
|
{
|
||||||
|
foreach (var obj in hitObjectContainer.AliveObjects)
|
||||||
|
{
|
||||||
|
if (obj.HitObject.StartTime >= targetTime)
|
||||||
|
yield break;
|
||||||
|
|
||||||
|
yield return obj;
|
||||||
|
|
||||||
|
foreach (var nestedObj in obj.NestedHitObjects)
|
||||||
|
{
|
||||||
|
if (nestedObj.HitObject.StartTime >= targetTime)
|
||||||
|
break;
|
||||||
|
|
||||||
|
yield return nestedObj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -20,6 +20,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
private readonly ApproachCircleProxyContainer approachCircles;
|
private readonly ApproachCircleProxyContainer approachCircles;
|
||||||
private readonly JudgementContainer<DrawableOsuJudgement> judgementLayer;
|
private readonly JudgementContainer<DrawableOsuJudgement> judgementLayer;
|
||||||
private readonly FollowPointRenderer followPoints;
|
private readonly FollowPointRenderer followPoints;
|
||||||
|
private readonly OrderedHitPolicy hitPolicy;
|
||||||
|
|
||||||
public static readonly Vector2 BASE_SIZE = new Vector2(512, 384);
|
public static readonly Vector2 BASE_SIZE = new Vector2(512, 384);
|
||||||
|
|
||||||
@ -51,6 +52,8 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
Depth = -1,
|
Depth = -1,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
hitPolicy = new OrderedHitPolicy(HitObjectContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Add(DrawableHitObject h)
|
public override void Add(DrawableHitObject h)
|
||||||
@ -64,7 +67,10 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
|
|
||||||
base.Add(h);
|
base.Add(h);
|
||||||
|
|
||||||
followPoints.AddFollowPoints((DrawableOsuHitObject)h);
|
DrawableOsuHitObject osuHitObject = (DrawableOsuHitObject)h;
|
||||||
|
osuHitObject.CheckHittable = hitPolicy.IsHittable;
|
||||||
|
|
||||||
|
followPoints.AddFollowPoints(osuHitObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool Remove(DrawableHitObject h)
|
public override bool Remove(DrawableHitObject h)
|
||||||
@ -79,6 +85,9 @@ 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.
|
||||||
|
hitPolicy.HandleHit(judgedObject);
|
||||||
|
|
||||||
if (!judgedObject.DisplayResult || !DisplayJudgements.Value)
|
if (!judgedObject.DisplayResult || !DisplayJudgements.Value)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 37 KiB |
@ -0,0 +1,5 @@
|
|||||||
|
[General]
|
||||||
|
Name: an old skin
|
||||||
|
Author: an old guy
|
||||||
|
|
||||||
|
// no version specified means v1
|
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 4.6 KiB |
After Width: | Height: | Size: 5.5 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 5.9 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 20 KiB |
21
osu.Game.Rulesets.Taiko.Tests/TaikoSkinnableTestScene.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using osu.Game.Rulesets.Taiko.Skinning;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Tests
|
||||||
|
{
|
||||||
|
public abstract class TaikoSkinnableTestScene : SkinnableTestScene
|
||||||
|
{
|
||||||
|
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||||
|
{
|
||||||
|
typeof(TaikoRuleset),
|
||||||
|
typeof(TaikoLegacySkinTransformer),
|
||||||
|
};
|
||||||
|
|
||||||
|
protected override Ruleset CreateRulesetForSkinProvider() => new TaikoRuleset();
|
||||||
|
}
|
||||||
|
}
|
70
osu.Game.Rulesets.Taiko.Tests/TestSceneDrawableHit.cs
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.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;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Tests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class TestSceneDrawableHit : TaikoSkinnableTestScene
|
||||||
|
{
|
||||||
|
public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[]
|
||||||
|
{
|
||||||
|
typeof(DrawableHit),
|
||||||
|
typeof(DrawableCentreHit),
|
||||||
|
typeof(DrawableRimHit),
|
||||||
|
typeof(LegacyHit),
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
AddStep("Centre hit", () => SetContents(() => new DrawableCentreHit(createHitAtCurrentTime())
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
}));
|
||||||
|
|
||||||
|
AddStep("Centre hit (strong)", () => SetContents(() => new DrawableCentreHit(createHitAtCurrentTime(true))
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
}));
|
||||||
|
|
||||||
|
AddStep("Rim hit", () => SetContents(() => new DrawableRimHit(createHitAtCurrentTime())
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
}));
|
||||||
|
|
||||||
|
AddStep("Rim hit (strong)", () => SetContents(() => new DrawableRimHit(createHitAtCurrentTime(true))
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Hit createHitAtCurrentTime(bool strong = false)
|
||||||
|
{
|
||||||
|
var hit = new Hit
|
||||||
|
{
|
||||||
|
IsStrong = strong,
|
||||||
|
StartTime = Time.Current + 3000,
|
||||||
|
};
|
||||||
|
|
||||||
|
hit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||||
|
|
||||||
|
return hit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,24 +3,26 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osuTK;
|
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.UI;
|
using osu.Game.Rulesets.Taiko.UI;
|
||||||
using osu.Game.Tests.Visual;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Tests
|
namespace osu.Game.Rulesets.Taiko.Tests
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestSceneInputDrum : SkinnableTestScene
|
public class TestSceneInputDrum : TaikoSkinnableTestScene
|
||||||
{
|
{
|
||||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[]
|
||||||
{
|
{
|
||||||
typeof(InputDrum),
|
typeof(InputDrum),
|
||||||
};
|
typeof(LegacyInputDrum),
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
|
@ -1,9 +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.Containers;
|
||||||
using osu.Game.Graphics;
|
|
||||||
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
|
||||||
{
|
{
|
||||||
@ -14,13 +14,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
public DrawableCentreHit(Hit hit)
|
public DrawableCentreHit(Hit hit)
|
||||||
: base(hit)
|
: base(hit)
|
||||||
{
|
{
|
||||||
MainPiece.Add(new CentreHitSymbolPiece());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
protected override CompositeDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.CentreHit),
|
||||||
private void load(OsuColour colours)
|
_ => new CentreHitCirclePiece(), confineMode: ConfineMode.ScaleToFit);
|
||||||
{
|
|
||||||
MainPiece.AccentColour = colours.PinkDarker;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,17 +34,19 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
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;
|
||||||
MainPiece.Add(tickContainer = new Container<DrawableDrumRollTick> { RelativeSizeAxes = Axes.Both });
|
elongatedPiece.Add(tickContainer = new Container<DrawableDrumRollTick> { RelativeSizeAxes = Axes.Both });
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load(OsuColour colours)
|
||||||
{
|
{
|
||||||
MainPiece.AccentColour = colourIdle = colours.YellowDark;
|
elongatedPiece.AccentColour = colourIdle = colours.YellowDark;
|
||||||
colourEngaged = colours.YellowDarker;
|
colourEngaged = colours.YellowDarker;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,7 +86,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
return base.CreateNestedHitObject(hitObject);
|
return base.CreateNestedHitObject(hitObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override TaikoPiece CreateMainPiece() => new ElongatedCirclePiece();
|
protected override CompositeDrawable CreateMainPiece() => elongatedPiece = new ElongatedCirclePiece();
|
||||||
|
|
||||||
public override bool OnPressed(TaikoAction action) => false;
|
public override bool OnPressed(TaikoAction action) => false;
|
||||||
|
|
||||||
@ -101,7 +103,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);
|
Color4 newColour = Interpolation.ValueAt((float)rollingHits / rolling_hits_for_engaged_colour, colourIdle, colourEngaged, 0, 1);
|
||||||
MainPiece.FadeAccent(newColour, 100);
|
(MainPiece as IHasAccentColour)?.FadeAccent(newColour, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
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;
|
||||||
@ -19,7 +20,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
|
|
||||||
public override bool DisplayResult => false;
|
public override bool DisplayResult => false;
|
||||||
|
|
||||||
protected override TaikoPiece CreateMainPiece() => new TickPiece
|
protected override CompositeDrawable CreateMainPiece() => new TickPiece
|
||||||
{
|
{
|
||||||
Filled = HitObject.FirstTick
|
Filled = HitObject.FirstTick
|
||||||
};
|
};
|
||||||
|
@ -1,9 +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.Containers;
|
||||||
using osu.Game.Graphics;
|
|
||||||
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
|
||||||
{
|
{
|
||||||
@ -14,13 +14,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
public DrawableRimHit(Hit hit)
|
public DrawableRimHit(Hit hit)
|
||||||
: base(hit)
|
: base(hit)
|
||||||
{
|
{
|
||||||
MainPiece.Add(new RimHitSymbolPiece());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
protected override CompositeDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.RimHit),
|
||||||
private void load(OsuColour colours)
|
_ => new RimHitCirclePiece(), confineMode: ConfineMode.ScaleToFit);
|
||||||
{
|
|
||||||
MainPiece.AccentColour = colours.BlueDarker;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,11 +9,11 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
|
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
using osu.Framework.Graphics.Shapes;
|
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;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||||
{
|
{
|
||||||
@ -34,8 +34,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
private readonly CircularContainer targetRing;
|
private readonly CircularContainer targetRing;
|
||||||
private readonly CircularContainer expandingRing;
|
private readonly CircularContainer expandingRing;
|
||||||
|
|
||||||
private readonly SwellSymbolPiece symbol;
|
|
||||||
|
|
||||||
public DrawableSwell(Swell swell)
|
public DrawableSwell(Swell swell)
|
||||||
: base(swell)
|
: base(swell)
|
||||||
{
|
{
|
||||||
@ -107,18 +105,22 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
});
|
});
|
||||||
|
|
||||||
AddInternal(ticks = new Container<DrawableSwellTick> { RelativeSizeAxes = Axes.Both });
|
AddInternal(ticks = new Container<DrawableSwellTick> { RelativeSizeAxes = Axes.Both });
|
||||||
|
|
||||||
MainPiece.Add(symbol = new SwellSymbolPiece());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load(OsuColour colours)
|
||||||
{
|
{
|
||||||
MainPiece.AccentColour = colours.YellowDark;
|
|
||||||
expandingRing.Colour = colours.YellowLight;
|
expandingRing.Colour = colours.YellowLight;
|
||||||
targetRing.BorderColour = colours.YellowDark.Opacity(0.25f);
|
targetRing.BorderColour = colours.YellowDark.Opacity(0.25f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override CompositeDrawable CreateMainPiece() => new SwellCirclePiece
|
||||||
|
{
|
||||||
|
// to allow for rotation transform
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
};
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
@ -182,7 +184,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
.Then()
|
.Then()
|
||||||
.FadeTo(completion / 8, 2000, Easing.OutQuint);
|
.FadeTo(completion / 8, 2000, Easing.OutQuint);
|
||||||
|
|
||||||
symbol.RotateTo((float)(completion * HitObject.Duration / 8), 4000, Easing.OutQuint);
|
MainPiece.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,7 +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;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||||
{
|
{
|
||||||
@ -28,5 +30,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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
@ -45,7 +44,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Moves <see cref="Content"/> to a layer proxied above the playfield.
|
/// Moves <see cref="Content"/> to a layer proxied above the playfield.
|
||||||
/// Does nothing is content is already proxied.
|
/// Does nothing if content is already proxied.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected void ProxyContent()
|
protected void ProxyContent()
|
||||||
{
|
{
|
||||||
@ -108,19 +107,19 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract class DrawableTaikoHitObject<TTaikoHit> : DrawableTaikoHitObject
|
public abstract class DrawableTaikoHitObject<TObject> : DrawableTaikoHitObject
|
||||||
where TTaikoHit : TaikoHitObject
|
where TObject : TaikoHitObject
|
||||||
{
|
{
|
||||||
public override Vector2 OriginPosition => new Vector2(DrawHeight / 2);
|
public override Vector2 OriginPosition => new Vector2(DrawHeight / 2);
|
||||||
|
|
||||||
public new TTaikoHit HitObject;
|
public new TObject HitObject;
|
||||||
|
|
||||||
protected readonly Vector2 BaseSize;
|
protected readonly Vector2 BaseSize;
|
||||||
protected readonly TaikoPiece MainPiece;
|
protected readonly CompositeDrawable MainPiece;
|
||||||
|
|
||||||
private readonly Container<DrawableStrongNestedHit> strongHitContainer;
|
private readonly Container<DrawableStrongNestedHit> strongHitContainer;
|
||||||
|
|
||||||
protected DrawableTaikoHitObject(TTaikoHit hitObject)
|
protected DrawableTaikoHitObject(TObject hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
{
|
{
|
||||||
HitObject = hitObject;
|
HitObject = hitObject;
|
||||||
@ -132,7 +131,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
Size = BaseSize = new Vector2(HitObject.IsStrong ? TaikoHitObject.DEFAULT_STRONG_SIZE : TaikoHitObject.DEFAULT_SIZE);
|
Size = BaseSize = new Vector2(HitObject.IsStrong ? TaikoHitObject.DEFAULT_STRONG_SIZE : TaikoHitObject.DEFAULT_SIZE);
|
||||||
|
|
||||||
Content.Add(MainPiece = CreateMainPiece());
|
Content.Add(MainPiece = CreateMainPiece());
|
||||||
MainPiece.KiaiMode = HitObject.Kiai;
|
|
||||||
|
|
||||||
AddInternal(strongHitContainer = new Container<DrawableStrongNestedHit>());
|
AddInternal(strongHitContainer = new Container<DrawableStrongNestedHit>());
|
||||||
}
|
}
|
||||||
@ -169,7 +167,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 virtual TaikoPiece CreateMainPiece() => new CirclePiece();
|
protected abstract CompositeDrawable 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"/>.
|
||||||
|
@ -0,0 +1,52 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osuTK;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
|
||||||
|
{
|
||||||
|
public class CentreHitCirclePiece : CirclePiece
|
||||||
|
{
|
||||||
|
public CentreHitCirclePiece()
|
||||||
|
{
|
||||||
|
Add(new CentreHitSymbolPiece());
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colours)
|
||||||
|
{
|
||||||
|
AccentColour = colours.PinkDarker;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The symbol used for centre hit pieces.
|
||||||
|
/// </summary>
|
||||||
|
public class CentreHitSymbolPiece : Container
|
||||||
|
{
|
||||||
|
public CentreHitSymbolPiece()
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
Size = new Vector2(SYMBOL_SIZE);
|
||||||
|
Padding = new MarginPadding(SYMBOL_BORDER);
|
||||||
|
|
||||||
|
Children = new[]
|
||||||
|
{
|
||||||
|
new CircularContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
Children = new[] { new Box { RelativeSizeAxes = Axes.Both } }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,36 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osuTK;
|
|
||||||
using osu.Framework.Graphics.Shapes;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The symbol used for centre hit pieces.
|
|
||||||
/// </summary>
|
|
||||||
public class CentreHitSymbolPiece : Container
|
|
||||||
{
|
|
||||||
public CentreHitSymbolPiece()
|
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre;
|
|
||||||
Origin = Anchor.Centre;
|
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.Both;
|
|
||||||
Size = new Vector2(CirclePiece.SYMBOL_SIZE);
|
|
||||||
Padding = new MarginPadding(CirclePiece.SYMBOL_BORDER);
|
|
||||||
|
|
||||||
Children = new[]
|
|
||||||
{
|
|
||||||
new CircularContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Masking = true,
|
|
||||||
Children = new[] { new Box { RelativeSizeAxes = Axes.Both } }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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.Containers;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
|
namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
|
||||||
{
|
{
|
||||||
@ -20,21 +21,23 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
|
|||||||
/// for a usage example.
|
/// for a usage example.
|
||||||
/// </para>
|
/// </para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class CirclePiece : TaikoPiece
|
public abstract class CirclePiece : BeatSyncedContainer
|
||||||
{
|
{
|
||||||
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;
|
||||||
private const double pre_beat_transition_time = 80;
|
private const double pre_beat_transition_time = 80;
|
||||||
|
|
||||||
|
private Color4 accentColour;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The colour of the inner circle and outer glows.
|
/// The colour of the inner circle and outer glows.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public override Color4 AccentColour
|
public Color4 AccentColour
|
||||||
{
|
{
|
||||||
get => base.AccentColour;
|
get => accentColour;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
base.AccentColour = value;
|
accentColour = value;
|
||||||
|
|
||||||
background.Colour = AccentColour;
|
background.Colour = AccentColour;
|
||||||
|
|
||||||
@ -42,15 +45,17 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool kiaiMode;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether Kiai mode effects are enabled for this circle piece.
|
/// Whether Kiai mode effects are enabled for this circle piece.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public override bool KiaiMode
|
public bool KiaiMode
|
||||||
{
|
{
|
||||||
get => base.KiaiMode;
|
get => kiaiMode;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
base.KiaiMode = value;
|
kiaiMode = value;
|
||||||
|
|
||||||
resetEdgeEffects();
|
resetEdgeEffects();
|
||||||
}
|
}
|
||||||
@ -64,8 +69,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
|
|||||||
|
|
||||||
public Box FlashBox;
|
public Box FlashBox;
|
||||||
|
|
||||||
public CirclePiece()
|
protected CirclePiece()
|
||||||
{
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
EarlyActivationMilliseconds = pre_beat_transition_time;
|
EarlyActivationMilliseconds = pre_beat_transition_time;
|
||||||
|
|
||||||
AddRangeInternal(new Drawable[]
|
AddRangeInternal(new Drawable[]
|
||||||
|
@ -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.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
|
||||||
|
{
|
||||||
|
public class RimHitCirclePiece : CirclePiece
|
||||||
|
{
|
||||||
|
public RimHitCirclePiece()
|
||||||
|
{
|
||||||
|
Add(new RimHitSymbolPiece());
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colours)
|
||||||
|
{
|
||||||
|
AccentColour = colours.BlueDarker;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The symbol used for rim hit pieces.
|
||||||
|
/// </summary>
|
||||||
|
public class RimHitSymbolPiece : CircularContainer
|
||||||
|
{
|
||||||
|
public RimHitSymbolPiece()
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
Size = new Vector2(SYMBOL_SIZE);
|
||||||
|
|
||||||
|
BorderThickness = SYMBOL_BORDER;
|
||||||
|
BorderColour = Color4.White;
|
||||||
|
Masking = true;
|
||||||
|
Children = new[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Alpha = 0,
|
||||||
|
AlwaysPresent = true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,39 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osuTK;
|
|
||||||
using osuTK.Graphics;
|
|
||||||
using osu.Framework.Graphics.Shapes;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The symbol used for rim hit pieces.
|
|
||||||
/// </summary>
|
|
||||||
public class RimHitSymbolPiece : CircularContainer
|
|
||||||
{
|
|
||||||
public RimHitSymbolPiece()
|
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre;
|
|
||||||
Origin = Anchor.Centre;
|
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.Both;
|
|
||||||
Size = new Vector2(CirclePiece.SYMBOL_SIZE);
|
|
||||||
|
|
||||||
BorderThickness = CirclePiece.SYMBOL_BORDER;
|
|
||||||
BorderColour = Color4.White;
|
|
||||||
Masking = true;
|
|
||||||
Children = new[]
|
|
||||||
{
|
|
||||||
new Box
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Alpha = 0,
|
|
||||||
AlwaysPresent = true
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|