mirror of
https://github.com/ppy/osu.git
synced 2026-05-15 07:02:36 +08:00
Compare commits
433 Commits
2020.508.2
...
2020.523.0
+2
-2
@@ -51,7 +51,7 @@
|
||||
<Reference Include="Java.Interop" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.427.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.508.1" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.512.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.519.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.IO.Stores;
|
||||
using osu.Game.Rulesets.Catch.Skinning;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class CatchSkinColourDecodingTest
|
||||
{
|
||||
[Test]
|
||||
public void TestCatchSkinColourDecoding()
|
||||
{
|
||||
var store = new NamespacedResourceStore<byte[]>(new DllResourceStore(GetType().Assembly), "Resources/special-skin");
|
||||
var rawSkin = new TestLegacySkin(new SkinInfo { Name = "special-skin" }, store);
|
||||
var skin = new CatchLegacySkinTransformer(rawSkin);
|
||||
|
||||
Assert.AreEqual(new Color4(232, 185, 35, 255), skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDash)?.Value);
|
||||
Assert.AreEqual(new Color4(232, 74, 35, 255), skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDashAfterImage)?.Value);
|
||||
Assert.AreEqual(new Color4(0, 255, 255, 255), skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDashFruit)?.Value);
|
||||
}
|
||||
|
||||
private class TestLegacySkin : LegacySkin
|
||||
{
|
||||
public TestLegacySkin(SkinInfo skin, IResourceStore<byte[]> storage)
|
||||
// Bypass LegacySkinResourceStore to avoid returning null for retrieving files due to bad skin info (SkinInfo.Files = null).
|
||||
: base(skin, storage, null, "skin.ini")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,12 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
[CatchTheBeat]
|
||||
HyperDash: 232,185,35
|
||||
HyperDashFruit: 0,255,255
|
||||
HyperDashAfterImage: 232,74,35
|
||||
@@ -1,13 +1,9 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Tests
|
||||
@@ -15,17 +11,6 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
[TestFixture]
|
||||
public class TestSceneBananaShower : PlayerTestScene
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||
{
|
||||
typeof(BananaShower),
|
||||
typeof(Banana),
|
||||
typeof(DrawableBananaShower),
|
||||
typeof(DrawableBanana),
|
||||
|
||||
typeof(CatchRuleset),
|
||||
typeof(DrawableCatchRuleset),
|
||||
};
|
||||
|
||||
public TestSceneBananaShower()
|
||||
: base(new CatchRuleset())
|
||||
{
|
||||
|
||||
@@ -4,26 +4,18 @@
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestSceneCatcher : CatchSkinnableTestScene
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[]
|
||||
{
|
||||
typeof(CatcherArea),
|
||||
typeof(CatcherSprite)
|
||||
}).ToList();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
SetContents(() => new Catcher
|
||||
SetContents(() => new Catcher(new Container())
|
||||
{
|
||||
RelativePositionAxes = Axes.None,
|
||||
Anchor = Anchor.Centre,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
@@ -22,15 +21,6 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
{
|
||||
public class TestSceneDrawableHitObjects : OsuTestScene
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||
{
|
||||
typeof(Catcher),
|
||||
typeof(DrawableCatchRuleset),
|
||||
typeof(DrawableFruit),
|
||||
typeof(DrawableJuiceStream),
|
||||
typeof(DrawableBanana)
|
||||
};
|
||||
|
||||
private DrawableCatchRuleset drawableRuleset;
|
||||
private double playfieldTime => drawableRuleset.Playfield.Time.Current;
|
||||
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Rulesets.Catch.Mods;
|
||||
|
||||
@@ -11,8 +8,6 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
{
|
||||
public class TestSceneDrawableHitObjectsHidden : TestSceneDrawableHitObjects
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(CatchModHidden) }).ToList();
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
{
|
||||
|
||||
@@ -2,13 +2,10 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Tests
|
||||
@@ -16,22 +13,6 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
[TestFixture]
|
||||
public class TestSceneFruitObjects : CatchSkinnableTestScene
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[]
|
||||
{
|
||||
typeof(CatchHitObject),
|
||||
typeof(Fruit),
|
||||
typeof(FruitPiece),
|
||||
typeof(Droplet),
|
||||
typeof(Banana),
|
||||
typeof(BananaShower),
|
||||
typeof(DrawableCatchHitObject),
|
||||
typeof(DrawableFruit),
|
||||
typeof(DrawableDroplet),
|
||||
typeof(DrawableBanana),
|
||||
typeof(DrawableBananaShower),
|
||||
typeof(Pulp),
|
||||
}).ToList();
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// 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.Testing;
|
||||
@@ -18,11 +17,6 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
[TestFixture]
|
||||
public class TestSceneHyperDash : PlayerTestScene
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||
{
|
||||
typeof(CatcherArea),
|
||||
};
|
||||
|
||||
public TestSceneHyperDash()
|
||||
: base(new CatchRuleset())
|
||||
{
|
||||
|
||||
@@ -26,6 +26,48 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
[Resolved]
|
||||
private SkinManager skins { get; set; }
|
||||
|
||||
[Test]
|
||||
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()
|
||||
{
|
||||
@@ -68,6 +110,38 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
checkHyperDashFruitColour(skin, skin.HyperDashColour);
|
||||
}
|
||||
|
||||
private void checkHyperDashCatcherColour(ISkin skin, Color4 expectedCatcherColour, Color4? expectedEndGlowColour = null)
|
||||
{
|
||||
CatcherArea catcherArea = null;
|
||||
CatcherTrailDisplay trails = null;
|
||||
|
||||
AddStep("create hyper-dashing catcher", () =>
|
||||
{
|
||||
Child = setupSkinHierarchy(catcherArea = new CatcherArea
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Scale = new Vector2(4f),
|
||||
}, skin);
|
||||
|
||||
trails = catcherArea.OfType<CatcherTrailDisplay>().Single();
|
||||
catcherArea.MovableCatcher.SetHyperDashState(2);
|
||||
});
|
||||
|
||||
AddUntilStep("catcher colour is correct", () => catcherArea.MovableCatcher.Colour == expectedCatcherColour);
|
||||
|
||||
AddAssert("catcher trails colours are correct", () => trails.HyperDashTrailsColour == expectedCatcherColour);
|
||||
AddAssert("catcher end-glow colours are correct", () => trails.EndGlowSpritesColour == (expectedEndGlowColour ?? expectedCatcherColour));
|
||||
|
||||
AddStep("finish hyper-dashing", () =>
|
||||
{
|
||||
catcherArea.MovableCatcher.SetHyperDashState(1);
|
||||
catcherArea.MovableCatcher.FinishTransforms();
|
||||
});
|
||||
|
||||
AddAssert("catcher colour returned to white", () => catcherArea.MovableCatcher.Colour == Color4.White);
|
||||
}
|
||||
|
||||
private void checkHyperDashFruitColour(ISkin skin, Color4 expectedColour)
|
||||
{
|
||||
DrawableFruit drawableFruit = null;
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Scoring.Legacy;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
{
|
||||
@@ -34,11 +34,11 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
{
|
||||
mods = Score.Mods;
|
||||
|
||||
fruitsHit = Score?.GetCount300() ?? Score.Statistics[HitResult.Perfect];
|
||||
ticksHit = Score?.GetCount100() ?? 0;
|
||||
tinyTicksHit = Score?.GetCount50() ?? 0;
|
||||
tinyTicksMissed = Score?.GetCountKatu() ?? 0;
|
||||
misses = Score.Statistics[HitResult.Miss];
|
||||
fruitsHit = Score.Statistics.GetOrDefault(HitResult.Perfect);
|
||||
ticksHit = Score.Statistics.GetOrDefault(HitResult.LargeTickHit);
|
||||
tinyTicksHit = Score.Statistics.GetOrDefault(HitResult.SmallTickHit);
|
||||
tinyTicksMissed = Score.Statistics.GetOrDefault(HitResult.SmallTickMiss);
|
||||
misses = Score.Statistics.GetOrDefault(HitResult.Miss);
|
||||
|
||||
// Don't count scores made with supposedly unranked mods
|
||||
if (mods.Any(m => !m.Ranked))
|
||||
@@ -52,8 +52,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
|
||||
// Longer maps are worth more
|
||||
double lengthBonus =
|
||||
0.95f + 0.3f * Math.Min(1.0f, numTotalHits / 2500.0f) +
|
||||
(numTotalHits > 2500 ? (float)Math.Log10(numTotalHits / 2500.0f) * 0.475f : 0.0f);
|
||||
0.95 + 0.3 * Math.Min(1.0, numTotalHits / 2500.0) +
|
||||
(numTotalHits > 2500 ? Math.Log10(numTotalHits / 2500.0) * 0.475 : 0.0);
|
||||
|
||||
// Longer maps are worth more
|
||||
value *= lengthBonus;
|
||||
@@ -65,14 +65,14 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
if (Attributes.MaxCombo > 0)
|
||||
value *= Math.Min(Math.Pow(Score.MaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
|
||||
|
||||
float approachRate = (float)Attributes.ApproachRate;
|
||||
float approachRateFactor = 1.0f;
|
||||
if (approachRate > 9.0f)
|
||||
approachRateFactor += 0.1f * (approachRate - 9.0f); // 10% for each AR above 9
|
||||
if (approachRate > 10.0f)
|
||||
approachRateFactor += 0.1f * (approachRate - 10.0f); // Additional 10% at AR 11, 30% total
|
||||
else if (approachRate < 8.0f)
|
||||
approachRateFactor += 0.025f * (8.0f - approachRate); // 2.5% for each AR below 8
|
||||
double approachRate = Attributes.ApproachRate;
|
||||
double approachRateFactor = 1.0;
|
||||
if (approachRate > 9.0)
|
||||
approachRateFactor += 0.1 * (approachRate - 9.0); // 10% for each AR above 9
|
||||
if (approachRate > 10.0)
|
||||
approachRateFactor += 0.1 * (approachRate - 10.0); // Additional 10% at AR 11, 30% total
|
||||
else if (approachRate < 8.0)
|
||||
approachRateFactor += 0.025 * (8.0 - approachRate); // 2.5% for each AR below 8
|
||||
|
||||
value *= approachRateFactor;
|
||||
|
||||
@@ -80,10 +80,10 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
{
|
||||
value *= 1.05 + 0.075 * (10.0 - Math.Min(10.0, Attributes.ApproachRate)); // 7.5% for each AR below 10
|
||||
// Hiddens gives almost nothing on max approach rate, and more the lower it is
|
||||
if (approachRate <= 10.0f)
|
||||
value *= 1.05f + 0.075f * (10.0f - approachRate); // 7.5% for each AR below 10
|
||||
else if (approachRate > 10.0f)
|
||||
value *= 1.01f + 0.04f * (11.0f - Math.Min(11.0f, approachRate)); // 5% at AR 10, 1% at AR 11
|
||||
if (approachRate <= 10.0)
|
||||
value *= 1.05 + 0.075 * (10.0 - approachRate); // 7.5% for each AR below 10
|
||||
else if (approachRate > 10.0)
|
||||
value *= 1.01 + 0.04 * (11.0 - Math.Min(11.0, approachRate)); // 5% at AR 10, 1% at AR 11
|
||||
}
|
||||
|
||||
if (mods.Any(m => m is ModFlashlight))
|
||||
@@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
return value;
|
||||
}
|
||||
|
||||
private float accuracy() => totalHits() == 0 ? 0 : Math.Clamp((float)totalSuccessfulHits() / totalHits(), 0, 1);
|
||||
private double accuracy() => totalHits() == 0 ? 0 : Math.Clamp((double)totalSuccessfulHits() / totalHits(), 0, 1);
|
||||
private int totalHits() => tinyTicksHit + ticksHit + fruitsHit + misses + tinyTicksMissed;
|
||||
private int totalSuccessfulHits() => tinyTicksHit + ticksHit + fruitsHit;
|
||||
private int totalComboHits() => misses + ticksHit + fruitsHit;
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Catch.Skinning
|
||||
{
|
||||
private readonly ISkin source;
|
||||
|
||||
public CatchLegacySkinTransformer(ISkinSource source)
|
||||
public CatchLegacySkinTransformer(ISkin source)
|
||||
{
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
@@ -3,26 +3,37 @@
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Animations;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Catch.Skinning;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.UI
|
||||
{
|
||||
public class Catcher : Container, IKeyBindingHandler<CatchAction>
|
||||
public class Catcher : SkinReloadableDrawable, IKeyBindingHandler<CatchAction>
|
||||
{
|
||||
/// <summary>
|
||||
/// The default colour used to tint hyper-dash fruit, along with the moving catcher, its trail
|
||||
/// and end glow/after-image during a hyper-dash.
|
||||
/// </summary>
|
||||
public static readonly Color4 DEFAULT_HYPER_DASH_COLOUR = Color4.Red;
|
||||
|
||||
/// <summary>
|
||||
/// The duration between transitioning to hyper-dash state.
|
||||
/// </summary>
|
||||
public const double HYPER_DASH_TRANSITION_DURATION = 180;
|
||||
|
||||
/// <summary>
|
||||
/// Whether we are hyper-dashing or not.
|
||||
/// </summary>
|
||||
@@ -35,7 +46,10 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
|
||||
public Container ExplodingFruitTarget;
|
||||
|
||||
public Container AdditiveTarget;
|
||||
[NotNull]
|
||||
private readonly Container trailsTarget;
|
||||
|
||||
private CatcherTrailDisplay trails;
|
||||
|
||||
public CatcherAnimationState CurrentState { get; private set; }
|
||||
|
||||
@@ -44,33 +58,23 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
/// </summary>
|
||||
private const float allowed_catch_range = 0.8f;
|
||||
|
||||
protected bool Dashing
|
||||
/// <summary>
|
||||
/// The drawable catcher for <see cref="CurrentState"/>.
|
||||
/// </summary>
|
||||
internal Drawable CurrentDrawableCatcher => currentCatcher.Drawable;
|
||||
|
||||
private bool dashing;
|
||||
|
||||
public bool Dashing
|
||||
{
|
||||
get => dashing;
|
||||
set
|
||||
protected set
|
||||
{
|
||||
if (value == dashing) return;
|
||||
|
||||
dashing = value;
|
||||
|
||||
Trail |= dashing;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Activate or deactivate the trail. Will be automatically deactivated when conditions to keep the trail displayed are no longer met.
|
||||
/// </summary>
|
||||
protected bool Trail
|
||||
{
|
||||
get => trail;
|
||||
set
|
||||
{
|
||||
if (value == trail || AdditiveTarget == null) return;
|
||||
|
||||
trail = value;
|
||||
|
||||
if (Trail)
|
||||
beginTrail();
|
||||
updateTrailVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,18 +91,19 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
|
||||
private CatcherSprite currentCatcher;
|
||||
|
||||
private Color4 hyperDashColour = DEFAULT_HYPER_DASH_COLOUR;
|
||||
private Color4 hyperDashEndGlowColour = DEFAULT_HYPER_DASH_COLOUR;
|
||||
|
||||
private int currentDirection;
|
||||
|
||||
private bool dashing;
|
||||
|
||||
private bool trail;
|
||||
|
||||
private double hyperDashModifier = 1;
|
||||
private int hyperDashDirection;
|
||||
private float hyperDashTargetPosition;
|
||||
|
||||
public Catcher(BeatmapDifficulty difficulty = null)
|
||||
public Catcher([NotNull] Container trailsTarget, BeatmapDifficulty difficulty = null)
|
||||
{
|
||||
this.trailsTarget = trailsTarget;
|
||||
|
||||
RelativePositionAxes = Axes.X;
|
||||
X = 0.5f;
|
||||
|
||||
@@ -114,7 +119,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Children = new Drawable[]
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
caughtFruit = new Container<DrawableHitObject>
|
||||
{
|
||||
@@ -138,6 +143,8 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
}
|
||||
};
|
||||
|
||||
trailsTarget.Add(trails = new CatcherTrailDisplay(this));
|
||||
|
||||
updateCatcher();
|
||||
}
|
||||
|
||||
@@ -185,7 +192,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
|
||||
caughtFruit.Add(fruit);
|
||||
|
||||
Add(new HitExplosion(fruit)
|
||||
AddInternal(new HitExplosion(fruit)
|
||||
{
|
||||
X = fruit.X,
|
||||
Scale = new Vector2(fruit.HitObject.Scale)
|
||||
@@ -240,8 +247,6 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
/// <param name="targetPosition">When this catcher crosses this position, this catcher ends hyper-dashing.</param>
|
||||
public void SetHyperDashState(double modifier = 1, float targetPosition = -1)
|
||||
{
|
||||
const float hyper_dash_transition_length = 180;
|
||||
|
||||
var wasHyperDashing = HyperDashing;
|
||||
|
||||
if (modifier <= 1 || X == targetPosition)
|
||||
@@ -250,11 +255,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
hyperDashDirection = 0;
|
||||
|
||||
if (wasHyperDashing)
|
||||
{
|
||||
this.FadeColour(Color4.White, hyper_dash_transition_length, Easing.OutQuint);
|
||||
this.FadeTo(1, hyper_dash_transition_length, Easing.OutQuint);
|
||||
Trail &= Dashing;
|
||||
}
|
||||
runHyperDashStateTransition(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -264,20 +265,32 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
|
||||
if (!wasHyperDashing)
|
||||
{
|
||||
this.FadeColour(Color4.OrangeRed, hyper_dash_transition_length, Easing.OutQuint);
|
||||
this.FadeTo(0.2f, hyper_dash_transition_length, Easing.OutQuint);
|
||||
Trail = true;
|
||||
|
||||
var hyperDashEndGlow = createAdditiveSprite();
|
||||
|
||||
hyperDashEndGlow.MoveToOffset(new Vector2(0, -10), 1200, Easing.In);
|
||||
hyperDashEndGlow.ScaleTo(hyperDashEndGlow.Scale * 0.95f).ScaleTo(hyperDashEndGlow.Scale * 1.2f, 1200, Easing.In);
|
||||
hyperDashEndGlow.FadeOut(1200);
|
||||
hyperDashEndGlow.Expire(true);
|
||||
trails.DisplayEndGlow();
|
||||
runHyperDashStateTransition(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void runHyperDashStateTransition(bool hyperDashing)
|
||||
{
|
||||
trails.HyperDashTrailsColour = hyperDashColour;
|
||||
trails.EndGlowSpritesColour = hyperDashEndGlowColour;
|
||||
updateTrailVisibility();
|
||||
|
||||
if (hyperDashing)
|
||||
{
|
||||
this.FadeColour(hyperDashColour, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
|
||||
this.FadeTo(0.2f, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.FadeColour(Color4.White, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
|
||||
this.FadeTo(1f, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateTrailVisibility() => trails.DisplayTrail = Dashing || HyperDashing;
|
||||
|
||||
public bool OnPressed(CatchAction action)
|
||||
{
|
||||
switch (action)
|
||||
@@ -366,6 +379,21 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
});
|
||||
}
|
||||
|
||||
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
|
||||
{
|
||||
base.SkinChanged(skin, allowFallback);
|
||||
|
||||
hyperDashColour =
|
||||
skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDash)?.Value ??
|
||||
DEFAULT_HYPER_DASH_COLOUR;
|
||||
|
||||
hyperDashEndGlowColour =
|
||||
skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDashAfterImage)?.Value ??
|
||||
hyperDashColour;
|
||||
|
||||
runHyperDashStateTransition(HyperDashing);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
@@ -411,22 +439,6 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
(currentCatcher.Drawable as IFramedAnimation)?.GotoFrame(0);
|
||||
}
|
||||
|
||||
private void beginTrail()
|
||||
{
|
||||
if (!dashing && !HyperDashing)
|
||||
{
|
||||
Trail = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var additive = createAdditiveSprite();
|
||||
|
||||
additive.FadeTo(0.4f).FadeOut(800, Easing.OutQuint);
|
||||
additive.Expire(true);
|
||||
|
||||
Scheduler.AddDelayed(beginTrail, HyperDashing ? 25 : 50);
|
||||
}
|
||||
|
||||
private void updateState(CatcherAnimationState state)
|
||||
{
|
||||
if (CurrentState == state)
|
||||
@@ -436,25 +448,6 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
updateCatcher();
|
||||
}
|
||||
|
||||
private CatcherTrailSprite createAdditiveSprite()
|
||||
{
|
||||
var tex = (currentCatcher.Drawable as TextureAnimation)?.CurrentFrame ?? ((Sprite)currentCatcher.Drawable).Texture;
|
||||
|
||||
var sprite = new CatcherTrailSprite(tex)
|
||||
{
|
||||
Anchor = Anchor,
|
||||
Scale = Scale,
|
||||
Colour = HyperDashing ? Color4.Red : Color4.White,
|
||||
Blending = BlendingParameters.Additive,
|
||||
RelativePositionAxes = RelativePositionAxes,
|
||||
Position = Position
|
||||
};
|
||||
|
||||
AdditiveTarget?.Add(sprite);
|
||||
|
||||
return sprite;
|
||||
}
|
||||
|
||||
private void removeFromPlateWithTransform(DrawableHitObject fruit, Action<DrawableHitObject> action)
|
||||
{
|
||||
if (ExplodingFruitTarget != null)
|
||||
|
||||
@@ -33,10 +33,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
Height = CATCHER_SIZE;
|
||||
Child = MovableCatcher = new Catcher(difficulty)
|
||||
{
|
||||
AdditiveTarget = this,
|
||||
};
|
||||
Child = MovableCatcher = new Catcher(this, difficulty);
|
||||
}
|
||||
|
||||
public static float GetCatcherSize(BeatmapDifficulty difficulty)
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
// 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 JetBrains.Annotations;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Animations;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a component responsible for displaying
|
||||
/// the appropriate catcher trails when requested to.
|
||||
/// </summary>
|
||||
public class CatcherTrailDisplay : CompositeDrawable
|
||||
{
|
||||
private readonly Catcher catcher;
|
||||
|
||||
private readonly Container<CatcherTrailSprite> dashTrails;
|
||||
private readonly Container<CatcherTrailSprite> hyperDashTrails;
|
||||
private readonly Container<CatcherTrailSprite> endGlowSprites;
|
||||
|
||||
private Color4 hyperDashTrailsColour;
|
||||
|
||||
public Color4 HyperDashTrailsColour
|
||||
{
|
||||
get => hyperDashTrailsColour;
|
||||
set
|
||||
{
|
||||
if (hyperDashTrailsColour == value)
|
||||
return;
|
||||
|
||||
hyperDashTrailsColour = value;
|
||||
hyperDashTrails.FadeColour(hyperDashTrailsColour, Catcher.HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
|
||||
private Color4 endGlowSpritesColour;
|
||||
|
||||
public Color4 EndGlowSpritesColour
|
||||
{
|
||||
get => endGlowSpritesColour;
|
||||
set
|
||||
{
|
||||
if (endGlowSpritesColour == value)
|
||||
return;
|
||||
|
||||
endGlowSpritesColour = value;
|
||||
endGlowSprites.FadeColour(endGlowSpritesColour, Catcher.HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
|
||||
private bool trail;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to start displaying trails following the catcher.
|
||||
/// </summary>
|
||||
public bool DisplayTrail
|
||||
{
|
||||
get => trail;
|
||||
set
|
||||
{
|
||||
if (trail == value)
|
||||
return;
|
||||
|
||||
trail = value;
|
||||
|
||||
if (trail)
|
||||
displayTrail();
|
||||
}
|
||||
}
|
||||
|
||||
public CatcherTrailDisplay([NotNull] Catcher catcher)
|
||||
{
|
||||
this.catcher = catcher ?? throw new ArgumentNullException(nameof(catcher));
|
||||
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
InternalChildren = new[]
|
||||
{
|
||||
dashTrails = new Container<CatcherTrailSprite> { RelativeSizeAxes = Axes.Both },
|
||||
hyperDashTrails = new Container<CatcherTrailSprite> { RelativeSizeAxes = Axes.Both, Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR },
|
||||
endGlowSprites = new Container<CatcherTrailSprite> { RelativeSizeAxes = Axes.Both, Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR },
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Displays a single end-glow catcher sprite.
|
||||
/// </summary>
|
||||
public void DisplayEndGlow()
|
||||
{
|
||||
var endGlow = createTrailSprite(endGlowSprites);
|
||||
|
||||
endGlow.MoveToOffset(new Vector2(0, -10), 1200, Easing.In);
|
||||
endGlow.ScaleTo(endGlow.Scale * 0.95f).ScaleTo(endGlow.Scale * 1.2f, 1200, Easing.In);
|
||||
endGlow.FadeOut(1200);
|
||||
endGlow.Expire(true);
|
||||
}
|
||||
|
||||
private void displayTrail()
|
||||
{
|
||||
if (!DisplayTrail)
|
||||
return;
|
||||
|
||||
var sprite = createTrailSprite(catcher.HyperDashing ? hyperDashTrails : dashTrails);
|
||||
|
||||
sprite.FadeTo(0.4f).FadeOut(800, Easing.OutQuint);
|
||||
sprite.Expire(true);
|
||||
|
||||
Scheduler.AddDelayed(displayTrail, catcher.HyperDashing ? 25 : 50);
|
||||
}
|
||||
|
||||
private CatcherTrailSprite createTrailSprite(Container<CatcherTrailSprite> target)
|
||||
{
|
||||
var texture = (catcher.CurrentDrawableCatcher as TextureAnimation)?.CurrentFrame ?? ((Sprite)catcher.CurrentDrawableCatcher).Texture;
|
||||
|
||||
var sprite = new CatcherTrailSprite(texture)
|
||||
{
|
||||
Anchor = catcher.Anchor,
|
||||
Scale = catcher.Scale,
|
||||
Blending = BlendingParameters.Additive,
|
||||
RelativePositionAxes = catcher.RelativePositionAxes,
|
||||
Position = catcher.Position
|
||||
};
|
||||
|
||||
target.Add(sprite);
|
||||
|
||||
return sprite;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Mania.Edit;
|
||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
@@ -14,7 +15,6 @@ using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests
|
||||
@@ -43,12 +43,18 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
});
|
||||
}
|
||||
|
||||
protected override SnapResult SnapForBlueprint(PlacementBlueprint blueprint)
|
||||
{
|
||||
var time = column.TimeAtScreenSpacePosition(InputManager.CurrentState.Mouse.Position);
|
||||
var pos = column.ScreenSpacePositionAtTime(time);
|
||||
|
||||
return new ManiaSnapResult(pos, time, column);
|
||||
}
|
||||
|
||||
protected override Container CreateHitObjectContainer() => new ScrollingTestContainer(ScrollingDirection.Down) { RelativeSizeAxes = Axes.Both };
|
||||
|
||||
protected override void AddHitObject(DrawableHitObject hitObject) => column.Add((DrawableManiaHitObject)hitObject);
|
||||
|
||||
public Column ColumnAt(Vector2 screenSpacePosition) => column;
|
||||
|
||||
public int TotalColumns => 1;
|
||||
public ManiaPlayfield Playfield => null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ using osu.Framework.Timing;
|
||||
using osu.Game.Rulesets.Mania.Edit;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests
|
||||
@@ -18,11 +17,9 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
[Cached(Type = typeof(IAdjustableClock))]
|
||||
private readonly IAdjustableClock clock = new StopwatchClock();
|
||||
|
||||
private readonly Column column;
|
||||
|
||||
protected ManiaSelectionBlueprintTestScene()
|
||||
{
|
||||
Add(column = new Column(0)
|
||||
Add(new Column(0)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
@@ -31,8 +28,6 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
});
|
||||
}
|
||||
|
||||
public Column ColumnAt(Vector2 screenSpacePosition) => column;
|
||||
|
||||
public int TotalColumns => 1;
|
||||
public ManiaPlayfield Playfield => null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Rulesets.Mania.Skinning;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Rulesets.UI.Scrolling.Algorithms;
|
||||
using osu.Game.Tests.Visual;
|
||||
@@ -27,13 +24,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||
[Cached(Type = typeof(IScrollingInfo))]
|
||||
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()
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
@@ -15,12 +14,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||
{
|
||||
public class TestSceneDrawableJudgement : ManiaSkinnableTestScene
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||
{
|
||||
typeof(DrawableJudgement),
|
||||
typeof(DrawableManiaJudgement)
|
||||
};
|
||||
|
||||
public TestSceneDrawableJudgement()
|
||||
{
|
||||
foreach (HitResult result in Enum.GetValues(typeof(HitResult)).OfType<HitResult>().Skip(1))
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Skinning;
|
||||
@@ -21,12 +18,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||
[TestFixture]
|
||||
public class TestSceneHitExplosion : ManiaSkinnableTestScene
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||
{
|
||||
typeof(DrawableNote),
|
||||
typeof(DrawableManiaHitObject),
|
||||
};
|
||||
|
||||
public TestSceneHitExplosion()
|
||||
{
|
||||
int runcount = 0;
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Mania.Skinning;
|
||||
using osu.Game.Rulesets.Mania.UI.Components;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
@@ -15,12 +12,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||
{
|
||||
public class TestSceneKeyArea : ManiaSkinnableTestScene
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||
{
|
||||
typeof(DefaultKeyArea),
|
||||
typeof(LegacyKeyArea)
|
||||
};
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
// 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;
|
||||
|
||||
@@ -14,12 +10,6 @@ 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()
|
||||
{
|
||||
|
||||
@@ -1,23 +1,14 @@
|
||||
// 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()
|
||||
{
|
||||
|
||||
@@ -12,7 +12,6 @@ using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Rulesets.Mania.UI.Components;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Tests.Visual;
|
||||
@@ -24,15 +23,6 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
[TestFixture]
|
||||
public class TestSceneColumn : ManiaInputTestScene
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||
{
|
||||
typeof(Column),
|
||||
typeof(ColumnBackground),
|
||||
typeof(ColumnHitObjectArea),
|
||||
typeof(DefaultKeyArea),
|
||||
typeof(DefaultHitTarget)
|
||||
};
|
||||
|
||||
[Cached(typeof(IReadOnlyList<Mod>))]
|
||||
private IReadOnlyList<Mod> mods { get; set; } = Array.Empty<Mod>();
|
||||
|
||||
|
||||
@@ -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.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Edit;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests
|
||||
{
|
||||
[Cached(typeof(IManiaHitObjectComposer))]
|
||||
public class TestSceneManiaBeatSnapGrid : EditorClockTestScene, IManiaHitObjectComposer
|
||||
{
|
||||
[Cached(typeof(IScrollingInfo))]
|
||||
private ScrollingTestContainer.TestScrollingInfo scrollingInfo = new ScrollingTestContainer.TestScrollingInfo();
|
||||
|
||||
[Cached(typeof(EditorBeatmap))]
|
||||
private EditorBeatmap editorBeatmap = new EditorBeatmap(new ManiaBeatmap(new StageDefinition()));
|
||||
|
||||
private readonly ManiaBeatSnapGrid beatSnapGrid;
|
||||
|
||||
public TestSceneManiaBeatSnapGrid()
|
||||
{
|
||||
editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 200 });
|
||||
editorBeatmap.ControlPointInfo.Add(10000, new TimingControlPoint { BeatLength = 200 });
|
||||
|
||||
BeatDivisor.Value = 3;
|
||||
|
||||
// Some sane defaults
|
||||
scrollingInfo.Algorithm.Algorithm = ScrollVisualisationMethod.Constant;
|
||||
scrollingInfo.Direction.Value = ScrollingDirection.Up;
|
||||
scrollingInfo.TimeRange.Value = 1000;
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
Playfield = new ManiaPlayfield(new List<StageDefinition>
|
||||
{
|
||||
new StageDefinition { Columns = 4 },
|
||||
new StageDefinition { Columns = 3 }
|
||||
})
|
||||
{
|
||||
Clock = new FramedClock(new StopwatchClock())
|
||||
},
|
||||
beatSnapGrid = new ManiaBeatSnapGrid()
|
||||
};
|
||||
}
|
||||
|
||||
protected override bool OnMouseMove(MouseMoveEvent e)
|
||||
{
|
||||
// We're providing a constant scroll algorithm.
|
||||
float relativePosition = Playfield.Stages[0].HitObjectContainer.ToLocalSpace(e.ScreenSpaceMousePosition).Y / Playfield.Stages[0].HitObjectContainer.DrawHeight;
|
||||
double timeValue = scrollingInfo.TimeRange.Value * relativePosition;
|
||||
|
||||
beatSnapGrid.SelectionTimeRange = (timeValue, timeValue);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public ManiaPlayfield Playfield { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
@@ -10,10 +8,13 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Edit;
|
||||
using osu.Game.Rulesets.Mania.Edit.Blueprints;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
@@ -26,11 +27,6 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
{
|
||||
public class TestSceneManiaHitObjectComposer : EditorClockTestScene
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||
{
|
||||
typeof(ManiaBlueprintContainer)
|
||||
};
|
||||
|
||||
private TestComposer composer;
|
||||
|
||||
[SetUp]
|
||||
@@ -46,11 +42,15 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
public void TestDragOffscreenSelectionVerticallyUpScroll()
|
||||
{
|
||||
DrawableHitObject lastObject = null;
|
||||
double originalTime = 0;
|
||||
Vector2 originalPosition = Vector2.Zero;
|
||||
|
||||
setScrollStep(ScrollingDirection.Up);
|
||||
|
||||
AddStep("seek to last object", () =>
|
||||
{
|
||||
lastObject = this.ChildrenOfType<DrawableHitObject>().Single(d => d.HitObject == composer.EditorBeatmap.HitObjects.Last());
|
||||
originalTime = lastObject.HitObject.StartTime;
|
||||
Clock.Seek(composer.EditorBeatmap.HitObjects.Last().StartTime);
|
||||
});
|
||||
|
||||
@@ -66,26 +66,28 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
|
||||
AddStep("move mouse downwards", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(lastObject, new Vector2(0, 20));
|
||||
InputManager.MoveMouseTo(lastObject, new Vector2(0, lastObject.ScreenSpaceDrawQuad.Height * 4));
|
||||
InputManager.ReleaseButton(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddAssert("hitobjects not moved columns", () => composer.EditorBeatmap.HitObjects.All(h => ((ManiaHitObject)h).Column == 0));
|
||||
AddAssert("hitobjects moved downwards", () => lastObject.DrawPosition.Y - originalPosition.Y > 0);
|
||||
AddAssert("hitobjects not moved too far", () => lastObject.DrawPosition.Y - originalPosition.Y < 50);
|
||||
AddAssert("hitobject has moved time", () => lastObject.HitObject.StartTime == originalTime + 125);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDragOffscreenSelectionVerticallyDownScroll()
|
||||
{
|
||||
DrawableHitObject lastObject = null;
|
||||
double originalTime = 0;
|
||||
Vector2 originalPosition = Vector2.Zero;
|
||||
|
||||
AddStep("set down scroll", () => ((Bindable<ScrollingDirection>)composer.Composer.ScrollingInfo.Direction).Value = ScrollingDirection.Down);
|
||||
setScrollStep(ScrollingDirection.Down);
|
||||
|
||||
AddStep("seek to last object", () =>
|
||||
{
|
||||
lastObject = this.ChildrenOfType<DrawableHitObject>().Single(d => d.HitObject == composer.EditorBeatmap.HitObjects.Last());
|
||||
originalTime = lastObject.HitObject.StartTime;
|
||||
Clock.Seek(composer.EditorBeatmap.HitObjects.Last().StartTime);
|
||||
});
|
||||
|
||||
@@ -101,13 +103,13 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
|
||||
AddStep("move mouse upwards", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(lastObject, new Vector2(0, -20));
|
||||
InputManager.MoveMouseTo(lastObject, new Vector2(0, -lastObject.ScreenSpaceDrawQuad.Height * 4));
|
||||
InputManager.ReleaseButton(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddAssert("hitobjects not moved columns", () => composer.EditorBeatmap.HitObjects.All(h => ((ManiaHitObject)h).Column == 0));
|
||||
AddAssert("hitobjects moved upwards", () => originalPosition.Y - lastObject.DrawPosition.Y > 0);
|
||||
AddAssert("hitobjects not moved too far", () => originalPosition.Y - lastObject.DrawPosition.Y < 50);
|
||||
AddAssert("hitobject has moved time", () => lastObject.HitObject.StartTime == originalTime + 125);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -116,6 +118,8 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
DrawableHitObject lastObject = null;
|
||||
Vector2 originalPosition = Vector2.Zero;
|
||||
|
||||
setScrollStep(ScrollingDirection.Down);
|
||||
|
||||
AddStep("seek to last object", () =>
|
||||
{
|
||||
lastObject = this.ChildrenOfType<DrawableHitObject>().Single(d => d.HitObject == composer.EditorBeatmap.HitObjects.Last());
|
||||
@@ -147,6 +151,46 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
AddAssert("hitobjects not moved vertically", () => lastObject.DrawPosition.Y - originalPosition.Y <= DefaultNotePiece.NOTE_HEIGHT);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDragHoldNoteSelectionVertically()
|
||||
{
|
||||
setScrollStep(ScrollingDirection.Down);
|
||||
|
||||
AddStep("setup beatmap", () =>
|
||||
{
|
||||
composer.EditorBeatmap.Clear();
|
||||
composer.EditorBeatmap.Add(new HoldNote
|
||||
{
|
||||
Column = 1,
|
||||
EndTime = 200
|
||||
});
|
||||
});
|
||||
|
||||
DrawableHoldNote holdNote = null;
|
||||
|
||||
AddStep("grab hold note", () =>
|
||||
{
|
||||
holdNote = this.ChildrenOfType<DrawableHoldNote>().Single();
|
||||
InputManager.MoveMouseTo(holdNote);
|
||||
InputManager.PressButton(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddStep("move drag upwards", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(holdNote, new Vector2(0, -100));
|
||||
InputManager.ReleaseButton(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddAssert("head note positioned correctly", () => Precision.AlmostEquals(holdNote.ScreenSpaceDrawQuad.BottomLeft, holdNote.Head.ScreenSpaceDrawQuad.BottomLeft));
|
||||
AddAssert("tail note positioned correctly", () => Precision.AlmostEquals(holdNote.ScreenSpaceDrawQuad.TopLeft, holdNote.Tail.ScreenSpaceDrawQuad.BottomLeft));
|
||||
|
||||
AddAssert("head blueprint positioned correctly", () => this.ChildrenOfType<HoldNoteNoteSelectionBlueprint>().ElementAt(0).DrawPosition == holdNote.Head.DrawPosition);
|
||||
AddAssert("tail blueprint positioned correctly", () => this.ChildrenOfType<HoldNoteNoteSelectionBlueprint>().ElementAt(1).DrawPosition == holdNote.Tail.DrawPosition);
|
||||
}
|
||||
|
||||
private void setScrollStep(ScrollingDirection direction)
|
||||
=> AddStep($"set scroll direction = {direction}", () => ((Bindable<ScrollingDirection>)composer.Composer.ScrollingInfo.Direction).Value = direction);
|
||||
|
||||
private class TestComposer : CompositeDrawable
|
||||
{
|
||||
[Cached(typeof(EditorBeatmap))]
|
||||
@@ -167,7 +211,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
};
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
EditorBeatmap.Add(new Note { StartTime = 100 * i });
|
||||
EditorBeatmap.Add(new Note { StartTime = 125 * i });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
@@ -30,12 +28,6 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
[TestFixture]
|
||||
public class TestSceneNotes : OsuTestScene
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||
{
|
||||
typeof(DrawableNote),
|
||||
typeof(DrawableHoldNote)
|
||||
};
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@@ -37,12 +38,12 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
||||
{
|
||||
mods = Score.Mods;
|
||||
scaledScore = Score.TotalScore;
|
||||
countPerfect = Score.Statistics[HitResult.Perfect];
|
||||
countGreat = Score.Statistics[HitResult.Great];
|
||||
countGood = Score.Statistics[HitResult.Good];
|
||||
countOk = Score.Statistics[HitResult.Ok];
|
||||
countMeh = Score.Statistics[HitResult.Meh];
|
||||
countMiss = Score.Statistics[HitResult.Miss];
|
||||
countPerfect = Score.Statistics.GetOrDefault(HitResult.Perfect);
|
||||
countGreat = Score.Statistics.GetOrDefault(HitResult.Great);
|
||||
countGood = Score.Statistics.GetOrDefault(HitResult.Good);
|
||||
countOk = Score.Statistics.GetOrDefault(HitResult.Ok);
|
||||
countMeh = Score.Statistics.GetOrDefault(HitResult.Meh);
|
||||
countMiss = Score.Statistics.GetOrDefault(HitResult.Miss);
|
||||
|
||||
if (mods.Any(m => !m.Ranked))
|
||||
return 0;
|
||||
|
||||
@@ -2,11 +2,14 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||
{
|
||||
@@ -16,6 +19,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||
private readonly EditNotePiece headPiece;
|
||||
private readonly EditNotePiece tailPiece;
|
||||
|
||||
[Resolved]
|
||||
private IManiaHitObjectComposer composer { get; set; }
|
||||
|
||||
public HoldNotePlacementBlueprint()
|
||||
: base(new HoldNote())
|
||||
{
|
||||
@@ -35,8 +41,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||
|
||||
if (Column != null)
|
||||
{
|
||||
headPiece.Y = PositionAt(HitObject.StartTime);
|
||||
tailPiece.Y = PositionAt(HitObject.EndTime);
|
||||
headPiece.Y = Parent.ToLocalSpace(Column.ScreenSpacePositionAtTime(HitObject.StartTime)).Y;
|
||||
tailPiece.Y = Parent.ToLocalSpace(Column.ScreenSpacePositionAtTime(HitObject.EndTime)).Y;
|
||||
}
|
||||
|
||||
var topPosition = new Vector2(headPiece.DrawPosition.X, Math.Min(headPiece.DrawPosition.Y, tailPiece.DrawPosition.Y));
|
||||
@@ -49,29 +55,37 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||
|
||||
protected override void OnMouseUp(MouseUpEvent e)
|
||||
{
|
||||
if (e.Button != MouseButton.Left)
|
||||
return;
|
||||
|
||||
base.OnMouseUp(e);
|
||||
EndPlacement(true);
|
||||
}
|
||||
|
||||
private double originalStartTime;
|
||||
|
||||
public override void UpdatePosition(Vector2 screenSpacePosition)
|
||||
public override void UpdatePosition(SnapResult result)
|
||||
{
|
||||
base.UpdatePosition(screenSpacePosition);
|
||||
base.UpdatePosition(result);
|
||||
|
||||
if (PlacementActive)
|
||||
{
|
||||
var endTime = TimeAt(screenSpacePosition);
|
||||
|
||||
HitObject.StartTime = endTime < originalStartTime ? endTime : originalStartTime;
|
||||
HitObject.Duration = Math.Abs(endTime - originalStartTime);
|
||||
if (result.Time is double endTime)
|
||||
{
|
||||
HitObject.StartTime = endTime < originalStartTime ? endTime : originalStartTime;
|
||||
HitObject.Duration = Math.Abs(endTime - originalStartTime);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
headPiece.Width = tailPiece.Width = SnappedWidth;
|
||||
headPiece.X = tailPiece.X = SnappedMousePosition.X;
|
||||
if (result is ManiaSnapResult maniaResult)
|
||||
{
|
||||
headPiece.Width = tailPiece.Width = maniaResult.Column.DrawWidth;
|
||||
headPiece.X = tailPiece.X = ToLocalSpace(result.ScreenSpacePosition).X;
|
||||
}
|
||||
|
||||
originalStartTime = HitObject.StartTime = TimeAt(screenSpacePosition);
|
||||
if (result.Time is double startTime)
|
||||
originalStartTime = HitObject.StartTime = startTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,6 +77,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||
|
||||
public override Quad SelectionQuad => ScreenSpaceDrawQuad;
|
||||
|
||||
public override Vector2 SelectionPoint => DrawableObject.Head.ScreenSpaceDrawQuad.Centre;
|
||||
public override Vector2 ScreenSpaceSelectionPoint => DrawableObject.Head.ScreenSpaceDrawQuad.Centre;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,42 +1,34 @@
|
||||
// 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.Input;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||
{
|
||||
public abstract class ManiaPlacementBlueprint<T> : PlacementBlueprint,
|
||||
IRequireHighFrequencyMousePosition // the playfield could be moving behind us
|
||||
public abstract class ManiaPlacementBlueprint<T> : PlacementBlueprint
|
||||
where T : ManiaHitObject
|
||||
{
|
||||
protected new T HitObject => (T)base.HitObject;
|
||||
|
||||
protected Column Column;
|
||||
private Column column;
|
||||
|
||||
/// <summary>
|
||||
/// The current mouse position, snapped to the closest column.
|
||||
/// </summary>
|
||||
protected Vector2 SnappedMousePosition { get; private set; }
|
||||
public Column Column
|
||||
{
|
||||
get => column;
|
||||
set
|
||||
{
|
||||
if (value == column)
|
||||
return;
|
||||
|
||||
/// <summary>
|
||||
/// The width of the closest column to the current mouse position.
|
||||
/// </summary>
|
||||
protected float SnappedWidth { get; private set; }
|
||||
|
||||
[Resolved]
|
||||
private IManiaHitObjectComposer composer { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private IScrollingInfo scrollingInfo { get; set; }
|
||||
column = value;
|
||||
HitObject.Column = column.Index;
|
||||
}
|
||||
}
|
||||
|
||||
protected ManiaPlacementBlueprint(T hitObject)
|
||||
: base(hitObject)
|
||||
@@ -46,106 +38,22 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||
|
||||
protected override bool OnMouseDown(MouseDownEvent e)
|
||||
{
|
||||
if (Column == null)
|
||||
return base.OnMouseDown(e);
|
||||
if (e.Button != MouseButton.Left)
|
||||
return false;
|
||||
|
||||
HitObject.Column = Column.Index;
|
||||
BeginPlacement(TimeAt(e.ScreenSpaceMousePosition), true);
|
||||
if (Column == null)
|
||||
return false;
|
||||
|
||||
BeginPlacement(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void UpdatePosition(Vector2 screenSpacePosition)
|
||||
public override void UpdatePosition(SnapResult result)
|
||||
{
|
||||
base.UpdatePosition(result);
|
||||
|
||||
if (!PlacementActive)
|
||||
Column = ColumnAt(screenSpacePosition);
|
||||
|
||||
if (Column == null) return;
|
||||
|
||||
SnappedWidth = Column.DrawWidth;
|
||||
|
||||
// Snap to the column
|
||||
var parentPos = Parent.ToLocalSpace(Column.ToScreenSpace(new Vector2(Column.DrawWidth / 2, 0)));
|
||||
SnappedMousePosition = new Vector2(parentPos.X, Parent.ToLocalSpace(screenSpacePosition).Y);
|
||||
}
|
||||
|
||||
protected double TimeAt(Vector2 screenSpacePosition)
|
||||
{
|
||||
if (Column == null)
|
||||
return 0;
|
||||
|
||||
var hitObjectContainer = Column.HitObjectContainer;
|
||||
|
||||
// If we're scrolling downwards, a position of 0 is actually further away from the hit target
|
||||
// so we need to flip the vertical coordinate in the hitobject container's space
|
||||
var hitObjectPos = mouseToHitObjectPosition(Column.HitObjectContainer.ToLocalSpace(screenSpacePosition)).Y;
|
||||
if (scrollingInfo.Direction.Value == ScrollingDirection.Down)
|
||||
hitObjectPos = hitObjectContainer.DrawHeight - hitObjectPos;
|
||||
|
||||
return scrollingInfo.Algorithm.TimeAt(hitObjectPos,
|
||||
EditorClock.CurrentTime,
|
||||
scrollingInfo.TimeRange.Value,
|
||||
hitObjectContainer.DrawHeight);
|
||||
}
|
||||
|
||||
protected float PositionAt(double time)
|
||||
{
|
||||
var pos = scrollingInfo.Algorithm.PositionAt(time,
|
||||
EditorClock.CurrentTime,
|
||||
scrollingInfo.TimeRange.Value,
|
||||
Column.HitObjectContainer.DrawHeight);
|
||||
|
||||
if (scrollingInfo.Direction.Value == ScrollingDirection.Down)
|
||||
pos = Column.HitObjectContainer.DrawHeight - pos;
|
||||
|
||||
return hitObjectToMousePosition(Column.HitObjectContainer.ToSpaceOfOtherDrawable(new Vector2(0, pos), Parent)).Y;
|
||||
}
|
||||
|
||||
protected Column ColumnAt(Vector2 screenSpacePosition)
|
||||
=> composer.ColumnAt(screenSpacePosition);
|
||||
|
||||
/// <summary>
|
||||
/// Converts a mouse position to a hitobject position.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Blueprints are centred on the mouse position, such that the hitobject position is anchored at the top or bottom of the blueprint depending on the scroll direction.
|
||||
/// </remarks>
|
||||
/// <param name="mousePosition">The mouse position.</param>
|
||||
/// <returns>The resulting hitobject position, acnhored at the top or bottom of the blueprint depending on the scroll direction.</returns>
|
||||
private Vector2 mouseToHitObjectPosition(Vector2 mousePosition)
|
||||
{
|
||||
switch (scrollingInfo.Direction.Value)
|
||||
{
|
||||
case ScrollingDirection.Up:
|
||||
mousePosition.Y -= DefaultNotePiece.NOTE_HEIGHT / 2;
|
||||
break;
|
||||
|
||||
case ScrollingDirection.Down:
|
||||
mousePosition.Y += DefaultNotePiece.NOTE_HEIGHT / 2;
|
||||
break;
|
||||
}
|
||||
|
||||
return mousePosition;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a hitobject position to a mouse position.
|
||||
/// </summary>
|
||||
/// <param name="hitObjectPosition">The hitobject position.</param>
|
||||
/// <returns>The resulting mouse position, anchored at the centre of the hitobject.</returns>
|
||||
private Vector2 hitObjectToMousePosition(Vector2 hitObjectPosition)
|
||||
{
|
||||
switch (scrollingInfo.Direction.Value)
|
||||
{
|
||||
case ScrollingDirection.Up:
|
||||
hitObjectPosition.Y += DefaultNotePiece.NOTE_HEIGHT / 2;
|
||||
break;
|
||||
|
||||
case ScrollingDirection.Down:
|
||||
hitObjectPosition.Y -= DefaultNotePiece.NOTE_HEIGHT / 2;
|
||||
break;
|
||||
}
|
||||
|
||||
return hitObjectPosition;
|
||||
Column = (result as ManiaSnapResult)?.Column;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,33 +3,41 @@
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||
{
|
||||
public class NotePlacementBlueprint : ManiaPlacementBlueprint<Note>
|
||||
{
|
||||
private readonly EditNotePiece piece;
|
||||
|
||||
public NotePlacementBlueprint()
|
||||
: base(new Note())
|
||||
{
|
||||
Origin = Anchor.Centre;
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
AutoSizeAxes = Axes.Y;
|
||||
|
||||
InternalChild = new EditNotePiece { RelativeSizeAxes = Axes.X };
|
||||
InternalChild = piece = new EditNotePiece { Origin = Anchor.Centre };
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
public override void UpdatePosition(SnapResult result)
|
||||
{
|
||||
base.Update();
|
||||
base.UpdatePosition(result);
|
||||
|
||||
Width = SnappedWidth;
|
||||
Position = SnappedMousePosition;
|
||||
if (result is ManiaSnapResult maniaResult)
|
||||
{
|
||||
piece.Width = maniaResult.Column.DrawWidth;
|
||||
piece.Position = ToLocalSpace(result.ScreenSpacePosition);
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool OnMouseDown(MouseDownEvent e)
|
||||
{
|
||||
if (e.Button != MouseButton.Left)
|
||||
return false;
|
||||
|
||||
base.OnMouseDown(e);
|
||||
|
||||
// Place the note immediately.
|
||||
|
||||
@@ -2,14 +2,11 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Edit
|
||||
{
|
||||
public interface IManiaHitObjectComposer
|
||||
{
|
||||
Column ColumnAt(Vector2 screenSpacePosition);
|
||||
|
||||
int TotalColumns { get; }
|
||||
ManiaPlayfield Playfield { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
// 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.Bindables;
|
||||
using osu.Framework.Caching;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Edit
|
||||
{
|
||||
/// <summary>
|
||||
/// A grid which displays coloured beat divisor lines in proximity to the selection or placement cursor.
|
||||
/// </summary>
|
||||
public class ManiaBeatSnapGrid : Component
|
||||
{
|
||||
private const double visible_range = 750;
|
||||
|
||||
/// <summary>
|
||||
/// The range of time values of the current selection.
|
||||
/// </summary>
|
||||
public (double start, double end)? SelectionTimeRange
|
||||
{
|
||||
set
|
||||
{
|
||||
if (value == selectionTimeRange)
|
||||
return;
|
||||
|
||||
selectionTimeRange = value;
|
||||
lineCache.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
[Resolved]
|
||||
private EditorBeatmap beatmap { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private IScrollingInfo scrollingInfo { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private Bindable<WorkingBeatmap> working { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private BindableBeatDivisor beatDivisor { get; set; }
|
||||
|
||||
private readonly List<ScrollingHitObjectContainer> grids = new List<ScrollingHitObjectContainer>();
|
||||
|
||||
private readonly Cached lineCache = new Cached();
|
||||
|
||||
private (double start, double end)? selectionTimeRange;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(IManiaHitObjectComposer composer)
|
||||
{
|
||||
foreach (var stage in composer.Playfield.Stages)
|
||||
{
|
||||
foreach (var column in stage.Columns)
|
||||
{
|
||||
var lineContainer = new ScrollingHitObjectContainer();
|
||||
|
||||
grids.Add(lineContainer);
|
||||
column.UnderlayElements.Add(lineContainer);
|
||||
}
|
||||
}
|
||||
|
||||
beatDivisor.BindValueChanged(_ => createLines(), true);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (!lineCache.IsValid)
|
||||
{
|
||||
lineCache.Validate();
|
||||
createLines();
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Stack<DrawableGridLine> availableLines = new Stack<DrawableGridLine>();
|
||||
|
||||
private void createLines()
|
||||
{
|
||||
foreach (var grid in grids)
|
||||
{
|
||||
foreach (var line in grid.Objects.OfType<DrawableGridLine>())
|
||||
availableLines.Push(line);
|
||||
|
||||
grid.Clear(false);
|
||||
}
|
||||
|
||||
if (selectionTimeRange == null)
|
||||
return;
|
||||
|
||||
var range = selectionTimeRange.Value;
|
||||
|
||||
var timingPoint = beatmap.ControlPointInfo.TimingPointAt(range.start - visible_range);
|
||||
|
||||
double time = timingPoint.Time;
|
||||
int beat = 0;
|
||||
|
||||
// progress time until in the visible range.
|
||||
while (time < range.start - visible_range)
|
||||
{
|
||||
time += timingPoint.BeatLength / beatDivisor.Value;
|
||||
beat++;
|
||||
}
|
||||
|
||||
while (time < range.end + visible_range)
|
||||
{
|
||||
var nextTimingPoint = beatmap.ControlPointInfo.TimingPointAt(time);
|
||||
|
||||
// switch to the next timing point if we have reached it.
|
||||
if (nextTimingPoint.Time > timingPoint.Time)
|
||||
{
|
||||
beat = 0;
|
||||
time = nextTimingPoint.Time;
|
||||
timingPoint = nextTimingPoint;
|
||||
}
|
||||
|
||||
Color4 colour = BindableBeatDivisor.GetColourFor(
|
||||
BindableBeatDivisor.GetDivisorForBeatIndex(Math.Max(1, beat), beatDivisor.Value), colours);
|
||||
|
||||
foreach (var grid in grids)
|
||||
{
|
||||
if (!availableLines.TryPop(out var line))
|
||||
line = new DrawableGridLine();
|
||||
|
||||
line.HitObject.StartTime = time;
|
||||
line.Colour = colour;
|
||||
|
||||
grid.Add(line);
|
||||
}
|
||||
|
||||
beat++;
|
||||
time += timingPoint.BeatLength / beatDivisor.Value;
|
||||
}
|
||||
|
||||
foreach (var grid in grids)
|
||||
{
|
||||
// required to update ScrollingHitObjectContainer's cache.
|
||||
grid.UpdateSubTree();
|
||||
|
||||
foreach (var line in grid.Objects.OfType<DrawableGridLine>())
|
||||
{
|
||||
time = line.HitObject.StartTime;
|
||||
|
||||
if (time >= range.start && time <= range.end)
|
||||
line.Alpha = 1;
|
||||
else
|
||||
{
|
||||
double timeSeparation = time < range.start ? range.start - time : time - range.end;
|
||||
line.Alpha = (float)Math.Max(0, 1 - timeSeparation / visible_range);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class DrawableGridLine : DrawableHitObject
|
||||
{
|
||||
[Resolved]
|
||||
private IScrollingInfo scrollingInfo { get; set; }
|
||||
|
||||
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
||||
|
||||
public DrawableGridLine()
|
||||
: base(new HitObject())
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
Height = 2;
|
||||
|
||||
AddInternal(new Box { RelativeSizeAxes = Axes.Both });
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
direction.BindTo(scrollingInfo.Direction);
|
||||
direction.BindValueChanged(onDirectionChanged, true);
|
||||
}
|
||||
|
||||
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
|
||||
{
|
||||
Origin = Anchor = direction.NewValue == ScrollingDirection.Up
|
||||
? Anchor.TopLeft
|
||||
: Anchor.BottomLeft;
|
||||
}
|
||||
|
||||
protected override void UpdateInitialTransforms()
|
||||
{
|
||||
// don't perform any fading – we are handling that ourselves.
|
||||
}
|
||||
|
||||
protected override void UpdateStateTransforms(ArmedState state)
|
||||
{
|
||||
LifetimeEnd = HitObject.StartTime + visible_range;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,12 @@ using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Edit.Tools;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Input;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Screens.Edit.Compose.Components;
|
||||
@@ -20,18 +23,26 @@ namespace osu.Game.Rulesets.Mania.Edit
|
||||
public class ManiaHitObjectComposer : HitObjectComposer<ManiaHitObject>, IManiaHitObjectComposer
|
||||
{
|
||||
private DrawableManiaEditRuleset drawableRuleset;
|
||||
private ManiaBeatSnapGrid beatSnapGrid;
|
||||
private InputManager inputManager;
|
||||
|
||||
public ManiaHitObjectComposer(Ruleset ruleset)
|
||||
: base(ruleset)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the column that intersects a screen-space position.
|
||||
/// </summary>
|
||||
/// <param name="screenSpacePosition">The screen-space position.</param>
|
||||
/// <returns>The column which intersects with <paramref name="screenSpacePosition"/>.</returns>
|
||||
public Column ColumnAt(Vector2 screenSpacePosition) => drawableRuleset.GetColumnByPosition(screenSpacePosition);
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
AddInternal(beatSnapGrid = new ManiaBeatSnapGrid());
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
inputManager = GetContainingInputManager();
|
||||
}
|
||||
|
||||
private DependencyContainer dependencies;
|
||||
|
||||
@@ -42,28 +53,22 @@ namespace osu.Game.Rulesets.Mania.Edit
|
||||
|
||||
public IScrollingInfo ScrollingInfo => drawableRuleset.ScrollingInfo;
|
||||
|
||||
public int TotalColumns => Playfield.TotalColumns;
|
||||
|
||||
public override (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time)
|
||||
public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition)
|
||||
{
|
||||
var hoc = Playfield.GetColumn(0).HitObjectContainer;
|
||||
var column = Playfield.GetColumnByPosition(screenSpacePosition);
|
||||
|
||||
float targetPosition = hoc.ToLocalSpace(ToScreenSpace(position)).Y;
|
||||
if (column == null)
|
||||
return new SnapResult(screenSpacePosition, null);
|
||||
|
||||
if (drawableRuleset.ScrollingInfo.Direction.Value == ScrollingDirection.Down)
|
||||
{
|
||||
// We're dealing with screen coordinates in which the position decreases towards the centre of the screen resulting in an increase in start time.
|
||||
// The scrolling algorithm instead assumes a top anchor meaning an increase in time corresponds to an increase in position,
|
||||
// so when scrolling downwards the coordinates need to be flipped.
|
||||
targetPosition = hoc.DrawHeight - targetPosition;
|
||||
}
|
||||
double targetTime = column.TimeAtScreenSpacePosition(screenSpacePosition);
|
||||
|
||||
double targetTime = drawableRuleset.ScrollingInfo.Algorithm.TimeAt(targetPosition,
|
||||
EditorClock.CurrentTime,
|
||||
drawableRuleset.ScrollingInfo.TimeRange.Value,
|
||||
hoc.DrawHeight);
|
||||
// apply beat snapping
|
||||
targetTime = BeatSnapProvider.SnapTime(targetTime);
|
||||
|
||||
return base.GetSnappedPosition(position, targetTime);
|
||||
// convert back to screen space
|
||||
screenSpacePosition = column.ScreenSpacePositionAtTime(targetTime);
|
||||
|
||||
return new ManiaSnapResult(screenSpacePosition, targetTime, column);
|
||||
}
|
||||
|
||||
protected override DrawableRuleset<ManiaHitObject> CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods = null)
|
||||
@@ -83,5 +88,28 @@ namespace osu.Game.Rulesets.Mania.Edit
|
||||
new NoteCompositionTool(),
|
||||
new HoldNoteCompositionTool()
|
||||
};
|
||||
|
||||
protected override void UpdateAfterChildren()
|
||||
{
|
||||
base.UpdateAfterChildren();
|
||||
|
||||
if (BlueprintContainer.CurrentTool is SelectTool)
|
||||
{
|
||||
if (EditorBeatmap.SelectedHitObjects.Any())
|
||||
{
|
||||
beatSnapGrid.SelectionTimeRange = (EditorBeatmap.SelectedHitObjects.Min(h => h.StartTime), EditorBeatmap.SelectedHitObjects.Max(h => h.GetEndTime()));
|
||||
}
|
||||
else
|
||||
beatSnapGrid.SelectionTimeRange = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = SnapScreenSpacePositionToValidTime(inputManager.CurrentState.Mouse.Position);
|
||||
if (result.Time is double time)
|
||||
beatSnapGrid.SelectionTimeRange = (time, time);
|
||||
else
|
||||
beatSnapGrid.SelectionTimeRange = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Mania.Edit
|
||||
|
||||
private void performColumnMovement(int lastColumn, MoveSelectionEvent moveEvent)
|
||||
{
|
||||
var currentColumn = composer.ColumnAt(moveEvent.ScreenSpacePosition);
|
||||
var currentColumn = composer.Playfield.GetColumnByPosition(moveEvent.ScreenSpacePosition);
|
||||
if (currentColumn == null)
|
||||
return;
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Mania.Edit
|
||||
maxColumn = obj.Column;
|
||||
}
|
||||
|
||||
columnDelta = Math.Clamp(columnDelta, -minColumn, composer.TotalColumns - 1 - maxColumn);
|
||||
columnDelta = Math.Clamp(columnDelta, -minColumn, composer.Playfield.TotalColumns - 1 - maxColumn);
|
||||
|
||||
foreach (var obj in SelectedHitObjects.OfType<ManiaHitObject>())
|
||||
obj.Column += columnDelta;
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Edit
|
||||
{
|
||||
public class ManiaSnapResult : SnapResult
|
||||
{
|
||||
public readonly Column Column;
|
||||
|
||||
public ManiaSnapResult(Vector2 screenSpacePosition, double time, Column column)
|
||||
: base(screenSpacePosition, time)
|
||||
{
|
||||
Column = column;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,5 +7,20 @@ namespace osu.Game.Rulesets.Mania.Scoring
|
||||
{
|
||||
public class ManiaHitWindows : HitWindows
|
||||
{
|
||||
public override bool IsHitResultAllowed(HitResult result)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case HitResult.Perfect:
|
||||
case HitResult.Great:
|
||||
case HitResult.Good:
|
||||
case HitResult.Ok:
|
||||
case HitResult.Meh:
|
||||
case HitResult.Miss:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.UI
|
||||
{
|
||||
@@ -37,6 +38,8 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
|
||||
internal readonly Container TopLevelContainer;
|
||||
|
||||
public Container UnderlayElements => hitObjectArea.UnderlayElements;
|
||||
|
||||
public Column(int index)
|
||||
{
|
||||
Index = index;
|
||||
@@ -140,5 +143,54 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
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
|
||||
=> DrawRectangle.Inflate(new Vector2(Stage.COLUMN_SPACING / 2, 0)).Contains(ToLocalSpace(screenSpacePos));
|
||||
|
||||
/// <summary>
|
||||
/// Given a time, return the screen space position within this column.
|
||||
/// </summary>
|
||||
public Vector2 ScreenSpacePositionAtTime(double time)
|
||||
{
|
||||
var pos = ScrollingInfo.Algorithm.PositionAt(time, Time.Current, ScrollingInfo.TimeRange.Value, HitObjectContainer.DrawHeight);
|
||||
|
||||
switch (ScrollingInfo.Direction.Value)
|
||||
{
|
||||
case ScrollingDirection.Down:
|
||||
// We're dealing with screen coordinates in which the position decreases towards the centre of the screen resulting in an increase in start time.
|
||||
// The scrolling algorithm instead assumes a top anchor meaning an increase in time corresponds to an increase in position,
|
||||
// so when scrolling downwards the coordinates need to be flipped.
|
||||
pos = HitObjectContainer.DrawHeight - pos;
|
||||
|
||||
// Blueprints are centred on the mouse position, such that the hitobject position is anchored at the top or bottom of the blueprint depending on the scroll direction.
|
||||
pos -= DefaultNotePiece.NOTE_HEIGHT / 2;
|
||||
break;
|
||||
|
||||
case ScrollingDirection.Up:
|
||||
pos += DefaultNotePiece.NOTE_HEIGHT / 2;
|
||||
break;
|
||||
}
|
||||
|
||||
return HitObjectContainer.ToScreenSpace(new Vector2(HitObjectContainer.DrawWidth / 2, pos));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a position in screen space, return the time within this column.
|
||||
/// </summary>
|
||||
public double TimeAtScreenSpacePosition(Vector2 screenSpacePosition)
|
||||
{
|
||||
// convert to local space of column so we can snap and fetch correct location.
|
||||
Vector2 localPosition = HitObjectContainer.ToLocalSpace(screenSpacePosition);
|
||||
|
||||
switch (ScrollingInfo.Direction.Value)
|
||||
{
|
||||
case ScrollingDirection.Down:
|
||||
// as above
|
||||
localPosition.Y = HitObjectContainer.DrawHeight - localPosition.Y;
|
||||
break;
|
||||
}
|
||||
|
||||
// offset for the fact that blueprints are centered, as above.
|
||||
localPosition.Y -= DefaultNotePiece.NOTE_HEIGHT / 2;
|
||||
|
||||
return ScrollingInfo.Algorithm.TimeAt(localPosition.Y, Time.Current, ScrollingInfo.TimeRange.Value, HitObjectContainer.DrawHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,9 @@ namespace osu.Game.Rulesets.Mania.UI.Components
|
||||
public class ColumnHitObjectArea : HitObjectArea
|
||||
{
|
||||
public readonly Container Explosions;
|
||||
|
||||
public readonly Container UnderlayElements;
|
||||
|
||||
private readonly Drawable hitTarget;
|
||||
|
||||
public ColumnHitObjectArea(int columnIndex, HitObjectContainer hitObjectContainer)
|
||||
@@ -19,6 +22,11 @@ namespace osu.Game.Rulesets.Mania.UI.Components
|
||||
{
|
||||
AddRangeInternal(new[]
|
||||
{
|
||||
UnderlayElements = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Depth = 2,
|
||||
},
|
||||
hitTarget = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitTarget, columnIndex), _ => new DefaultHitTarget())
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
|
||||
@@ -23,7 +23,6 @@ using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.UI
|
||||
{
|
||||
@@ -108,13 +107,6 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
|
||||
private void updateTimeRange() => TimeRange.Value = configTimeRange.Value * speedAdjustmentTrack.AggregateTempo.Value * speedAdjustmentTrack.AggregateFrequency.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the column that intersects a screen-space position.
|
||||
/// </summary>
|
||||
/// <param name="screenSpacePosition">The screen-space position.</param>
|
||||
/// <returns>The column which intersects with <paramref name="screenSpacePosition"/>.</returns>
|
||||
public Column GetColumnByPosition(Vector2 screenSpacePosition) => Playfield.GetColumnByPosition(screenSpacePosition);
|
||||
|
||||
public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new ManiaPlayfieldAdjustmentContainer();
|
||||
|
||||
protected override Playfield CreatePlayfield() => new ManiaPlayfield(Beatmap.Stages);
|
||||
|
||||
@@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
[Cached]
|
||||
public class ManiaPlayfield : ScrollingPlayfield
|
||||
{
|
||||
public IReadOnlyList<Stage> Stages => stages;
|
||||
|
||||
private readonly List<Stage> stages = new List<Stage>();
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => stages.Any(s => s.ReceivePositionalInputAt(screenSpacePos));
|
||||
|
||||
@@ -1,21 +1,12 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
@@ -15,12 +14,6 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
public class TestSceneDrawableJudgement : OsuSkinnableTestScene
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[]
|
||||
{
|
||||
typeof(DrawableJudgement),
|
||||
typeof(DrawableOsuJudgement)
|
||||
}).ToList();
|
||||
|
||||
public TestSceneDrawableJudgement()
|
||||
{
|
||||
foreach (HitResult result in Enum.GetValues(typeof(HitResult)).OfType<HitResult>().Skip(1))
|
||||
|
||||
@@ -2,16 +2,12 @@
|
||||
// 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.Framework.Testing.Input;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Osu.Skinning;
|
||||
using osu.Game.Rulesets.Osu.UI.Cursor;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Play;
|
||||
using osuTK;
|
||||
|
||||
@@ -20,16 +16,6 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
[TestFixture]
|
||||
public class TestSceneGameplayCursor : OsuSkinnableTestScene
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[]
|
||||
{
|
||||
typeof(GameplayCursorContainer),
|
||||
typeof(OsuCursorContainer),
|
||||
typeof(OsuCursor),
|
||||
typeof(LegacyCursor),
|
||||
typeof(LegacyCursorTrail),
|
||||
typeof(CursorTrail)
|
||||
}).ToList();
|
||||
|
||||
[Cached]
|
||||
private GameplayBeatmap gameplayBeatmap;
|
||||
|
||||
|
||||
@@ -8,8 +8,6 @@ using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osuTK;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
@@ -20,11 +18,6 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
[TestFixture]
|
||||
public class TestSceneHitCircle : OsuSkinnableTestScene
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||
{
|
||||
typeof(DrawableHitCircle)
|
||||
};
|
||||
|
||||
private int depthIndex;
|
||||
|
||||
public TestSceneHitCircle()
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
|
||||
@@ -12,8 +9,6 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
[TestFixture]
|
||||
public class TestSceneHitCircleHidden : TestSceneHitCircle
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModHidden) }).ToList();
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
{
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
@@ -16,7 +15,6 @@ using osu.Game.Rulesets.Osu.Beatmaps;
|
||||
using osu.Game.Rulesets.Osu.Edit;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Edit.Compose.Components;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
@@ -28,18 +26,13 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
private const double beat_length = 100;
|
||||
private static readonly Vector2 grid_position = new Vector2(512, 384);
|
||||
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||
{
|
||||
typeof(CircularDistanceSnapGrid)
|
||||
};
|
||||
|
||||
[Cached(typeof(EditorBeatmap))]
|
||||
private readonly EditorBeatmap editorBeatmap;
|
||||
|
||||
[Cached]
|
||||
private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor();
|
||||
|
||||
[Cached(typeof(IDistanceSnapProvider))]
|
||||
[Cached(typeof(IPositionSnapProvider))]
|
||||
private readonly SnapProvider snapProvider = new SnapProvider();
|
||||
|
||||
private TestOsuDistanceSnapGrid grid;
|
||||
@@ -179,9 +172,9 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
}
|
||||
}
|
||||
|
||||
private class SnapProvider : IDistanceSnapProvider
|
||||
private class SnapProvider : IPositionSnapProvider
|
||||
{
|
||||
public (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time) => (position, time);
|
||||
public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0);
|
||||
|
||||
public float GetBeatSnapDistanceAt(double referenceTime) => (float)beat_length;
|
||||
|
||||
|
||||
@@ -399,7 +399,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
public TestSlider()
|
||||
{
|
||||
DefaultsApplied += () =>
|
||||
DefaultsApplied += _ =>
|
||||
{
|
||||
HeadCircle.HitWindows = new TestHitWindows();
|
||||
TailCircle.HitWindows = new TestHitWindows();
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Humanizer;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
@@ -19,13 +16,6 @@ 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;
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
@@ -14,11 +12,6 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
public class TestSceneResumeOverlay : OsuManualInputManagerTestScene
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||
{
|
||||
typeof(OsuResumeOverlay),
|
||||
};
|
||||
|
||||
public TestSceneResumeOverlay()
|
||||
{
|
||||
ManualOsuInputManager osuInputManager;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@@ -21,29 +20,12 @@ using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestSceneSlider : OsuSkinnableTestScene
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||
{
|
||||
typeof(Slider),
|
||||
typeof(SliderTick),
|
||||
typeof(SliderTailCircle),
|
||||
typeof(SliderBall),
|
||||
typeof(SliderBody),
|
||||
typeof(SnakingSliderBody),
|
||||
typeof(DrawableSlider),
|
||||
typeof(DrawableSliderTick),
|
||||
typeof(DrawableSliderTail),
|
||||
typeof(DrawableSliderHead),
|
||||
typeof(DrawableSliderRepeat),
|
||||
typeof(DrawableOsuHitObject)
|
||||
};
|
||||
|
||||
private Container content;
|
||||
|
||||
protected override Container<Drawable> Content
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
|
||||
@@ -12,8 +9,6 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
[TestFixture]
|
||||
public class TestSceneSliderHidden : TestSceneSlider
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModHidden) }).ToList();
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
{
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
@@ -13,8 +12,6 @@ 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.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
|
||||
using osu.Game.Rulesets.Osu.Replays;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
@@ -27,17 +24,6 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
public class TestSceneSliderInput : RateAdjustedBeatmapTestScene
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||
{
|
||||
typeof(SliderBall),
|
||||
typeof(DrawableSlider),
|
||||
typeof(DrawableSliderTick),
|
||||
typeof(DrawableSliderRepeat),
|
||||
typeof(DrawableOsuHitObject),
|
||||
typeof(DrawableSliderHead),
|
||||
typeof(DrawableSliderTail),
|
||||
};
|
||||
|
||||
private const double time_before_slider = 250;
|
||||
private const double time_slider_start = 1500;
|
||||
private const double time_during_slide_1 = 2500;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
@@ -22,16 +20,6 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
public class TestSceneSliderSelectionBlueprint : SelectionBlueprintTestScene
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||
{
|
||||
typeof(SliderSelectionBlueprint),
|
||||
typeof(SliderCircleSelectionBlueprint),
|
||||
typeof(SliderBodyPiece),
|
||||
typeof(SliderCircle),
|
||||
typeof(PathControlPointVisualiser),
|
||||
typeof(PathControlPointPiece)
|
||||
};
|
||||
|
||||
private Slider slider;
|
||||
private DrawableSlider drawableObject;
|
||||
private TestSliderBlueprint blueprint;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
@@ -12,7 +10,6 @@ using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests
|
||||
@@ -20,13 +17,6 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
[TestFixture]
|
||||
public class TestSceneSpinner : OsuTestScene
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||
{
|
||||
typeof(SpinnerDisc),
|
||||
typeof(DrawableSpinner),
|
||||
typeof(DrawableOsuHitObject)
|
||||
};
|
||||
|
||||
private readonly Container content;
|
||||
protected override Container<Drawable> Content => content;
|
||||
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
|
||||
@@ -12,8 +9,6 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
[TestFixture]
|
||||
public class TestSceneSpinnerHidden : TestSceneSpinner
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModHidden) }).ToList();
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
{
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners;
|
||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Tests.Visual;
|
||||
@@ -18,12 +15,6 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
public class TestSceneSpinnerSelectionBlueprint : SelectionBlueprintTestScene
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||
{
|
||||
typeof(SpinnerSelectionBlueprint),
|
||||
typeof(SpinnerPiece)
|
||||
};
|
||||
|
||||
public TestSceneSpinnerSelectionBlueprint()
|
||||
{
|
||||
var spinner = new Spinner
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
@@ -12,7 +10,6 @@ using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests
|
||||
@@ -20,14 +17,6 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
[TestFixture]
|
||||
public class TestSceneSpinnerSpunOut : OsuTestScene
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||
{
|
||||
typeof(SpinnerDisc),
|
||||
typeof(DrawableSpinner),
|
||||
typeof(DrawableOsuHitObject),
|
||||
typeof(OsuModSpunOut)
|
||||
};
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@@ -45,10 +46,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
mods = Score.Mods;
|
||||
accuracy = Score.Accuracy;
|
||||
scoreMaxCombo = Score.MaxCombo;
|
||||
countGreat = Score.Statistics[HitResult.Great];
|
||||
countGood = Score.Statistics[HitResult.Good];
|
||||
countMeh = Score.Statistics[HitResult.Meh];
|
||||
countMiss = Score.Statistics[HitResult.Miss];
|
||||
countGreat = Score.Statistics.GetOrDefault(HitResult.Great);
|
||||
countGood = Score.Statistics.GetOrDefault(HitResult.Good);
|
||||
countMeh = Score.Statistics.GetOrDefault(HitResult.Meh);
|
||||
countMiss = Score.Statistics.GetOrDefault(HitResult.Miss);
|
||||
|
||||
// Don't count scores made with supposedly unranked mods
|
||||
if (mods.Any(m => !m.Ranked))
|
||||
@@ -180,7 +181,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
int amountHitObjectsWithAccuracy = countHitCircles;
|
||||
|
||||
if (amountHitObjectsWithAccuracy > 0)
|
||||
betterAccuracyPercentage = ((countGreat - (totalHits - amountHitObjectsWithAccuracy)) * 6 + countGood * 2 + countMeh) / (amountHitObjectsWithAccuracy * 6);
|
||||
betterAccuracyPercentage = ((countGreat - (totalHits - amountHitObjectsWithAccuracy)) * 6 + countGood * 2 + countMeh) / (double)(amountHitObjectsWithAccuracy * 6);
|
||||
else
|
||||
betterAccuracyPercentage = 0;
|
||||
|
||||
@@ -203,7 +204,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
return accuracyValue;
|
||||
}
|
||||
|
||||
private double totalHits => countGreat + countGood + countMeh + countMiss;
|
||||
private double totalSuccessfulHits => countGreat + countGood + countMeh;
|
||||
private int totalHits => countGreat + countGood + countMeh + countMiss;
|
||||
private int totalSuccessfulHits => countGreat + countGood + countMeh;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ using osu.Framework.Input.Events;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles
|
||||
@@ -40,6 +39,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles
|
||||
return base.OnMouseDown(e);
|
||||
}
|
||||
|
||||
public override void UpdatePosition(Vector2 screenSpacePosition) => HitObject.Position = ToLocalSpace(screenSpacePosition);
|
||||
public override void UpdatePosition(SnapResult result)
|
||||
{
|
||||
base.UpdatePosition(result);
|
||||
HitObject.Position = ToLocalSpace(result.ScreenSpacePosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
private IEditorChangeHandler changeHandler { get; set; }
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
private IDistanceSnapProvider snapProvider { get; set; }
|
||||
private IPositionSnapProvider snapProvider { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; }
|
||||
@@ -162,11 +162,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
if (ControlPoint == slider.Path.ControlPoints[0])
|
||||
{
|
||||
// Special handling for the head control point - the position of the slider changes which means the snapped position and time have to be taken into account
|
||||
(Vector2 snappedPosition, double snappedTime) = snapProvider?.GetSnappedPosition(e.MousePosition, slider.StartTime) ?? (e.MousePosition, slider.StartTime);
|
||||
Vector2 movementDelta = snappedPosition - slider.Position;
|
||||
var result = snapProvider?.SnapScreenSpacePositionToValidTime(e.ScreenSpaceMousePosition);
|
||||
|
||||
Vector2 movementDelta = Parent.ToLocalSpace(result?.ScreenSpacePosition ?? e.ScreenSpaceMousePosition) - slider.Position;
|
||||
|
||||
slider.Position += movementDelta;
|
||||
slider.StartTime = snappedTime;
|
||||
slider.StartTime = result?.Time ?? slider.StartTime;
|
||||
|
||||
// Since control points are relative to the position of the slider, they all need to be offset backwards by the delta
|
||||
for (int i = 1; i < slider.Path.ControlPoints.Count; i++)
|
||||
|
||||
@@ -67,13 +67,15 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
inputManager = GetContainingInputManager();
|
||||
}
|
||||
|
||||
public override void UpdatePosition(Vector2 screenSpacePosition)
|
||||
public override void UpdatePosition(SnapResult result)
|
||||
{
|
||||
base.UpdatePosition(result);
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case PlacementState.Initial:
|
||||
BeginPlacement();
|
||||
HitObject.Position = ToLocalSpace(screenSpacePosition);
|
||||
HitObject.Position = ToLocalSpace(result.ScreenSpacePosition);
|
||||
break;
|
||||
|
||||
case PlacementState.Body:
|
||||
|
||||
@@ -190,7 +190,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
new OsuMenuItem("Add control point", MenuItemType.Standard, () => addControlPoint(rightClickPosition)),
|
||||
};
|
||||
|
||||
public override Vector2 SelectionPoint => ((DrawableSlider)DrawableObject).HeadCircle.ScreenSpaceDrawQuad.Centre;
|
||||
public override Vector2 ScreenSpaceSelectionPoint => ((DrawableSlider)DrawableObject).HeadCircle.ScreenSpaceDrawQuad.Centre;
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => BodyPiece.ReceivePositionalInputAt(screenSpacePos);
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
|
||||
@@ -60,9 +59,5 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void UpdatePosition(Vector2 screenSpacePosition)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,8 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public override double ScoreMultiplier => 1;
|
||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail), typeof(ModAutoplay) };
|
||||
|
||||
public bool AllowFail => false;
|
||||
public bool PerformFail() => false;
|
||||
|
||||
public bool RestartOnFail => false;
|
||||
|
||||
private OsuInputManager inputManager;
|
||||
|
||||
@@ -28,8 +28,11 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
slider.NestedHitObjects.OfType<SliderTick>().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
|
||||
slider.NestedHitObjects.OfType<SliderRepeat>().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
|
||||
|
||||
foreach (var point in slider.Path.ControlPoints)
|
||||
var controlPoints = slider.Path.ControlPoints.Select(p => new PathControlPoint(p.Position.Value, p.Type.Value)).ToArray();
|
||||
foreach (var point in controlPoints)
|
||||
point.Position.Value = new Vector2(point.Position.Value.X, -point.Position.Value.Y);
|
||||
|
||||
slider.Path = new SliderPath(controlPoints, slider.Path.ExpectedDistance.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
||||
private void bindEvents(DrawableOsuHitObject drawableObject)
|
||||
{
|
||||
drawableObject.HitObject.PositionBindable.BindValueChanged(_ => scheduleRefresh());
|
||||
drawableObject.HitObject.DefaultsApplied += scheduleRefresh;
|
||||
drawableObject.HitObject.DefaultsApplied += _ => scheduleRefresh();
|
||||
}
|
||||
|
||||
private void scheduleRefresh()
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 183 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 185 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 100 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 94 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 60 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
@@ -1,21 +1,12 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Rulesets.Taiko.Skinning;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||
{
|
||||
public abstract class TaikoSkinnableTestScene : SkinnableTestScene
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||
{
|
||||
typeof(TaikoRuleset),
|
||||
typeof(TaikoLegacySkinTransformer),
|
||||
};
|
||||
|
||||
protected override Ruleset CreateRulesetForSkinProvider() => new TaikoRuleset();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
@@ -12,7 +9,6 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Rulesets.Taiko.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Taiko.Skinning;
|
||||
using osu.Game.Rulesets.Taiko.UI;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Tests.Visual;
|
||||
@@ -22,13 +18,6 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||
[TestFixture]
|
||||
public class TestSceneDrawableBarLine : TaikoSkinnableTestScene
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[]
|
||||
{
|
||||
typeof(DrawableBarLine),
|
||||
typeof(LegacyBarLine),
|
||||
typeof(BarLine),
|
||||
}).ToList();
|
||||
|
||||
[Cached(typeof(IScrollingInfo))]
|
||||
private ScrollingTestContainer.TestScrollingInfo info = new ScrollingTestContainer.TestScrollingInfo
|
||||
{
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
@@ -11,7 +8,6 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Rulesets.Taiko.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Taiko.Skinning;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
@@ -20,13 +16,6 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||
[TestFixture]
|
||||
public class TestSceneDrawableDrumRoll : TaikoSkinnableTestScene
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[]
|
||||
{
|
||||
typeof(DrawableDrumRoll),
|
||||
typeof(DrawableDrumRollTick),
|
||||
typeof(LegacyDrumRoll),
|
||||
}).ToList();
|
||||
|
||||
[Cached(typeof(IScrollingInfo))]
|
||||
private ScrollingTestContainer.TestScrollingInfo info = new ScrollingTestContainer.TestScrollingInfo
|
||||
{
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
@@ -11,20 +8,12 @@ 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.Skinning
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestSceneDrawableHit : TaikoSkinnableTestScene
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[]
|
||||
{
|
||||
typeof(DrawableHit),
|
||||
typeof(LegacyHit),
|
||||
typeof(LegacyCirclePiece),
|
||||
}).ToList();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Humanizer;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Taiko.Judgements;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Rulesets.Taiko.Scoring;
|
||||
using osu.Game.Rulesets.Taiko.UI;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestSceneDrawableTaikoMascot : TaikoSkinnableTestScene
|
||||
{
|
||||
[Cached(typeof(IScrollingInfo))]
|
||||
private ScrollingTestContainer.TestScrollingInfo info = new ScrollingTestContainer.TestScrollingInfo
|
||||
{
|
||||
Direction = { Value = ScrollingDirection.Left },
|
||||
TimeRange = { Value = 5000 },
|
||||
};
|
||||
|
||||
private TaikoScoreProcessor scoreProcessor;
|
||||
|
||||
private IEnumerable<DrawableTaikoMascot> mascots => this.ChildrenOfType<DrawableTaikoMascot>();
|
||||
private IEnumerable<TaikoPlayfield> playfields => this.ChildrenOfType<TaikoPlayfield>();
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
scoreProcessor = new TaikoScoreProcessor();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestStateAnimations()
|
||||
{
|
||||
AddStep("set beatmap", () => setBeatmap());
|
||||
|
||||
AddStep("clear state", () => SetContents(() => new TaikoMascotAnimation(TaikoMascotAnimationState.Clear)));
|
||||
AddStep("idle state", () => SetContents(() => new TaikoMascotAnimation(TaikoMascotAnimationState.Idle)));
|
||||
AddStep("kiai state", () => SetContents(() => new TaikoMascotAnimation(TaikoMascotAnimationState.Kiai)));
|
||||
AddStep("fail state", () => SetContents(() => new TaikoMascotAnimation(TaikoMascotAnimationState.Fail)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestInitialState()
|
||||
{
|
||||
AddStep("create mascot", () => SetContents(() => new DrawableTaikoMascot { RelativeSizeAxes = Axes.Both }));
|
||||
|
||||
AddAssert("mascot initially idle", () => allMascotsIn(TaikoMascotAnimationState.Idle));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestClearStateTransition()
|
||||
{
|
||||
AddStep("set beatmap", () => setBeatmap());
|
||||
|
||||
AddStep("create mascot", () => SetContents(() => new DrawableTaikoMascot { RelativeSizeAxes = Axes.Both }));
|
||||
|
||||
AddStep("set clear state", () => mascots.ForEach(mascot => mascot.State.Value = TaikoMascotAnimationState.Clear));
|
||||
AddStep("miss", () => mascots.ForEach(mascot => mascot.LastResult.Value = new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Miss }));
|
||||
AddAssert("skins with animations remain in clear state", () => someMascotsIn(TaikoMascotAnimationState.Clear));
|
||||
AddUntilStep("state reverts to fail", () => allMascotsIn(TaikoMascotAnimationState.Fail));
|
||||
|
||||
AddStep("set clear state again", () => mascots.ForEach(mascot => mascot.State.Value = TaikoMascotAnimationState.Clear));
|
||||
AddAssert("skins with animations change to clear", () => someMascotsIn(TaikoMascotAnimationState.Clear));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestIdleState()
|
||||
{
|
||||
AddStep("set beatmap", () => setBeatmap());
|
||||
|
||||
createDrawableRuleset();
|
||||
|
||||
assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Great }, TaikoMascotAnimationState.Idle);
|
||||
assertStateAfterResult(new JudgementResult(new StrongHitObject(), new TaikoStrongJudgement()) { Type = HitResult.Miss }, TaikoMascotAnimationState.Idle);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestKiaiState()
|
||||
{
|
||||
AddStep("set beatmap", () => setBeatmap(true));
|
||||
|
||||
createDrawableRuleset();
|
||||
|
||||
assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Good }, TaikoMascotAnimationState.Kiai);
|
||||
assertStateAfterResult(new JudgementResult(new Hit(), new TaikoStrongJudgement()) { Type = HitResult.Miss }, TaikoMascotAnimationState.Kiai);
|
||||
assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Miss }, TaikoMascotAnimationState.Fail);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMissState()
|
||||
{
|
||||
AddStep("set beatmap", () => setBeatmap());
|
||||
|
||||
createDrawableRuleset();
|
||||
|
||||
assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Great }, TaikoMascotAnimationState.Idle);
|
||||
assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Miss }, TaikoMascotAnimationState.Fail);
|
||||
assertStateAfterResult(new JudgementResult(new DrumRoll(), new TaikoDrumRollJudgement()) { Type = HitResult.Great }, TaikoMascotAnimationState.Fail);
|
||||
assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Good }, TaikoMascotAnimationState.Idle);
|
||||
}
|
||||
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public void TestClearStateOnComboMilestone(bool kiai)
|
||||
{
|
||||
AddStep("set beatmap", () => setBeatmap(kiai));
|
||||
|
||||
createDrawableRuleset();
|
||||
|
||||
AddRepeatStep("reach 49 combo", () => applyNewResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Great }), 49);
|
||||
|
||||
assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Good }, TaikoMascotAnimationState.Clear);
|
||||
}
|
||||
|
||||
[TestCase(true, TaikoMascotAnimationState.Kiai)]
|
||||
[TestCase(false, TaikoMascotAnimationState.Idle)]
|
||||
public void TestClearStateOnClearedSwell(bool kiai, TaikoMascotAnimationState expectedStateAfterClear)
|
||||
{
|
||||
AddStep("set beatmap", () => setBeatmap(kiai));
|
||||
|
||||
createDrawableRuleset();
|
||||
|
||||
assertStateAfterResult(new JudgementResult(new Swell(), new TaikoSwellJudgement()) { Type = HitResult.Great }, TaikoMascotAnimationState.Clear);
|
||||
AddUntilStep($"state reverts to {expectedStateAfterClear.ToString().ToLower()}", () => allMascotsIn(expectedStateAfterClear));
|
||||
}
|
||||
|
||||
private void setBeatmap(bool kiai = false)
|
||||
{
|
||||
var controlPointInfo = new ControlPointInfo();
|
||||
controlPointInfo.Add(0, new TimingControlPoint { BeatLength = 90 });
|
||||
|
||||
if (kiai)
|
||||
controlPointInfo.Add(0, new EffectControlPoint { KiaiMode = true });
|
||||
|
||||
Beatmap.Value = CreateWorkingBeatmap(new Beatmap
|
||||
{
|
||||
HitObjects = new List<HitObject> { new Hit { Type = HitType.Centre } },
|
||||
BeatmapInfo = new BeatmapInfo
|
||||
{
|
||||
BaseDifficulty = new BeatmapDifficulty(),
|
||||
Metadata = new BeatmapMetadata
|
||||
{
|
||||
Artist = "Unknown",
|
||||
Title = "Sample Beatmap",
|
||||
AuthorString = "Craftplacer",
|
||||
},
|
||||
Ruleset = new TaikoRuleset().RulesetInfo
|
||||
},
|
||||
ControlPointInfo = controlPointInfo
|
||||
});
|
||||
|
||||
scoreProcessor.ApplyBeatmap(Beatmap.Value.Beatmap);
|
||||
}
|
||||
|
||||
private void createDrawableRuleset()
|
||||
{
|
||||
AddUntilStep("wait for beatmap to be loaded", () => Beatmap.Value.Track.IsLoaded);
|
||||
|
||||
AddStep("create drawable ruleset", () =>
|
||||
{
|
||||
Beatmap.Value.Track.Start();
|
||||
|
||||
SetContents(() =>
|
||||
{
|
||||
var ruleset = new TaikoRuleset();
|
||||
return new DrawableTaikoRuleset(ruleset, Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void assertStateAfterResult(JudgementResult judgementResult, TaikoMascotAnimationState expectedState)
|
||||
{
|
||||
AddStep($"{judgementResult.Type.ToString().ToLower()} result for {judgementResult.Judgement.GetType().Name.Humanize(LetterCasing.LowerCase)}",
|
||||
() => applyNewResult(judgementResult));
|
||||
|
||||
AddAssert($"state is {expectedState.ToString().ToLower()}", () => allMascotsIn(expectedState));
|
||||
}
|
||||
|
||||
private void applyNewResult(JudgementResult judgementResult)
|
||||
{
|
||||
scoreProcessor.ApplyResult(judgementResult);
|
||||
|
||||
foreach (var playfield in playfields)
|
||||
{
|
||||
var hit = new DrawableTestHit(new Hit(), judgementResult.Type);
|
||||
Add(hit);
|
||||
|
||||
playfield.OnNewResult(hit, judgementResult);
|
||||
}
|
||||
|
||||
foreach (var mascot in mascots)
|
||||
{
|
||||
mascot.LastResult.Value = judgementResult;
|
||||
}
|
||||
}
|
||||
|
||||
private bool allMascotsIn(TaikoMascotAnimationState state) => mascots.All(d => d.State.Value == state);
|
||||
private bool someMascotsIn(TaikoMascotAnimationState state) => mascots.Any(d => d.State.Value == state);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
@@ -11,7 +8,6 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Rulesets.Taiko.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Taiko.Skinning;
|
||||
using osu.Game.Rulesets.Taiko.UI;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||
@@ -19,13 +15,6 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||
[TestFixture]
|
||||
public class TestSceneHitExplosion : TaikoSkinnableTestScene
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[]
|
||||
{
|
||||
typeof(HitExplosion),
|
||||
typeof(LegacyHitExplosion),
|
||||
typeof(DefaultHitExplosion),
|
||||
}).ToList();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
// 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.Framework.Graphics.Containers;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Taiko.Skinning;
|
||||
using osu.Game.Rulesets.Taiko.UI;
|
||||
using osuTK;
|
||||
|
||||
@@ -18,12 +14,6 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||
[TestFixture]
|
||||
public class TestSceneInputDrum : TaikoSkinnableTestScene
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[]
|
||||
{
|
||||
typeof(InputDrum),
|
||||
typeof(LegacyInputDrum),
|
||||
}).ToList();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
|
||||
@@ -2,15 +2,12 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Taiko.Beatmaps;
|
||||
using osu.Game.Rulesets.Taiko.Skinning;
|
||||
using osu.Game.Rulesets.Taiko.UI;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Tests.Visual;
|
||||
@@ -19,13 +16,6 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||
{
|
||||
public class TestSceneTaikoPlayfield : TaikoSkinnableTestScene
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[]
|
||||
{
|
||||
typeof(TaikoHitTarget),
|
||||
typeof(TaikoLegacyHitTarget),
|
||||
typeof(PlayfieldBackgroundRight),
|
||||
}).ToList();
|
||||
|
||||
[Cached(typeof(IScrollingInfo))]
|
||||
private ScrollingTestContainer.TestScrollingInfo info = new ScrollingTestContainer.TestScrollingInfo
|
||||
{
|
||||
@@ -51,6 +41,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Height = 0.6f,
|
||||
}));
|
||||
|
||||
AddRepeatStep("change height", () => this.ChildrenOfType<TaikoPlayfield>().ForEach(p => p.Height = Math.Max(0.2f, (p.Height + 0.2f) % 1f)), 50);
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Taiko.Skinning;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||
{
|
||||
public class TestSceneTaikoScroller : TaikoSkinnableTestScene
|
||||
{
|
||||
private readonly ManualClock clock = new ManualClock();
|
||||
|
||||
private bool reversed;
|
||||
|
||||
public TestSceneTaikoScroller()
|
||||
{
|
||||
AddStep("Load scroller", () => SetContents(() =>
|
||||
new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.Scroller), _ => Empty())
|
||||
{
|
||||
Clock = new FramedClock(clock),
|
||||
Height = 0.4f,
|
||||
}));
|
||||
|
||||
AddToggleStep("Toggle passing", passing => this.ChildrenOfType<LegacyTaikoScroller>().ForEach(s => s.LastResult.Value =
|
||||
new JudgementResult(null, new Judgement()) { Type = passing ? HitResult.Perfect : HitResult.Miss }));
|
||||
|
||||
AddToggleStep("toggle playback direction", reversed => this.reversed = reversed);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
clock.CurrentTime += (reversed ? -1 : 1) * Clock.ElapsedFrameTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
||||
[NonParallelizable]
|
||||
[TestCase("basic")]
|
||||
[TestCase("slider-generating-drumroll")]
|
||||
[TestCase("sample-to-type-conversions")]
|
||||
public void Test(string name) => base.Test(name);
|
||||
|
||||
protected override IEnumerable<ConvertValue> CreateConvertValue(HitObject hitObject)
|
||||
@@ -41,7 +42,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
||||
public struct ConvertValue : IEquatable<ConvertValue>
|
||||
{
|
||||
/// <summary>
|
||||
/// A sane value to account for osu!stable using ints everwhere.
|
||||
/// A sane value to account for osu!stable using ints everywhere.
|
||||
/// </summary>
|
||||
private const float conversion_lenience = 2;
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Taiko.Objects.Drawables;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Tests
|
||||
{
|
||||
/// <summary>
|
||||
/// Taiko has some interesting rules for legacy mappings.
|
||||
/// </summary>
|
||||
[HeadlessTest]
|
||||
public class TestSceneSampleOutput : PlayerTestScene
|
||||
{
|
||||
public TestSceneSampleOutput()
|
||||
: base(new TaikoRuleset())
|
||||
{
|
||||
}
|
||||
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
base.SetUpSteps();
|
||||
AddAssert("has correct samples", () =>
|
||||
{
|
||||
var names = Player.DrawableRuleset.Playfield.AllHitObjects.OfType<DrawableHit>().Select(h => string.Join(',', h.GetSamples().Select(s => s.Name)));
|
||||
|
||||
var expected = new[]
|
||||
{
|
||||
string.Empty,
|
||||
string.Empty,
|
||||
string.Empty,
|
||||
string.Empty,
|
||||
HitSampleInfo.HIT_FINISH,
|
||||
HitSampleInfo.HIT_WHISTLE,
|
||||
HitSampleInfo.HIT_WHISTLE,
|
||||
HitSampleInfo.HIT_WHISTLE,
|
||||
};
|
||||
|
||||
return names.SequenceEqual(expected);
|
||||
});
|
||||
}
|
||||
|
||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TaikoBeatmapConversionTest().GetBeatmap("sample-to-type-conversions");
|
||||
}
|
||||
}
|
||||
@@ -167,13 +167,15 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
||||
|
||||
default:
|
||||
{
|
||||
bool isRim = samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE);
|
||||
bool isRimDefinition(HitSampleInfo s) => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE;
|
||||
|
||||
bool isRim = samples.Any(isRimDefinition);
|
||||
|
||||
yield return new Hit
|
||||
{
|
||||
StartTime = obj.StartTime,
|
||||
Type = isRim ? HitType.Rim : HitType.Centre,
|
||||
Samples = obj.Samples,
|
||||
Samples = samples,
|
||||
IsStrong = strong
|
||||
};
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@@ -31,10 +32,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
public override double Calculate(Dictionary<string, double> categoryDifficulty = null)
|
||||
{
|
||||
mods = Score.Mods;
|
||||
countGreat = Score.Statistics[HitResult.Great];
|
||||
countGood = Score.Statistics[HitResult.Good];
|
||||
countMeh = Score.Statistics[HitResult.Meh];
|
||||
countMiss = Score.Statistics[HitResult.Miss];
|
||||
countGreat = Score.Statistics.GetOrDefault(HitResult.Great);
|
||||
countGood = Score.Statistics.GetOrDefault(HitResult.Good);
|
||||
countMeh = Score.Statistics.GetOrDefault(HitResult.Meh);
|
||||
countMiss = Score.Statistics.GetOrDefault(HitResult.Miss);
|
||||
|
||||
// Don't count scores made with supposedly unranked mods
|
||||
if (mods.Any(m => !m.Ranked))
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
|
||||
@@ -47,6 +49,42 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
? new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.CentreHit), _ => new CentreHitCirclePiece(), confineMode: ConfineMode.ScaleToFit)
|
||||
: new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.RimHit), _ => new RimHitCirclePiece(), confineMode: ConfineMode.ScaleToFit);
|
||||
|
||||
public override IEnumerable<HitSampleInfo> GetSamples()
|
||||
{
|
||||
// normal and claps are always handled by the drum (see DrumSampleMapping).
|
||||
// in addition, whistles are excluded as they are an alternative rim marker.
|
||||
|
||||
var samples = HitObject.Samples.Where(s =>
|
||||
s.Name != HitSampleInfo.HIT_NORMAL
|
||||
&& s.Name != HitSampleInfo.HIT_CLAP
|
||||
&& s.Name != HitSampleInfo.HIT_WHISTLE);
|
||||
|
||||
if (HitObject.Type == HitType.Rim && HitObject.IsStrong)
|
||||
{
|
||||
// strong + rim always maps to whistle.
|
||||
// TODO: this should really be in the legacy decoder, but can't be because legacy encoding parity would be broken.
|
||||
// when we add a taiko editor, this is probably not going to play nice.
|
||||
|
||||
var corrected = samples.ToList();
|
||||
|
||||
for (var i = 0; i < corrected.Count; i++)
|
||||
{
|
||||
var s = corrected[i];
|
||||
|
||||
if (s.Name != HitSampleInfo.HIT_FINISH)
|
||||
continue;
|
||||
|
||||
var sClone = s.Clone();
|
||||
sClone.Name = HitSampleInfo.HIT_WHISTLE;
|
||||
corrected[i] = sClone;
|
||||
}
|
||||
|
||||
return corrected;
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||
{
|
||||
Debug.Assert(HitObject.HitWindows != null);
|
||||
|
||||
@@ -165,8 +165,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
return base.CreateNestedHitObject(hitObject);
|
||||
}
|
||||
|
||||
// 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);
|
||||
// Most osu!taiko hitsounds are managed by the drum (see DrumSampleMapping).
|
||||
public override IEnumerable<HitSampleInfo> GetSamples() => Enumerable.Empty<HitSampleInfo>();
|
||||
|
||||
protected abstract SkinnableDrawable CreateMainPiece();
|
||||
|
||||
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
{
|
||||
"Mappings": [
|
||||
{
|
||||
"StartTime": 110.0,
|
||||
"Objects": [
|
||||
{
|
||||
"StartTime": 110.0,
|
||||
"EndTime": 110.0,
|
||||
"IsRim": false,
|
||||
"IsCentre": true,
|
||||
"IsDrumRoll": false,
|
||||
"IsSwell": false,
|
||||
"IsStrong": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"StartTime": 538.0,
|
||||
"Objects": [
|
||||
{
|
||||
"StartTime": 538.0,
|
||||
"EndTime": 538.0,
|
||||
"IsRim": true,
|
||||
"IsCentre": false,
|
||||
"IsDrumRoll": false,
|
||||
"IsSwell": false,
|
||||
"IsStrong": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"StartTime": 967.0,
|
||||
"Objects": [
|
||||
{
|
||||
"StartTime": 967.0,
|
||||
"EndTime": 967.0,
|
||||
"IsRim": true,
|
||||
"IsCentre": false,
|
||||
"IsDrumRoll": false,
|
||||
"IsSwell": false,
|
||||
"IsStrong": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"StartTime": 1395.0,
|
||||
"Objects": [
|
||||
{
|
||||
"StartTime": 1395.0,
|
||||
"EndTime": 1395.0,
|
||||
"IsRim": true,
|
||||
"IsCentre": false,
|
||||
"IsDrumRoll": false,
|
||||
"IsSwell": false,
|
||||
"IsStrong": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"StartTime": 1824.0,
|
||||
"Objects": [
|
||||
{
|
||||
"StartTime": 1824.0,
|
||||
"EndTime": 1824.0,
|
||||
"IsRim": false,
|
||||
"IsCentre": true,
|
||||
"IsDrumRoll": false,
|
||||
"IsSwell": false,
|
||||
"IsStrong": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"StartTime": 2252.0,
|
||||
"Objects": [
|
||||
{
|
||||
"StartTime": 2252.0,
|
||||
"EndTime": 2252.0,
|
||||
"IsRim": true,
|
||||
"IsCentre": false,
|
||||
"IsDrumRoll": false,
|
||||
"IsSwell": false,
|
||||
"IsStrong": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"StartTime": 2681.0,
|
||||
"Objects": [
|
||||
{
|
||||
"StartTime": 2681.0,
|
||||
"EndTime": 2681.0,
|
||||
"IsRim": true,
|
||||
"IsCentre": false,
|
||||
"IsDrumRoll": false,
|
||||
"IsSwell": false,
|
||||
"IsStrong": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"StartTime": 3110.0,
|
||||
"Objects": [
|
||||
{
|
||||
"StartTime": 3110.0,
|
||||
"EndTime": 3110.0,
|
||||
"IsRim": true,
|
||||
"IsCentre": false,
|
||||
"IsDrumRoll": false,
|
||||
"IsSwell": false,
|
||||
"IsStrong": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
osu file format v14
|
||||
|
||||
[General]
|
||||
AudioFilename: audio.mp3
|
||||
AudioLeadIn: 0
|
||||
PreviewTime: -1
|
||||
Countdown: 0
|
||||
SampleSet: Normal
|
||||
StackLeniency: 0.5
|
||||
Mode: 1
|
||||
LetterboxInBreaks: 0
|
||||
WidescreenStoryboard: 1
|
||||
|
||||
[Editor]
|
||||
Bookmarks: 110,13824,54967,82395,109824
|
||||
DistanceSpacing: 0.1
|
||||
BeatDivisor: 4
|
||||
GridSize: 32
|
||||
TimelineZoom: 3.099999
|
||||
|
||||
[Metadata]
|
||||
Title:test
|
||||
TitleUnicode:test
|
||||
Artist:sample conversion
|
||||
ArtistUnicode:sample conversion
|
||||
Creator:banchobot
|
||||
Version:sample test
|
||||
Source:
|
||||
Tags:
|
||||
BeatmapID:0
|
||||
BeatmapSetID:-1
|
||||
|
||||
[Difficulty]
|
||||
HPDrainRate:6
|
||||
CircleSize:2
|
||||
OverallDifficulty:6
|
||||
ApproachRate:7
|
||||
SliderMultiplier:1.4
|
||||
SliderTickRate:4
|
||||
|
||||
[Events]
|
||||
//Background and Video events
|
||||
//Break Periods
|
||||
//Storyboard Layer 0 (Background)
|
||||
//Storyboard Layer 1 (Fail)
|
||||
//Storyboard Layer 2 (Pass)
|
||||
//Storyboard Layer 3 (Foreground)
|
||||
//Storyboard Layer 4 (Overlay)
|
||||
//Storyboard Sound Samples
|
||||
|
||||
[TimingPoints]
|
||||
110,428.571428571429,4,1,0,100,1,0
|
||||
|
||||
[HitObjects]
|
||||
256,192,110,5,0,0:0:0:0:
|
||||
256,192,538,1,8,0:0:0:0:
|
||||
256,192,967,1,2,0:0:0:0:
|
||||
256,192,1395,1,10,0:0:0:0:
|
||||
256,192,1824,1,4,0:0:0:0:
|
||||
256,192,2252,1,12,0:0:0:0:
|
||||
256,192,2681,1,6,0:0:0:0:
|
||||
256,192,3110,1,14,0:0:0:0:
|
||||
@@ -0,0 +1,152 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Skinning
|
||||
{
|
||||
public class LegacyTaikoScroller : CompositeDrawable
|
||||
{
|
||||
public Bindable<JudgementResult> LastResult = new Bindable<JudgementResult>();
|
||||
|
||||
public LegacyTaikoScroller()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(GameplayBeatmap gameplayBeatmap)
|
||||
{
|
||||
if (gameplayBeatmap != null)
|
||||
((IBindable<JudgementResult>)LastResult).BindTo(gameplayBeatmap.LastJudgementResult);
|
||||
}
|
||||
|
||||
private bool passing;
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
LastResult.BindValueChanged(result =>
|
||||
{
|
||||
var r = result.NewValue;
|
||||
|
||||
// always ignore hitobjects that don't affect combo (drumroll ticks etc.)
|
||||
if (r?.Judgement.AffectsCombo == false)
|
||||
return;
|
||||
|
||||
passing = r == null || r.Type > HitResult.Miss;
|
||||
|
||||
foreach (var sprite in InternalChildren.OfType<ScrollerSprite>())
|
||||
sprite.Passing = passing;
|
||||
}, true);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
// store X before checking wide enough so if we perform layout there is no positional discrepancy.
|
||||
float currentX = (InternalChildren?.FirstOrDefault()?.X ?? 0) - (float)Clock.ElapsedFrameTime * 0.1f;
|
||||
|
||||
// ensure we have enough sprites
|
||||
if (!InternalChildren.Any()
|
||||
|| InternalChildren.First().ScreenSpaceDrawQuad.Width * InternalChildren.Count < ScreenSpaceDrawQuad.Width * 2)
|
||||
AddInternal(new ScrollerSprite { Passing = passing });
|
||||
|
||||
var first = InternalChildren.First();
|
||||
var last = InternalChildren.Last();
|
||||
|
||||
foreach (var sprite in InternalChildren)
|
||||
{
|
||||
// add the x coordinates and perform re-layout on all sprites as spacing may change with gameplay scale.
|
||||
sprite.X = currentX;
|
||||
currentX += sprite.DrawWidth;
|
||||
}
|
||||
|
||||
if (first.ScreenSpaceDrawQuad.TopLeft.X >= ScreenSpaceDrawQuad.TopLeft.X)
|
||||
{
|
||||
foreach (var internalChild in InternalChildren)
|
||||
internalChild.X -= first.DrawWidth;
|
||||
}
|
||||
|
||||
if (last.ScreenSpaceDrawQuad.TopRight.X <= ScreenSpaceDrawQuad.TopRight.X)
|
||||
{
|
||||
foreach (var internalChild in InternalChildren)
|
||||
internalChild.X += first.DrawWidth;
|
||||
}
|
||||
}
|
||||
|
||||
private class ScrollerSprite : CompositeDrawable
|
||||
{
|
||||
private Sprite passingSprite;
|
||||
private Sprite failingSprite;
|
||||
|
||||
private bool passing = true;
|
||||
|
||||
public bool Passing
|
||||
{
|
||||
get => passing;
|
||||
set
|
||||
{
|
||||
if (value == passing)
|
||||
return;
|
||||
|
||||
passing = value;
|
||||
|
||||
if (IsLoaded)
|
||||
updatePassing();
|
||||
}
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ISkinSource skin)
|
||||
{
|
||||
AutoSizeAxes = Axes.X;
|
||||
RelativeSizeAxes = Axes.Y;
|
||||
|
||||
FillMode = FillMode.Fit;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
passingSprite = new Sprite { Texture = skin.GetTexture("taiko-slider") },
|
||||
failingSprite = new Sprite { Texture = skin.GetTexture("taiko-slider-fail"), Alpha = 0 },
|
||||
};
|
||||
|
||||
updatePassing();
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
foreach (var c in InternalChildren)
|
||||
c.Scale = new Vector2(DrawHeight / c.Height);
|
||||
}
|
||||
|
||||
private void updatePassing()
|
||||
{
|
||||
if (passing)
|
||||
{
|
||||
passingSprite.Show();
|
||||
failingSprite.FadeOut(200);
|
||||
}
|
||||
else
|
||||
{
|
||||
failingSprite.FadeIn(200);
|
||||
passingSprite.Delay(200).FadeOut();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Rulesets.Taiko.UI;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Skinning
|
||||
@@ -85,6 +86,18 @@ namespace osu.Game.Rulesets.Taiko.Skinning
|
||||
return new LegacyHitExplosion(sprite);
|
||||
|
||||
return null;
|
||||
|
||||
case TaikoSkinComponents.Scroller:
|
||||
if (GetTexture("taiko-slider") != null)
|
||||
return new LegacyTaikoScroller();
|
||||
|
||||
return null;
|
||||
|
||||
case TaikoSkinComponents.Mascot:
|
||||
if (GetTexture("pippidonclear0") != null)
|
||||
return new DrawableTaikoMascot();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return source.GetDrawableComponent(component);
|
||||
|
||||
@@ -18,5 +18,7 @@ namespace osu.Game.Rulesets.Taiko
|
||||
TaikoExplosionMiss,
|
||||
TaikoExplosionGood,
|
||||
TaikoExplosionGreat,
|
||||
Scroller,
|
||||
Mascot,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Taiko.Judgements;
|
||||
using osu.Game.Screens.Play;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.UI
|
||||
{
|
||||
public class DrawableTaikoMascot : BeatSyncedContainer
|
||||
{
|
||||
public readonly Bindable<TaikoMascotAnimationState> State;
|
||||
public readonly Bindable<JudgementResult> LastResult;
|
||||
|
||||
private readonly Dictionary<TaikoMascotAnimationState, TaikoMascotAnimation> animations;
|
||||
private TaikoMascotAnimation currentAnimation;
|
||||
|
||||
private bool lastObjectHit = true;
|
||||
private bool kiaiMode;
|
||||
|
||||
public DrawableTaikoMascot(TaikoMascotAnimationState startingState = TaikoMascotAnimationState.Idle)
|
||||
{
|
||||
Origin = Anchor = Anchor.BottomLeft;
|
||||
|
||||
State = new Bindable<TaikoMascotAnimationState>(startingState);
|
||||
LastResult = new Bindable<JudgementResult>();
|
||||
|
||||
animations = new Dictionary<TaikoMascotAnimationState, TaikoMascotAnimation>();
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(TextureStore textures, GameplayBeatmap gameplayBeatmap)
|
||||
{
|
||||
InternalChildren = new[]
|
||||
{
|
||||
animations[TaikoMascotAnimationState.Idle] = new TaikoMascotAnimation(TaikoMascotAnimationState.Idle),
|
||||
animations[TaikoMascotAnimationState.Clear] = new TaikoMascotAnimation(TaikoMascotAnimationState.Clear),
|
||||
animations[TaikoMascotAnimationState.Kiai] = new TaikoMascotAnimation(TaikoMascotAnimationState.Kiai),
|
||||
animations[TaikoMascotAnimationState.Fail] = new TaikoMascotAnimation(TaikoMascotAnimationState.Fail),
|
||||
};
|
||||
|
||||
if (gameplayBeatmap != null)
|
||||
((IBindable<JudgementResult>)LastResult).BindTo(gameplayBeatmap.LastJudgementResult);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
animations.Values.ForEach(animation => animation.Hide());
|
||||
|
||||
State.BindValueChanged(mascotStateChanged, true);
|
||||
LastResult.BindValueChanged(onNewResult);
|
||||
}
|
||||
|
||||
private void onNewResult(ValueChangedEvent<JudgementResult> resultChangedEvent)
|
||||
{
|
||||
var result = resultChangedEvent.NewValue;
|
||||
if (result == null)
|
||||
return;
|
||||
|
||||
// TODO: missing support for clear/fail state transition at end of beatmap gameplay
|
||||
|
||||
if (triggerComboClear(result) || triggerSwellClear(result))
|
||||
{
|
||||
State.Value = TaikoMascotAnimationState.Clear;
|
||||
// always consider a clear equivalent to a hit to avoid clear -> miss transitions
|
||||
lastObjectHit = true;
|
||||
}
|
||||
|
||||
if (!result.Judgement.AffectsCombo)
|
||||
return;
|
||||
|
||||
lastObjectHit = result.IsHit;
|
||||
}
|
||||
|
||||
protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes)
|
||||
{
|
||||
kiaiMode = effectPoint.KiaiMode;
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
State.Value = getNextState();
|
||||
}
|
||||
|
||||
private TaikoMascotAnimationState getNextState()
|
||||
{
|
||||
// don't change state if current animation is still playing (and we haven't rewound before it).
|
||||
// used for clear state - others are manually animated on new beats.
|
||||
if (currentAnimation?.Completed == false && currentAnimation.DisplayTime <= Time.Current)
|
||||
return State.Value;
|
||||
|
||||
if (!lastObjectHit)
|
||||
return TaikoMascotAnimationState.Fail;
|
||||
|
||||
return kiaiMode ? TaikoMascotAnimationState.Kiai : TaikoMascotAnimationState.Idle;
|
||||
}
|
||||
|
||||
private void mascotStateChanged(ValueChangedEvent<TaikoMascotAnimationState> state)
|
||||
{
|
||||
currentAnimation?.Hide();
|
||||
currentAnimation = animations[state.NewValue];
|
||||
currentAnimation.Show();
|
||||
}
|
||||
|
||||
private bool triggerComboClear(JudgementResult judgementResult)
|
||||
=> (judgementResult.ComboAtJudgement + 1) % 50 == 0 && judgementResult.Judgement.AffectsCombo && judgementResult.IsHit;
|
||||
|
||||
private bool triggerSwellClear(JudgementResult judgementResult)
|
||||
=> judgementResult.Judgement is TaikoSwellJudgement && judgementResult.IsHit;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
@@ -16,11 +17,15 @@ using osu.Game.Replays;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.UI
|
||||
{
|
||||
public class DrawableTaikoRuleset : DrawableScrollingRuleset<TaikoHitObject>
|
||||
{
|
||||
private SkinnableDrawable scroller;
|
||||
|
||||
protected override ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Overlapping;
|
||||
|
||||
protected override bool UserScrollSpeedAdjustment => false;
|
||||
@@ -36,6 +41,20 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
private void load()
|
||||
{
|
||||
new BarLineGenerator<BarLine>(Beatmap).BarLines.ForEach(bar => Playfield.Add(bar.Major ? new DrawableBarLineMajor(bar) : new DrawableBarLine(bar)));
|
||||
|
||||
FrameStableComponents.Add(scroller = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.Scroller), _ => Empty())
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Depth = float.MaxValue
|
||||
});
|
||||
}
|
||||
|
||||
protected override void UpdateAfterChildren()
|
||||
{
|
||||
base.UpdateAfterChildren();
|
||||
|
||||
var playfieldScreen = Playfield.ScreenSpaceDrawQuad;
|
||||
scroller.Height = ToLocalSpace(playfieldScreen.TopLeft + new Vector2(0, playfieldScreen.Height / 20)).Y;
|
||||
}
|
||||
|
||||
public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new TaikoPlayfieldAdjustmentContainer();
|
||||
|
||||
@@ -12,6 +12,7 @@ using osu.Framework.Input.Bindings;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Taiko.Audio;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.UI
|
||||
@@ -145,6 +146,9 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
centreHit.Colour = colours.Pink;
|
||||
}
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
private GameplayClock gameplayClock { get; set; }
|
||||
|
||||
public bool OnPressed(TaikoAction action)
|
||||
{
|
||||
Drawable target = null;
|
||||
@@ -157,14 +161,16 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
target = centreHit;
|
||||
back = centre;
|
||||
|
||||
drumSample.Centre?.Play();
|
||||
if (gameplayClock?.IsSeeking != true)
|
||||
drumSample.Centre?.Play();
|
||||
}
|
||||
else if (action == RimAction)
|
||||
{
|
||||
target = rimHit;
|
||||
back = rim;
|
||||
|
||||
drumSample.Rim?.Play();
|
||||
if (gameplayClock?.IsSeeking != true)
|
||||
drumSample.Rim?.Play();
|
||||
}
|
||||
|
||||
if (target != null)
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Animations;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.UI
|
||||
{
|
||||
public sealed class TaikoMascotAnimation : BeatSyncedContainer
|
||||
{
|
||||
private readonly TextureAnimation textureAnimation;
|
||||
|
||||
private int currentFrame;
|
||||
|
||||
public double DisplayTime;
|
||||
|
||||
public TaikoMascotAnimation(TaikoMascotAnimationState state)
|
||||
{
|
||||
InternalChild = textureAnimation = createTextureAnimation(state).With(animation =>
|
||||
{
|
||||
animation.Origin = animation.Anchor = Anchor.BottomLeft;
|
||||
animation.Scale = new Vector2(0.51f); // close enough to stable
|
||||
});
|
||||
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
Origin = Anchor = Anchor.BottomLeft;
|
||||
|
||||
// needs to be always present to prevent the animation clock consuming time spent when not present.
|
||||
AlwaysPresent = true;
|
||||
}
|
||||
|
||||
public bool Completed => !textureAnimation.IsPlaying || textureAnimation.PlaybackPosition >= textureAnimation.Duration;
|
||||
|
||||
public override void Show()
|
||||
{
|
||||
base.Show();
|
||||
DisplayTime = Time.Current;
|
||||
textureAnimation.Seek(0);
|
||||
}
|
||||
|
||||
protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes)
|
||||
{
|
||||
// assume that if the animation is playing on its own, it's independent from the beat and doesn't need to be touched.
|
||||
if (textureAnimation.FrameCount == 0 || textureAnimation.IsPlaying)
|
||||
return;
|
||||
|
||||
textureAnimation.GotoFrame(currentFrame);
|
||||
currentFrame = (currentFrame + 1) % textureAnimation.FrameCount;
|
||||
}
|
||||
|
||||
private static TextureAnimation createTextureAnimation(TaikoMascotAnimationState state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case TaikoMascotAnimationState.Clear:
|
||||
return new ClearMascotTextureAnimation();
|
||||
|
||||
case TaikoMascotAnimationState.Idle:
|
||||
case TaikoMascotAnimationState.Kiai:
|
||||
case TaikoMascotAnimationState.Fail:
|
||||
return new ManualMascotTextureAnimation(state);
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(state), $"Mascot animations for state {state} are not supported");
|
||||
}
|
||||
}
|
||||
|
||||
private class ManualMascotTextureAnimation : TextureAnimation
|
||||
{
|
||||
private readonly TaikoMascotAnimationState state;
|
||||
|
||||
public ManualMascotTextureAnimation(TaikoMascotAnimationState state)
|
||||
{
|
||||
this.state = state;
|
||||
|
||||
IsPlaying = false;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ISkinSource skin)
|
||||
{
|
||||
for (int frameIndex = 0; true; frameIndex++)
|
||||
{
|
||||
var texture = getAnimationFrame(skin, state, frameIndex);
|
||||
|
||||
if (texture == null)
|
||||
break;
|
||||
|
||||
AddFrame(texture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ClearMascotTextureAnimation : TextureAnimation
|
||||
{
|
||||
private const float clear_animation_speed = 1000 / 10f;
|
||||
|
||||
private static readonly int[] clear_animation_sequence = { 0, 1, 2, 3, 4, 5, 6, 5, 6, 5, 4, 3, 2, 1, 0 };
|
||||
|
||||
public ClearMascotTextureAnimation()
|
||||
{
|
||||
DefaultFrameLength = clear_animation_speed;
|
||||
Loop = false;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ISkinSource skin)
|
||||
{
|
||||
foreach (var frameIndex in clear_animation_sequence)
|
||||
{
|
||||
var texture = getAnimationFrame(skin, TaikoMascotAnimationState.Clear, frameIndex);
|
||||
|
||||
if (texture == null)
|
||||
// as per https://osu.ppy.sh/help/wiki/Skinning/osu!taiko#pippidon
|
||||
break;
|
||||
|
||||
AddFrame(texture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Texture getAnimationFrame(ISkin skin, TaikoMascotAnimationState state, int frameIndex)
|
||||
=> skin.GetTexture($"pippidon{state.ToString().ToLower()}{frameIndex}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.UI
|
||||
{
|
||||
public enum TaikoMascotAnimationState
|
||||
{
|
||||
Idle,
|
||||
Clear,
|
||||
Kiai,
|
||||
Fail
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user