1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-15 07:42:37 +08:00

Compare commits

..

433 Commits

330 changed files with 4363 additions and 2397 deletions
+2 -2
View File
@@ -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;
}
+75 -82
View File
@@ -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)
+1 -4
View File
@@ -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;
}
}
}
+52
View File
@@ -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;
+4 -1
View File
@@ -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();
@@ -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();
+8 -2
View File
@@ -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