Merge branch 'master' into dynamic-difficulty-icon

This commit is contained in:
Dean Herbert
2020-10-05 17:37:01 +09:00
committed by GitHub
148 changed files with 1526 additions and 716 deletions

View File

@ -52,6 +52,6 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.904.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2020.904.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.930.0" /> <PackageReference Include="ppy.osu.Framework.Android" Version="2020.1004.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -9,7 +9,7 @@ using osu.Framework.Android;
namespace osu.Android namespace osu.Android
{ {
[Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullSensor, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false)] [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false)]
public class OsuGameActivity : AndroidGameActivity public class OsuGameActivity : AndroidGameActivity
{ {
protected override Framework.Game CreateGame() => new OsuGameAndroid(); protected override Framework.Game CreateGame() => new OsuGameAndroid();

View File

@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods
public void TestDroplet(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new Droplet { StartTime = 1000 }), shouldMiss); public void TestDroplet(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new Droplet { StartTime = 1000 }), shouldMiss);
// We only care about testing misses, hits are tested via JuiceStream // We only care about testing misses, hits are tested via JuiceStream
[TestCase(false)] [TestCase(true)]
public void TestTinyDroplet(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new TinyDroplet { StartTime = 1000 }), shouldMiss); public void TestTinyDroplet(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new TinyDroplet { StartTime = 1000 }), shouldMiss);
} }
} }

View File

@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Catch.Tests
[Test] [Test]
public void TestCatchComboCounter() public void TestCatchComboCounter()
{ {
AddRepeatStep("perform hit", () => performJudgement(HitResult.Perfect), 20); AddRepeatStep("perform hit", () => performJudgement(HitResult.Great), 20);
AddStep("perform miss", () => performJudgement(HitResult.Miss)); AddStep("perform miss", () => performJudgement(HitResult.Miss));
AddStep("randomize judged object colour", () => AddStep("randomize judged object colour", () =>

View File

@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
{ {
mods = Score.Mods; mods = Score.Mods;
fruitsHit = Score.Statistics.GetOrDefault(HitResult.Perfect); fruitsHit = Score.Statistics.GetOrDefault(HitResult.Great);
ticksHit = Score.Statistics.GetOrDefault(HitResult.LargeTickHit); ticksHit = Score.Statistics.GetOrDefault(HitResult.LargeTickHit);
tinyTicksHit = Score.Statistics.GetOrDefault(HitResult.SmallTickHit); tinyTicksHit = Score.Statistics.GetOrDefault(HitResult.SmallTickHit);
tinyTicksMissed = Score.Statistics.GetOrDefault(HitResult.SmallTickMiss); tinyTicksMissed = Score.Statistics.GetOrDefault(HitResult.SmallTickMiss);

View File

@ -8,31 +8,7 @@ namespace osu.Game.Rulesets.Catch.Judgements
{ {
public class CatchBananaJudgement : CatchJudgement public class CatchBananaJudgement : CatchJudgement
{ {
public override bool AffectsCombo => false; public override HitResult MaxResult => HitResult.LargeBonus;
protected override int NumericResultFor(HitResult result)
{
switch (result)
{
default:
return 0;
case HitResult.Perfect:
return 1100;
}
}
protected override double HealthIncreaseFor(HitResult result)
{
switch (result)
{
default:
return 0;
case HitResult.Perfect:
return DEFAULT_MAX_HEALTH_INCREASE * 0.75;
}
}
public override bool ShouldExplodeFor(JudgementResult result) => true; public override bool ShouldExplodeFor(JudgementResult result) => true;
} }

View File

@ -7,16 +7,6 @@ namespace osu.Game.Rulesets.Catch.Judgements
{ {
public class CatchDropletJudgement : CatchJudgement public class CatchDropletJudgement : CatchJudgement
{ {
protected override int NumericResultFor(HitResult result) public override HitResult MaxResult => HitResult.LargeTickHit;
{
switch (result)
{
default:
return 0;
case HitResult.Perfect:
return 30;
}
}
} }
} }

View File

@ -9,19 +9,7 @@ namespace osu.Game.Rulesets.Catch.Judgements
{ {
public class CatchJudgement : Judgement public class CatchJudgement : Judgement
{ {
public override HitResult MaxResult => HitResult.Perfect; public override HitResult MaxResult => HitResult.Great;
protected override int NumericResultFor(HitResult result)
{
switch (result)
{
default:
return 0;
case HitResult.Perfect:
return 300;
}
}
/// <summary> /// <summary>
/// Whether fruit on the platter should explode or drop. /// Whether fruit on the platter should explode or drop.

View File

@ -7,30 +7,6 @@ namespace osu.Game.Rulesets.Catch.Judgements
{ {
public class CatchTinyDropletJudgement : CatchJudgement public class CatchTinyDropletJudgement : CatchJudgement
{ {
public override bool AffectsCombo => false; public override HitResult MaxResult => HitResult.SmallTickHit;
protected override int NumericResultFor(HitResult result)
{
switch (result)
{
default:
return 0;
case HitResult.Perfect:
return 10;
}
}
protected override double HealthIncreaseFor(HitResult result)
{
switch (result)
{
default:
return 0;
case HitResult.Perfect:
return 0.02;
}
}
} }
} }

View File

@ -8,7 +8,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Catch.UI;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
@ -86,7 +85,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
if (CheckPosition == null) return; if (CheckPosition == null) return;
if (timeOffset >= 0 && Result != null) if (timeOffset >= 0 && Result != null)
ApplyResult(r => r.Type = CheckPosition.Invoke(HitObject) ? HitResult.Perfect : HitResult.Miss); ApplyResult(r => r.Type = CheckPosition.Invoke(HitObject) ? r.Judgement.MaxResult : r.Judgement.MinResult);
} }
protected override void UpdateStateTransforms(ArmedState state) protected override void UpdateStateTransforms(ArmedState state)

View File

@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Catch.Scoring
{ {
switch (result) switch (result)
{ {
case HitResult.Perfect: case HitResult.Great:
case HitResult.Miss: case HitResult.Miss:
return true; return true;
} }

View File

@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Catch.Scoring
{ {
public class CatchScoreProcessor : ScoreProcessor public class CatchScoreProcessor : ScoreProcessor
{ {
public override HitWindows CreateHitWindows() => new CatchHitWindows();
} }
} }

View File

@ -33,10 +33,10 @@ namespace osu.Game.Rulesets.Catch.UI
public void OnNewResult(DrawableCatchHitObject judgedObject, JudgementResult result) public void OnNewResult(DrawableCatchHitObject judgedObject, JudgementResult result)
{ {
if (!result.Judgement.AffectsCombo || !result.HasResult) if (!result.Type.AffectsCombo() || !result.HasResult)
return; return;
if (result.Type == HitResult.Miss) if (!result.IsHit)
{ {
updateCombo(0, null); updateCombo(0, null);
return; return;
@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Catch.UI
public void OnRevertResult(DrawableCatchHitObject judgedObject, JudgementResult result) public void OnRevertResult(DrawableCatchHitObject judgedObject, JudgementResult result)
{ {
if (!result.Judgement.AffectsCombo || !result.HasResult) if (!result.Type.AffectsCombo() || !result.HasResult)
return; return;
updateCombo(result.ComboAtJudgement, judgedObject.AccentColour.Value); updateCombo(result.ComboAtJudgement, judgedObject.AccentColour.Value);

View File

@ -11,6 +11,7 @@ using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Rulesets.Catch.Replays; using osu.Game.Rulesets.Catch.Replays;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osuTK; using osuTK;
@ -52,7 +53,7 @@ namespace osu.Game.Rulesets.Catch.UI
public void OnNewResult(DrawableCatchHitObject fruit, JudgementResult result) public void OnNewResult(DrawableCatchHitObject fruit, JudgementResult result)
{ {
if (result.Judgement is IgnoreJudgement) if (!result.Type.IsScorable())
return; return;
void runAfterLoaded(Action action) void runAfterLoaded(Action action)

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Linq; using System.Linq;
using NUnit.Framework;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
@ -13,7 +14,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
{ {
public class TestSceneHoldNote : ManiaHitObjectTestScene public class TestSceneHoldNote : ManiaHitObjectTestScene
{ {
public TestSceneHoldNote() [Test]
public void TestHoldNote()
{ {
AddToggleStep("toggle hitting", v => AddToggleStep("toggle hitting", v =>
{ {

View File

@ -45,9 +45,9 @@ namespace osu.Game.Rulesets.Mania.Tests
}); });
assertHeadJudgement(HitResult.Miss); assertHeadJudgement(HitResult.Miss);
assertTickJudgement(HitResult.Miss); assertTickJudgement(HitResult.LargeTickMiss);
assertTailJudgement(HitResult.Miss); assertTailJudgement(HitResult.Miss);
assertNoteJudgement(HitResult.Perfect); assertNoteJudgement(HitResult.IgnoreHit);
} }
/// <summary> /// <summary>
@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Mania.Tests
}); });
assertHeadJudgement(HitResult.Miss); assertHeadJudgement(HitResult.Miss);
assertTickJudgement(HitResult.Miss); assertTickJudgement(HitResult.LargeTickMiss);
assertTailJudgement(HitResult.Miss); assertTailJudgement(HitResult.Miss);
} }
@ -82,7 +82,7 @@ namespace osu.Game.Rulesets.Mania.Tests
}); });
assertHeadJudgement(HitResult.Miss); assertHeadJudgement(HitResult.Miss);
assertTickJudgement(HitResult.Miss); assertTickJudgement(HitResult.LargeTickMiss);
assertTailJudgement(HitResult.Miss); assertTailJudgement(HitResult.Miss);
} }
@ -102,7 +102,7 @@ namespace osu.Game.Rulesets.Mania.Tests
}); });
assertHeadJudgement(HitResult.Perfect); assertHeadJudgement(HitResult.Perfect);
assertTickJudgement(HitResult.Perfect); assertTickJudgement(HitResult.LargeTickHit);
assertTailJudgement(HitResult.Miss); assertTailJudgement(HitResult.Miss);
} }
@ -122,7 +122,7 @@ namespace osu.Game.Rulesets.Mania.Tests
}); });
assertHeadJudgement(HitResult.Perfect); assertHeadJudgement(HitResult.Perfect);
assertTickJudgement(HitResult.Perfect); assertTickJudgement(HitResult.LargeTickHit);
assertTailJudgement(HitResult.Perfect); assertTailJudgement(HitResult.Perfect);
} }
@ -141,7 +141,7 @@ namespace osu.Game.Rulesets.Mania.Tests
}); });
assertHeadJudgement(HitResult.Perfect); assertHeadJudgement(HitResult.Perfect);
assertTickJudgement(HitResult.Miss); assertTickJudgement(HitResult.LargeTickMiss);
assertTailJudgement(HitResult.Miss); assertTailJudgement(HitResult.Miss);
} }
@ -161,7 +161,7 @@ namespace osu.Game.Rulesets.Mania.Tests
}); });
assertHeadJudgement(HitResult.Perfect); assertHeadJudgement(HitResult.Perfect);
assertTickJudgement(HitResult.Perfect); assertTickJudgement(HitResult.LargeTickHit);
assertTailJudgement(HitResult.Miss); assertTailJudgement(HitResult.Miss);
} }
@ -181,7 +181,7 @@ namespace osu.Game.Rulesets.Mania.Tests
}); });
assertHeadJudgement(HitResult.Perfect); assertHeadJudgement(HitResult.Perfect);
assertTickJudgement(HitResult.Perfect); assertTickJudgement(HitResult.LargeTickHit);
assertTailJudgement(HitResult.Meh); assertTailJudgement(HitResult.Meh);
} }
@ -199,7 +199,7 @@ namespace osu.Game.Rulesets.Mania.Tests
}); });
assertHeadJudgement(HitResult.Miss); assertHeadJudgement(HitResult.Miss);
assertTickJudgement(HitResult.Perfect); assertTickJudgement(HitResult.LargeTickHit);
assertTailJudgement(HitResult.Miss); assertTailJudgement(HitResult.Miss);
} }
@ -217,7 +217,7 @@ namespace osu.Game.Rulesets.Mania.Tests
}); });
assertHeadJudgement(HitResult.Miss); assertHeadJudgement(HitResult.Miss);
assertTickJudgement(HitResult.Perfect); assertTickJudgement(HitResult.LargeTickHit);
assertTailJudgement(HitResult.Meh); assertTailJudgement(HitResult.Meh);
} }
@ -235,7 +235,7 @@ namespace osu.Game.Rulesets.Mania.Tests
}); });
assertHeadJudgement(HitResult.Miss); assertHeadJudgement(HitResult.Miss);
assertTickJudgement(HitResult.Miss); assertTickJudgement(HitResult.LargeTickMiss);
assertTailJudgement(HitResult.Meh); assertTailJudgement(HitResult.Meh);
} }
@ -280,10 +280,10 @@ namespace osu.Game.Rulesets.Mania.Tests
}, beatmap); }, beatmap);
AddAssert("first hold note missed", () => judgementResults.Where(j => beatmap.HitObjects[0].NestedHitObjects.Contains(j.HitObject)) AddAssert("first hold note missed", () => judgementResults.Where(j => beatmap.HitObjects[0].NestedHitObjects.Contains(j.HitObject))
.All(j => j.Type == HitResult.Miss)); .All(j => !j.Type.IsHit()));
AddAssert("second hold note missed", () => judgementResults.Where(j => beatmap.HitObjects[1].NestedHitObjects.Contains(j.HitObject)) AddAssert("second hold note missed", () => judgementResults.Where(j => beatmap.HitObjects[1].NestedHitObjects.Contains(j.HitObject))
.All(j => j.Type == HitResult.Perfect)); .All(j => j.Type.IsHit()));
} }
private void assertHeadJudgement(HitResult result) private void assertHeadJudgement(HitResult result)

View File

@ -28,25 +28,33 @@ namespace osu.Game.Rulesets.Mania.Tests
[TestFixture] [TestFixture]
public class TestSceneNotes : OsuTestScene public class TestSceneNotes : OsuTestScene
{ {
[BackgroundDependencyLoader] [Test]
private void load() public void TestVariousNotes()
{ {
Child = new FillFlowContainer DrawableNote note1 = null;
DrawableNote note2 = null;
DrawableHoldNote holdNote1 = null;
DrawableHoldNote holdNote2 = null;
AddStep("create notes", () =>
{ {
Clock = new FramedClock(new ManualClock()), Child = new FillFlowContainer
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(20),
Children = new[]
{ {
createNoteDisplay(ScrollingDirection.Down, 1, out var note1), Clock = new FramedClock(new ManualClock()),
createNoteDisplay(ScrollingDirection.Up, 2, out var note2), Anchor = Anchor.Centre,
createHoldNoteDisplay(ScrollingDirection.Down, 1, out var holdNote1), Origin = Anchor.Centre,
createHoldNoteDisplay(ScrollingDirection.Up, 2, out var holdNote2), AutoSizeAxes = Axes.Both,
} Direction = FillDirection.Horizontal,
}; Spacing = new Vector2(20),
Children = new[]
{
createNoteDisplay(ScrollingDirection.Down, 1, out note1),
createNoteDisplay(ScrollingDirection.Up, 2, out note2),
createHoldNoteDisplay(ScrollingDirection.Down, 1, out holdNote1),
createHoldNoteDisplay(ScrollingDirection.Up, 2, out holdNote2),
}
};
});
AddAssert("note 1 facing downwards", () => verifyAnchors(note1, Anchor.y2)); AddAssert("note 1 facing downwards", () => verifyAnchors(note1, Anchor.y2));
AddAssert("note 2 facing upwards", () => verifyAnchors(note2, Anchor.y0)); AddAssert("note 2 facing upwards", () => verifyAnchors(note2, Anchor.y0));

View File

@ -7,18 +7,6 @@ namespace osu.Game.Rulesets.Mania.Judgements
{ {
public class HoldNoteTickJudgement : ManiaJudgement public class HoldNoteTickJudgement : ManiaJudgement
{ {
protected override int NumericResultFor(HitResult result) => result == MaxResult ? 20 : 0; public override HitResult MaxResult => HitResult.LargeTickHit;
protected override double HealthIncreaseFor(HitResult result)
{
switch (result)
{
default:
return 0;
case HitResult.Perfect:
return 0.01;
}
}
} }
} }

View File

@ -8,27 +8,33 @@ namespace osu.Game.Rulesets.Mania.Judgements
{ {
public class ManiaJudgement : Judgement public class ManiaJudgement : Judgement
{ {
protected override int NumericResultFor(HitResult result) protected override double HealthIncreaseFor(HitResult result)
{ {
switch (result) switch (result)
{ {
default: case HitResult.LargeTickHit:
return 0; return DEFAULT_MAX_HEALTH_INCREASE * 0.1;
case HitResult.LargeTickMiss:
return -DEFAULT_MAX_HEALTH_INCREASE * 0.1;
case HitResult.Meh: case HitResult.Meh:
return 50; return -DEFAULT_MAX_HEALTH_INCREASE * 0.5;
case HitResult.Ok: case HitResult.Ok:
return 100; return -DEFAULT_MAX_HEALTH_INCREASE * 0.3;
case HitResult.Good: case HitResult.Good:
return 200; return DEFAULT_MAX_HEALTH_INCREASE * 0.1;
case HitResult.Great: case HitResult.Great:
return 300; return DEFAULT_MAX_HEALTH_INCREASE * 0.8;
case HitResult.Perfect: case HitResult.Perfect:
return 350; return DEFAULT_MAX_HEALTH_INCREASE;
default:
return base.HealthIncreaseFor(result);
} }
} }
} }

View File

@ -239,11 +239,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{ {
if (Tail.AllJudged) if (Tail.AllJudged)
{ {
ApplyResult(r => r.Type = HitResult.Perfect); ApplyResult(r => r.Type = r.Judgement.MaxResult);
endHold(); endHold();
} }
if (Tail.Result.Type == HitResult.Miss) if (Tail.Judged && !Tail.IsHit)
HasBroken = true; HasBroken = true;
} }

View File

@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
if (!userTriggered) if (!userTriggered)
{ {
if (!HitObject.HitWindows.CanBeHit(timeOffset)) if (!HitObject.HitWindows.CanBeHit(timeOffset))
ApplyResult(r => r.Type = HitResult.Miss); ApplyResult(r => r.Type = r.Judgement.MinResult);
return; return;
} }

View File

@ -8,7 +8,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Objects.Drawables namespace osu.Game.Rulesets.Mania.Objects.Drawables
{ {
@ -17,6 +16,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
/// </summary> /// </summary>
public class DrawableHoldNoteTick : DrawableManiaHitObject<HoldNoteTick> public class DrawableHoldNoteTick : DrawableManiaHitObject<HoldNoteTick>
{ {
public override bool DisplayResult => false;
/// <summary> /// <summary>
/// References the time at which the user started holding the hold note. /// References the time at which the user started holding the hold note.
/// </summary> /// </summary>
@ -73,9 +74,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
var startTime = HoldStartTime?.Invoke(); var startTime = HoldStartTime?.Invoke();
if (startTime == null || startTime > HitObject.StartTime) if (startTime == null || startTime > HitObject.StartTime)
ApplyResult(r => r.Type = HitResult.Miss); ApplyResult(r => r.Type = r.Judgement.MinResult);
else else
ApplyResult(r => r.Type = HitResult.Perfect); ApplyResult(r => r.Type = r.Judgement.MaxResult);
} }
} }
} }

View File

@ -9,7 +9,6 @@ using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Objects.Drawables namespace osu.Game.Rulesets.Mania.Objects.Drawables
{ {
@ -136,7 +135,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
/// <summary> /// <summary>
/// Causes this <see cref="DrawableManiaHitObject"/> to get missed, disregarding all conditions in implementations of <see cref="DrawableHitObject.CheckForResult"/>. /// Causes this <see cref="DrawableManiaHitObject"/> to get missed, disregarding all conditions in implementations of <see cref="DrawableHitObject.CheckForResult"/>.
/// </summary> /// </summary>
public void MissForcefully() => ApplyResult(r => r.Type = HitResult.Miss); public void MissForcefully() => ApplyResult(r => r.Type = r.Judgement.MinResult);
} }
public abstract class DrawableManiaHitObject<TObject> : DrawableManiaHitObject public abstract class DrawableManiaHitObject<TObject> : DrawableManiaHitObject

View File

@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
if (!userTriggered) if (!userTriggered)
{ {
if (!HitObject.HitWindows.CanBeHit(timeOffset)) if (!HitObject.HitWindows.CanBeHit(timeOffset))
ApplyResult(r => r.Type = HitResult.Miss); ApplyResult(r => r.Type = r.Judgement.MinResult);
return; return;
} }

View File

@ -10,7 +10,5 @@ namespace osu.Game.Rulesets.Mania.Scoring
protected override double DefaultAccuracyPortion => 0.95; protected override double DefaultAccuracyPortion => 0.95;
protected override double DefaultComboPortion => 0.05; protected override double DefaultComboPortion => 0.05;
public override HitWindows CreateHitWindows() => new ManiaHitWindows();
} }
} }

View File

@ -130,6 +130,9 @@ namespace osu.Game.Rulesets.Mania.Skinning
private Drawable getResult(HitResult result) private Drawable getResult(HitResult result)
{ {
if (!hitresult_mapping.ContainsKey(result))
return null;
string filename = this.GetManiaSkinConfig<string>(hitresult_mapping[result])?.Value string filename = this.GetManiaSkinConfig<string>(hitresult_mapping[result])?.Value
?? default_hitresult_skin_filenames[result]; ?? default_hitresult_skin_filenames[result];

View File

@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Mania.UI
if (result.IsHit) if (result.IsHit)
hitPolicy.HandleHit(judgedObject); hitPolicy.HandleHit(judgedObject);
if (!result.IsHit || !judgedObject.DisplayResult || !DisplayJudgements.Value) if (!result.IsHit || !DisplayJudgements.Value)
return; return;
HitObjectArea.Explosions.Add(hitExplosionPool.Get(e => e.Apply(result))); HitObjectArea.Explosions.Add(hitExplosionPool.Get(e => e.Apply(result)));

View File

@ -20,7 +20,8 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
private int depthIndex; private int depthIndex;
public TestSceneHitCircle() [Test]
public void TestVariousHitCircles()
{ {
AddStep("Miss Big Single", () => SetContents(() => testSingle(2))); AddStep("Miss Big Single", () => SetContents(() => testSingle(2)));
AddStep("Miss Medium Single", () => SetContents(() => testSingle(5))); AddStep("Miss Medium Single", () => SetContents(() => testSingle(5)));

View File

@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
HitObjects = { new HitCircle { Position = new Vector2(256, 192) } } HitObjects = { new HitCircle { Position = new Vector2(256, 192) } }
}, },
PassCondition = () => Player.Results.Count > 0 && Player.Results[0].TimeOffset < -hitWindows.WindowFor(HitResult.Meh) && Player.Results[0].Type == HitResult.Miss PassCondition = () => Player.Results.Count > 0 && Player.Results[0].TimeOffset < -hitWindows.WindowFor(HitResult.Meh) && !Player.Results[0].IsHit
}); });
} }
@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
Autoplay = false, Autoplay = false,
Beatmap = beatmap, Beatmap = beatmap,
PassCondition = () => Player.Results.Count > 0 && Player.Results[0].TimeOffset >= hitWindows.WindowFor(HitResult.Meh) && Player.Results[0].Type == HitResult.Miss PassCondition = () => Player.Results.Count > 0 && Player.Results[0].TimeOffset >= hitWindows.WindowFor(HitResult.Meh) && !Player.Results[0].IsHit
}); });
} }

View File

@ -209,9 +209,9 @@ namespace osu.Game.Rulesets.Osu.Tests
}); });
addJudgementAssert(hitObjects[0], HitResult.Great); addJudgementAssert(hitObjects[0], HitResult.Great);
addJudgementAssert(hitObjects[1], HitResult.Great); addJudgementAssert(hitObjects[1], HitResult.IgnoreHit);
addJudgementAssert("slider head", () => ((Slider)hitObjects[1]).HeadCircle, HitResult.Miss); addJudgementAssert("slider head", () => ((Slider)hitObjects[1]).HeadCircle, HitResult.Miss);
addJudgementAssert("slider tick", () => ((Slider)hitObjects[1]).NestedHitObjects[1] as SliderTick, HitResult.Great); addJudgementAssert("slider tick", () => ((Slider)hitObjects[1]).NestedHitObjects[1] as SliderTick, HitResult.LargeTickHit);
} }
/// <summary> /// <summary>
@ -252,9 +252,9 @@ namespace osu.Game.Rulesets.Osu.Tests
}); });
addJudgementAssert(hitObjects[0], HitResult.Great); addJudgementAssert(hitObjects[0], HitResult.Great);
addJudgementAssert(hitObjects[1], HitResult.Great); addJudgementAssert(hitObjects[1], HitResult.IgnoreHit);
addJudgementAssert("slider head", () => ((Slider)hitObjects[1]).HeadCircle, HitResult.Great); addJudgementAssert("slider head", () => ((Slider)hitObjects[1]).HeadCircle, HitResult.Great);
addJudgementAssert("slider tick", () => ((Slider)hitObjects[1]).NestedHitObjects[1] as SliderTick, HitResult.Great); addJudgementAssert("slider tick", () => ((Slider)hitObjects[1]).NestedHitObjects[1] as SliderTick, HitResult.LargeTickHit);
} }
/// <summary> /// <summary>
@ -331,7 +331,7 @@ namespace osu.Game.Rulesets.Osu.Tests
}); });
addJudgementAssert(hitObjects[0], HitResult.Great); addJudgementAssert(hitObjects[0], HitResult.Great);
addJudgementAssert(hitObjects[1], HitResult.Great); addJudgementAssert(hitObjects[1], HitResult.IgnoreHit);
} }
private void addJudgementAssert(OsuHitObject hitObject, HitResult result) private void addJudgementAssert(OsuHitObject hitObject, HitResult result)

View File

@ -27,7 +27,8 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
private int depthIndex; private int depthIndex;
public TestSceneSlider() [Test]
public void TestVariousSliders()
{ {
AddStep("Big Single", () => SetContents(() => testSimpleBig())); AddStep("Big Single", () => SetContents(() => testSimpleBig()));
AddStep("Medium Single", () => SetContents(() => testSimpleMedium())); AddStep("Medium Single", () => SetContents(() => testSimpleMedium()));
@ -164,7 +165,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
var slider = new Slider var slider = new Slider
{ {
StartTime = Time.Current + 1000, StartTime = Time.Current + time_offset,
Position = new Vector2(239, 176), Position = new Vector2(239, 176),
Path = new SliderPath(PathType.PerfectCurve, new[] Path = new SliderPath(PathType.PerfectCurve, new[]
{ {
@ -185,22 +186,26 @@ namespace osu.Game.Rulesets.Osu.Tests
private Drawable testSlowSpeed() => createSlider(speedMultiplier: 0.5); private Drawable testSlowSpeed() => createSlider(speedMultiplier: 0.5);
private Drawable testShortSlowSpeed(int repeats = 0) => createSlider(distance: 100, repeats: repeats, speedMultiplier: 0.5); private Drawable testShortSlowSpeed(int repeats = 0) => createSlider(distance: max_length / 4, repeats: repeats, speedMultiplier: 0.5);
private Drawable testHighSpeed(int repeats = 0) => createSlider(repeats: repeats, speedMultiplier: 15); private Drawable testHighSpeed(int repeats = 0) => createSlider(repeats: repeats, speedMultiplier: 15);
private Drawable testShortHighSpeed(int repeats = 0) => createSlider(distance: 100, repeats: repeats, speedMultiplier: 15); private Drawable testShortHighSpeed(int repeats = 0) => createSlider(distance: max_length / 4, repeats: repeats, speedMultiplier: 15);
private Drawable createSlider(float circleSize = 2, float distance = 400, int repeats = 0, double speedMultiplier = 2, int stackHeight = 0) private const double time_offset = 1500;
private const float max_length = 200;
private Drawable createSlider(float circleSize = 2, float distance = max_length, int repeats = 0, double speedMultiplier = 2, int stackHeight = 0)
{ {
var slider = new Slider var slider = new Slider
{ {
StartTime = Time.Current + 1000, StartTime = Time.Current + time_offset,
Position = new Vector2(-(distance / 2), 0), Position = new Vector2(0, -(distance / 2)),
Path = new SliderPath(PathType.PerfectCurve, new[] Path = new SliderPath(PathType.PerfectCurve, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(distance, 0), new Vector2(0, distance),
}, distance), }, distance),
RepeatCount = repeats, RepeatCount = repeats,
StackHeight = stackHeight StackHeight = stackHeight
@ -213,14 +218,14 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
var slider = new Slider var slider = new Slider
{ {
StartTime = Time.Current + 1000, StartTime = Time.Current + time_offset,
Position = new Vector2(-200, 0), Position = new Vector2(-max_length / 2, 0),
Path = new SliderPath(PathType.PerfectCurve, new[] Path = new SliderPath(PathType.PerfectCurve, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(200, 200), new Vector2(max_length / 2, max_length / 2),
new Vector2(400, 0) new Vector2(max_length, 0)
}, 600), }, max_length * 1.5f),
RepeatCount = repeats, RepeatCount = repeats,
}; };
@ -233,16 +238,16 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
var slider = new Slider var slider = new Slider
{ {
StartTime = Time.Current + 1000, StartTime = Time.Current + time_offset,
Position = new Vector2(-200, 0), Position = new Vector2(-max_length / 2, 0),
Path = new SliderPath(PathType.Linear, new[] Path = new SliderPath(PathType.Linear, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(150, 75), new Vector2(max_length * 0.375f, max_length * 0.18f),
new Vector2(200, 0), new Vector2(max_length / 2, 0),
new Vector2(300, -200), new Vector2(max_length * 0.75f, -max_length / 2),
new Vector2(400, 0), new Vector2(max_length * 0.95f, 0),
new Vector2(430, 0) new Vector2(max_length, 0)
}), }),
RepeatCount = repeats, RepeatCount = repeats,
}; };
@ -256,15 +261,15 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
var slider = new Slider var slider = new Slider
{ {
StartTime = Time.Current + 1000, StartTime = Time.Current + time_offset,
Position = new Vector2(-200, 0), Position = new Vector2(-max_length / 2, 0),
Path = new SliderPath(PathType.Bezier, new[] Path = new SliderPath(PathType.Bezier, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(150, 75), new Vector2(max_length * 0.375f, max_length * 0.18f),
new Vector2(200, 100), new Vector2(max_length / 2, max_length / 4),
new Vector2(300, -200), new Vector2(max_length * 0.75f, -max_length / 2),
new Vector2(430, 0) new Vector2(max_length, 0)
}), }),
RepeatCount = repeats, RepeatCount = repeats,
}; };
@ -278,16 +283,16 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
var slider = new Slider var slider = new Slider
{ {
StartTime = Time.Current + 1000, StartTime = Time.Current + time_offset,
Position = new Vector2(0, 0), Position = new Vector2(0, 0),
Path = new SliderPath(PathType.Linear, new[] Path = new SliderPath(PathType.Linear, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(-200, 0), new Vector2(-max_length / 2, 0),
new Vector2(0, 0), new Vector2(0, 0),
new Vector2(0, -200), new Vector2(0, -max_length / 2),
new Vector2(-200, -200), new Vector2(-max_length / 2, -max_length / 2),
new Vector2(0, -200) new Vector2(0, -max_length / 2)
}), }),
RepeatCount = repeats, RepeatCount = repeats,
}; };
@ -305,14 +310,14 @@ namespace osu.Game.Rulesets.Osu.Tests
var slider = new Slider var slider = new Slider
{ {
StartTime = Time.Current + 1000, StartTime = Time.Current + time_offset,
Position = new Vector2(-100, 0), Position = new Vector2(-max_length / 4, 0),
Path = new SliderPath(PathType.Catmull, new[] Path = new SliderPath(PathType.Catmull, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(50, -50), new Vector2(max_length * 0.125f, max_length * 0.125f),
new Vector2(150, 50), new Vector2(max_length * 0.375f, max_length * 0.125f),
new Vector2(200, 0) new Vector2(max_length / 2, 0)
}), }),
RepeatCount = repeats, RepeatCount = repeats,
NodeSamples = repeatSamples NodeSamples = repeatSamples

View File

@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Osu.Tests
new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.RightButton }, Time = time_during_slide_2 }, new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.RightButton }, Time = time_during_slide_2 },
}); });
AddAssert("Tracking retained", assertGreatJudge); AddAssert("Tracking retained", assertMaxJudge);
} }
/// <summary> /// <summary>
@ -94,7 +94,7 @@ namespace osu.Game.Rulesets.Osu.Tests
new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.RightButton }, Time = time_during_slide_1 }, new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.RightButton }, Time = time_during_slide_1 },
}); });
AddAssert("Tracking retained", assertGreatJudge); AddAssert("Tracking retained", assertMaxJudge);
} }
/// <summary> /// <summary>
@ -115,7 +115,7 @@ namespace osu.Game.Rulesets.Osu.Tests
new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.RightButton }, Time = time_during_slide_1 }, new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.RightButton }, Time = time_during_slide_1 },
}); });
AddAssert("Tracking retained", assertGreatJudge); AddAssert("Tracking retained", assertMaxJudge);
} }
/// <summary> /// <summary>
@ -288,7 +288,7 @@ namespace osu.Game.Rulesets.Osu.Tests
new OsuReplayFrame { Position = new Vector2(slider_path_length, OsuHitObject.OBJECT_RADIUS * 1.199f), Actions = { OsuAction.LeftButton }, Time = time_slider_end }, new OsuReplayFrame { Position = new Vector2(slider_path_length, OsuHitObject.OBJECT_RADIUS * 1.199f), Actions = { OsuAction.LeftButton }, Time = time_slider_end },
}); });
AddAssert("Tracking kept", assertGreatJudge); AddAssert("Tracking kept", assertMaxJudge);
} }
/// <summary> /// <summary>
@ -312,13 +312,13 @@ namespace osu.Game.Rulesets.Osu.Tests
AddAssert("Tracking dropped", assertMidSliderJudgementFail); AddAssert("Tracking dropped", assertMidSliderJudgementFail);
} }
private bool assertGreatJudge() => judgementResults.Any() && judgementResults.All(t => t.Type == HitResult.Great); private bool assertMaxJudge() => judgementResults.Any() && judgementResults.All(t => t.Type == t.Judgement.MaxResult);
private bool assertHeadMissTailTracked() => judgementResults[^2].Type == HitResult.Great && judgementResults.First().Type == HitResult.Miss; private bool assertHeadMissTailTracked() => judgementResults[^2].Type == HitResult.SmallTickHit && !judgementResults.First().IsHit;
private bool assertMidSliderJudgements() => judgementResults[^2].Type == HitResult.Great; private bool assertMidSliderJudgements() => judgementResults[^2].Type == HitResult.SmallTickHit;
private bool assertMidSliderJudgementFail() => judgementResults[^2].Type == HitResult.Miss; private bool assertMidSliderJudgementFail() => judgementResults[^2].Type == HitResult.SmallTickMiss;
private ScoreAccessibleReplayPlayer currentPlayer; private ScoreAccessibleReplayPlayer currentPlayer;

View File

@ -145,7 +145,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
// multipled by 2 to nullify the score multiplier. (autoplay mod selected) // multipled by 2 to nullify the score multiplier. (autoplay mod selected)
var totalScore = ((ScoreExposedPlayer)Player).ScoreProcessor.TotalScore.Value * 2; var totalScore = ((ScoreExposedPlayer)Player).ScoreProcessor.TotalScore.Value * 2;
return totalScore == (int)(drawableSpinner.RotationTracker.RateAdjustedRotation / 360) * SpinnerTick.SCORE_PER_TICK; return totalScore == (int)(drawableSpinner.RotationTracker.RateAdjustedRotation / 360) * new SpinnerTick().CreateJudgement().MaxNumericResult;
}); });
addSeekStep(0); addSeekStep(0);

View File

@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
private double accuracy; private double accuracy;
private int scoreMaxCombo; private int scoreMaxCombo;
private int countGreat; private int countGreat;
private int countGood; private int countOk;
private int countMeh; private int countMeh;
private int countMiss; private int countMiss;
@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
accuracy = Score.Accuracy; accuracy = Score.Accuracy;
scoreMaxCombo = Score.MaxCombo; scoreMaxCombo = Score.MaxCombo;
countGreat = Score.Statistics.GetOrDefault(HitResult.Great); countGreat = Score.Statistics.GetOrDefault(HitResult.Great);
countGood = Score.Statistics.GetOrDefault(HitResult.Good); countOk = Score.Statistics.GetOrDefault(HitResult.Ok);
countMeh = Score.Statistics.GetOrDefault(HitResult.Meh); countMeh = Score.Statistics.GetOrDefault(HitResult.Meh);
countMiss = Score.Statistics.GetOrDefault(HitResult.Miss); countMiss = Score.Statistics.GetOrDefault(HitResult.Miss);
@ -181,7 +181,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
int amountHitObjectsWithAccuracy = countHitCircles; int amountHitObjectsWithAccuracy = countHitCircles;
if (amountHitObjectsWithAccuracy > 0) if (amountHitObjectsWithAccuracy > 0)
betterAccuracyPercentage = ((countGreat - (totalHits - amountHitObjectsWithAccuracy)) * 6 + countGood * 2 + countMeh) / (double)(amountHitObjectsWithAccuracy * 6); betterAccuracyPercentage = ((countGreat - (totalHits - amountHitObjectsWithAccuracy)) * 6 + countOk * 2 + countMeh) / (double)(amountHitObjectsWithAccuracy * 6);
else else
betterAccuracyPercentage = 0; betterAccuracyPercentage = 0;
@ -204,7 +204,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
return accuracyValue; return accuracyValue;
} }
private int totalHits => countGreat + countGood + countMeh + countMiss; private int totalHits => countGreat + countOk + countMeh + countMiss;
private int totalSuccessfulHits => countGreat + countGood + countMeh; private int totalSuccessfulHits => countGreat + countOk + countMeh;
} }
} }

View File

@ -7,10 +7,6 @@ namespace osu.Game.Rulesets.Osu.Judgements
{ {
public class OsuIgnoreJudgement : OsuJudgement public class OsuIgnoreJudgement : OsuJudgement
{ {
public override bool AffectsCombo => false; public override HitResult MaxResult => HitResult.IgnoreHit;
protected override int NumericResultFor(HitResult result) => 0;
protected override double HealthIncreaseFor(HitResult result) => 0;
} }
} }

View File

@ -9,23 +9,5 @@ namespace osu.Game.Rulesets.Osu.Judgements
public class OsuJudgement : Judgement public class OsuJudgement : Judgement
{ {
public override HitResult MaxResult => HitResult.Great; public override HitResult MaxResult => HitResult.Great;
protected override int NumericResultFor(HitResult result)
{
switch (result)
{
default:
return 0;
case HitResult.Meh:
return 50;
case HitResult.Good:
return 100;
case HitResult.Great:
return 300;
}
}
} }
} }

View File

@ -125,7 +125,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
if (!userTriggered) if (!userTriggered)
{ {
if (!HitObject.HitWindows.CanBeHit(timeOffset)) if (!HitObject.HitWindows.CanBeHit(timeOffset))
ApplyResult(r => r.Type = HitResult.Miss); ApplyResult(r => r.Type = r.Judgement.MinResult);
return; return;
} }
@ -143,7 +143,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
var circleResult = (OsuHitCircleJudgementResult)r; var circleResult = (OsuHitCircleJudgementResult)r;
// Todo: This should also consider misses, but they're a little more interesting to handle, since we don't necessarily know the position at the time of a miss. // Todo: This should also consider misses, but they're a little more interesting to handle, since we don't necessarily know the position at the time of a miss.
if (result != HitResult.Miss) if (result.IsHit())
{ {
var localMousePosition = ToLocalSpace(inputManager.CurrentState.Mouse.Position); var localMousePosition = ToLocalSpace(inputManager.CurrentState.Mouse.Position);
circleResult.CursorPositionAtHit = HitObject.StackedPosition + (localMousePosition - DrawSize / 2); circleResult.CursorPositionAtHit = HitObject.StackedPosition + (localMousePosition - DrawSize / 2);

View File

@ -8,7 +8,6 @@ using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects.Drawables namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
@ -68,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
/// <summary> /// <summary>
/// Causes this <see cref="DrawableOsuHitObject"/> to get missed, disregarding all conditions in implementations of <see cref="DrawableHitObject.CheckForResult"/>. /// Causes this <see cref="DrawableOsuHitObject"/> to get missed, disregarding all conditions in implementations of <see cref="DrawableHitObject.CheckForResult"/>.
/// </summary> /// </summary>
public void MissForcefully() => ApplyResult(r => r.Type = HitResult.Miss); public void MissForcefully() => ApplyResult(r => r.Type = r.Judgement.MinResult);
protected override JudgementResult CreateResult(Judgement judgement) => new OsuJudgementResult(HitObject, judgement); protected override JudgementResult CreateResult(Judgement judgement) => new OsuJudgementResult(HitObject, judgement);
} }

View File

@ -8,7 +8,6 @@ using osu.Game.Configuration;
using osuTK; using osuTK;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning; using osu.Game.Skinning;
using osuTK.Graphics; using osuTK.Graphics;
@ -67,7 +66,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
if (JudgedObject != null) if (JudgedObject != null)
{ {
lightingColour = JudgedObject.AccentColour.GetBoundCopy(); lightingColour = JudgedObject.AccentColour.GetBoundCopy();
lightingColour.BindValueChanged(colour => Lighting.Colour = Result.Type == HitResult.Miss ? Color4.Transparent : colour.NewValue, true); lightingColour.BindValueChanged(colour => Lighting.Colour = Result.IsHit ? colour.NewValue : Color4.Transparent, true);
} }
else else
{ {

View File

@ -13,7 +13,6 @@ using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Skinning; using osu.Game.Rulesets.Osu.Skinning;
using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.Scoring;
using osuTK.Graphics; using osuTK.Graphics;
using osu.Game.Skinning; using osu.Game.Skinning;
@ -110,6 +109,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
} }
} }
public override void StopAllSamples()
{
base.StopAllSamples();
slidingSample?.Stop();
}
private void updateSlidingSample(ValueChangedEvent<bool> tracking) private void updateSlidingSample(ValueChangedEvent<bool> tracking)
{ {
if (tracking.NewValue) if (tracking.NewValue)
@ -244,7 +249,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
// rather than doing it this way, we should probably attach the sample to the tail circle. // rather than doing it this way, we should probably attach the sample to the tail circle.
// this can only be done after we stop using LegacyLastTick. // this can only be done after we stop using LegacyLastTick.
if (TailCircle.Result.Type != HitResult.Miss) if (TailCircle.IsHit)
base.PlaySamples(); base.PlaySamples();
} }

View File

@ -9,7 +9,6 @@ using osu.Framework.Graphics;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
using osu.Game.Rulesets.Scoring;
using osuTK; using osuTK;
namespace osu.Game.Rulesets.Osu.Objects.Drawables namespace osu.Game.Rulesets.Osu.Objects.Drawables
@ -50,7 +49,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected override void CheckForResult(bool userTriggered, double timeOffset) protected override void CheckForResult(bool userTriggered, double timeOffset)
{ {
if (sliderRepeat.StartTime <= Time.Current) if (sliderRepeat.StartTime <= Time.Current)
ApplyResult(r => r.Type = drawableSlider.Tracking.Value ? HitResult.Great : HitResult.Miss); ApplyResult(r => r.Type = drawableSlider.Tracking.Value ? r.Judgement.MaxResult : r.Judgement.MinResult);
} }
protected override void UpdateInitialTransforms() protected override void UpdateInitialTransforms()

View File

@ -3,7 +3,6 @@
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.Scoring;
using osuTK; using osuTK;
namespace osu.Game.Rulesets.Osu.Objects.Drawables namespace osu.Game.Rulesets.Osu.Objects.Drawables
@ -46,7 +45,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected override void CheckForResult(bool userTriggered, double timeOffset) protected override void CheckForResult(bool userTriggered, double timeOffset)
{ {
if (!userTriggered && timeOffset >= 0) if (!userTriggered && timeOffset >= 0)
ApplyResult(r => r.Type = Tracking ? r.Judgement.MaxResult : HitResult.Miss); ApplyResult(r => r.Type = Tracking ? r.Judgement.MaxResult : r.Judgement.MinResult);
} }
private void updatePosition() => Position = HitObject.Position - slider.Position; private void updatePosition() => Position = HitObject.Position - slider.Position;

View File

@ -10,7 +10,6 @@ using osuTK.Graphics;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Skinning; using osu.Game.Skinning;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects.Drawables namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
@ -64,7 +63,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected override void CheckForResult(bool userTriggered, double timeOffset) protected override void CheckForResult(bool userTriggered, double timeOffset)
{ {
if (timeOffset >= 0) if (timeOffset >= 0)
ApplyResult(r => r.Type = Tracking ? r.Judgement.MaxResult : HitResult.Miss); ApplyResult(r => r.Type = Tracking ? r.Judgement.MaxResult : r.Judgement.MinResult);
} }
protected override void UpdateInitialTransforms() protected override void UpdateInitialTransforms()

View File

@ -124,6 +124,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
} }
} }
public override void StopAllSamples()
{
base.StopAllSamples();
spinningSample?.Stop();
}
protected override void AddNestedHitObject(DrawableHitObject hitObject) protected override void AddNestedHitObject(DrawableHitObject hitObject)
{ {
base.AddNestedHitObject(hitObject); base.AddNestedHitObject(hitObject);
@ -206,7 +212,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
return; return;
// Trigger a miss result for remaining ticks to avoid infinite gameplay. // Trigger a miss result for remaining ticks to avoid infinite gameplay.
foreach (var tick in ticks.Where(t => !t.IsHit)) foreach (var tick in ticks.Where(t => !t.Result.HasResult))
tick.TriggerResult(false); tick.TriggerResult(false);
ApplyResult(r => ApplyResult(r =>
@ -214,11 +220,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
if (Progress >= 1) if (Progress >= 1)
r.Type = HitResult.Great; r.Type = HitResult.Great;
else if (Progress > .9) else if (Progress > .9)
r.Type = HitResult.Good; r.Type = HitResult.Ok;
else if (Progress > .75) else if (Progress > .75)
r.Type = HitResult.Meh; r.Type = HitResult.Meh;
else if (Time.Current >= Spinner.EndTime) else if (Time.Current >= Spinner.EndTime)
r.Type = HitResult.Miss; r.Type = r.Judgement.MinResult;
}); });
} }
@ -262,7 +268,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
while (wholeSpins != spins) while (wholeSpins != spins)
{ {
var tick = ticks.FirstOrDefault(t => !t.IsHit); var tick = ticks.FirstOrDefault(t => !t.Result.HasResult);
// tick may be null if we've hit the spin limit. // tick may be null if we've hit the spin limit.
if (tick != null) if (tick != null)

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects.Drawables namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
public class DrawableSpinnerTick : DrawableOsuHitObject public class DrawableSpinnerTick : DrawableOsuHitObject
@ -18,6 +16,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
/// Apply a judgement result. /// Apply a judgement result.
/// </summary> /// </summary>
/// <param name="hit">Whether this tick was reached.</param> /// <param name="hit">Whether this tick was reached.</param>
internal void TriggerResult(bool hit) => ApplyResult(r => r.Type = hit ? r.Judgement.MaxResult : HitResult.Miss); internal void TriggerResult(bool hit) => ApplyResult(r => r.Type = hit ? r.Judgement.MaxResult : r.Judgement.MinResult);
} }
} }

View File

@ -6,6 +6,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Game.Rulesets.Objects.Drawables;
using osuTK; using osuTK;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
@ -25,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(TextureStore textures) private void load(TextureStore textures, DrawableHitObject drawableHitObject)
{ {
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
@ -35,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Origin = Anchor.Centre, Origin = Anchor.Centre,
Texture = textures.Get(@"Gameplay/osu/disc"), Texture = textures.Get(@"Gameplay/osu/disc"),
}, },
new TrianglesPiece new TrianglesPiece((int)drawableHitObject.HitObject.StartTime)
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Blending = BlendingParameters.Additive, Blending = BlendingParameters.Additive,

View File

@ -20,6 +20,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
private Spinner spinner; private Spinner spinner;
private const float initial_scale = 1.3f;
private const float idle_alpha = 0.2f; private const float idle_alpha = 0.2f;
private const float tracking_alpha = 0.4f; private const float tracking_alpha = 0.4f;
@ -41,7 +42,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
// we are slightly bigger than our parent, to clip the top and bottom of the circle // we are slightly bigger than our parent, to clip the top and bottom of the circle
// this should probably be revisited when scaled spinners are a thing. // this should probably be revisited when scaled spinners are a thing.
Scale = new Vector2(1.3f); Scale = new Vector2(initial_scale);
Anchor = Anchor.Centre; Anchor = Anchor.Centre;
Origin = Anchor.Centre; Origin = Anchor.Centre;
@ -93,6 +94,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
drawableSpinner.RotationTracker.Complete.BindValueChanged(complete => updateComplete(complete.NewValue, 200)); drawableSpinner.RotationTracker.Complete.BindValueChanged(complete => updateComplete(complete.NewValue, 200));
drawableSpinner.ApplyCustomUpdateState += updateStateTransforms; drawableSpinner.ApplyCustomUpdateState += updateStateTransforms;
updateStateTransforms(drawableSpinner, drawableSpinner.State.Value);
} }
protected override void Update() protected override void Update()
@ -115,8 +118,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
fill.Alpha = (float)Interpolation.Damp(fill.Alpha, drawableSpinner.RotationTracker.Tracking ? tracking_alpha : idle_alpha, 0.98f, (float)Math.Abs(Clock.ElapsedFrameTime)); fill.Alpha = (float)Interpolation.Damp(fill.Alpha, drawableSpinner.RotationTracker.Tracking ? tracking_alpha : idle_alpha, 0.98f, (float)Math.Abs(Clock.ElapsedFrameTime));
} }
const float initial_scale = 0.2f; const float initial_fill_scale = 0.2f;
float targetScale = initial_scale + (1 - initial_scale) * drawableSpinner.Progress; float targetScale = initial_fill_scale + (1 - initial_fill_scale) * drawableSpinner.Progress;
fill.Scale = new Vector2((float)Interpolation.Lerp(fill.Scale.X, targetScale, Math.Clamp(Math.Abs(Time.Elapsed) / 100, 0, 1))); fill.Scale = new Vector2((float)Interpolation.Lerp(fill.Scale.X, targetScale, Math.Clamp(Math.Abs(Time.Elapsed) / 100, 0, 1)));
mainContainer.Rotation = drawableSpinner.RotationTracker.Rotation; mainContainer.Rotation = drawableSpinner.RotationTracker.Rotation;
@ -124,41 +127,57 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state) private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state)
{ {
centre.ScaleTo(0); if (!(drawableHitObject is DrawableSpinner))
mainContainer.ScaleTo(0); return;
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt / 2, true)) using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true))
{ {
// constant ambient rotation to give the spinner "spinning" character. this.ScaleTo(initial_scale);
this.RotateTo((float)(25 * spinner.Duration / 2000), spinner.TimePreempt + spinner.Duration); this.RotateTo(0);
centre.ScaleTo(0.3f, spinner.TimePreempt / 4, Easing.OutQuint);
mainContainer.ScaleTo(0.2f, spinner.TimePreempt / 4, Easing.OutQuint);
using (BeginDelayedSequence(spinner.TimePreempt / 2, true)) using (BeginDelayedSequence(spinner.TimePreempt / 2, true))
{ {
centre.ScaleTo(0.5f, spinner.TimePreempt / 2, Easing.OutQuint); // constant ambient rotation to give the spinner "spinning" character.
mainContainer.ScaleTo(1, spinner.TimePreempt / 2, Easing.OutQuint); this.RotateTo((float)(25 * spinner.Duration / 2000), spinner.TimePreempt + spinner.Duration);
}
using (BeginDelayedSequence(spinner.TimePreempt + spinner.Duration + drawableHitObject.Result.TimeOffset, true))
{
switch (state)
{
case ArmedState.Hit:
this.ScaleTo(initial_scale * 1.2f, 320, Easing.Out);
this.RotateTo(mainContainer.Rotation + 180, 320);
break;
case ArmedState.Miss:
this.ScaleTo(initial_scale * 0.8f, 320, Easing.In);
break;
}
}
}
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true))
{
centre.ScaleTo(0);
mainContainer.ScaleTo(0);
using (BeginDelayedSequence(spinner.TimePreempt / 2, true))
{
centre.ScaleTo(0.3f, spinner.TimePreempt / 4, Easing.OutQuint);
mainContainer.ScaleTo(0.2f, spinner.TimePreempt / 4, Easing.OutQuint);
using (BeginDelayedSequence(spinner.TimePreempt / 2, true))
{
centre.ScaleTo(0.5f, spinner.TimePreempt / 2, Easing.OutQuint);
mainContainer.ScaleTo(1, spinner.TimePreempt / 2, Easing.OutQuint);
}
} }
} }
// transforms we have from completing the spinner will be rolled back, so reapply immediately. // transforms we have from completing the spinner will be rolled back, so reapply immediately.
updateComplete(state == ArmedState.Hit, 0); using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true))
updateComplete(state == ArmedState.Hit, 0);
using (BeginDelayedSequence(spinner.Duration, true))
{
switch (state)
{
case ArmedState.Hit:
this.ScaleTo(Scale * 1.2f, 320, Easing.Out);
this.RotateTo(mainContainer.Rotation + 180, 320);
break;
case ArmedState.Miss:
this.ScaleTo(Scale * 0.8f, 320, Easing.In);
break;
}
}
} }
private void updateComplete(bool complete, double duration) private void updateComplete(bool complete, double duration)

View File

@ -13,6 +13,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
/// </summary> /// </summary>
public class SpinnerBonusDisplay : CompositeDrawable public class SpinnerBonusDisplay : CompositeDrawable
{ {
private static readonly int score_per_tick = new SpinnerBonusTick().CreateJudgement().MaxNumericResult;
private readonly OsuSpriteText bonusCounter; private readonly OsuSpriteText bonusCounter;
public SpinnerBonusDisplay() public SpinnerBonusDisplay()
@ -36,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
return; return;
displayedCount = count; displayedCount = count;
bonusCounter.Text = $"{SpinnerBonusTick.SCORE_PER_TICK * count}"; bonusCounter.Text = $"{score_per_tick * count}";
bonusCounter.FadeOutFromOne(1500); bonusCounter.FadeOutFromOne(1500);
bonusCounter.ScaleTo(1.5f).Then().ScaleTo(1f, 1000, Easing.OutQuint); bonusCounter.ScaleTo(1.5f).Then().ScaleTo(1f, 1000, Easing.OutQuint);
} }

View File

@ -11,7 +11,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
protected override bool CreateNewTriangles => false; protected override bool CreateNewTriangles => false;
protected override float SpawnRatio => 0.5f; protected override float SpawnRatio => 0.5f;
public TrianglesPiece() public TrianglesPiece(int? seed = null)
: base(seed)
{ {
TriangleScale = 1.2f; TriangleScale = 1.2f;
HideAlphaDiscrepancies = false; HideAlphaDiscrepancies = false;

View File

@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Objects
public class SliderRepeatJudgement : OsuJudgement public class SliderRepeatJudgement : OsuJudgement
{ {
protected override int NumericResultFor(HitResult result) => result == MaxResult ? 30 : 0; public override HitResult MaxResult => HitResult.LargeTickHit;
} }
} }
} }

View File

@ -29,9 +29,7 @@ namespace osu.Game.Rulesets.Osu.Objects
public class SliderTailJudgement : OsuJudgement public class SliderTailJudgement : OsuJudgement
{ {
protected override int NumericResultFor(HitResult result) => 0; public override HitResult MaxResult => HitResult.SmallTickHit;
public override bool AffectsCombo => false;
} }
} }
} }

View File

@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Objects
public class SliderTickJudgement : OsuJudgement public class SliderTickJudgement : OsuJudgement
{ {
protected override int NumericResultFor(HitResult result) => result == MaxResult ? 10 : 0; public override HitResult MaxResult => HitResult.LargeTickHit;
} }
} }
} }

View File

@ -9,8 +9,6 @@ namespace osu.Game.Rulesets.Osu.Objects
{ {
public class SpinnerBonusTick : SpinnerTick public class SpinnerBonusTick : SpinnerTick
{ {
public new const int SCORE_PER_TICK = 50;
public SpinnerBonusTick() public SpinnerBonusTick()
{ {
Samples.Add(new HitSampleInfo { Name = "spinnerbonus" }); Samples.Add(new HitSampleInfo { Name = "spinnerbonus" });
@ -20,9 +18,7 @@ namespace osu.Game.Rulesets.Osu.Objects
public class OsuSpinnerBonusTickJudgement : OsuSpinnerTickJudgement public class OsuSpinnerBonusTickJudgement : OsuSpinnerTickJudgement
{ {
protected override int NumericResultFor(HitResult result) => result == MaxResult ? SCORE_PER_TICK : 0; public override HitResult MaxResult => HitResult.LargeBonus;
protected override double HealthIncreaseFor(HitResult result) => base.HealthIncreaseFor(result) * 2;
} }
} }
} }

View File

@ -9,19 +9,13 @@ namespace osu.Game.Rulesets.Osu.Objects
{ {
public class SpinnerTick : OsuHitObject public class SpinnerTick : OsuHitObject
{ {
public const int SCORE_PER_TICK = 10;
public override Judgement CreateJudgement() => new OsuSpinnerTickJudgement(); public override Judgement CreateJudgement() => new OsuSpinnerTickJudgement();
protected override HitWindows CreateHitWindows() => HitWindows.Empty; protected override HitWindows CreateHitWindows() => HitWindows.Empty;
public class OsuSpinnerTickJudgement : OsuJudgement public class OsuSpinnerTickJudgement : OsuJudgement
{ {
public override bool AffectsCombo => false; public override HitResult MaxResult => HitResult.SmallBonus;
protected override int NumericResultFor(HitResult result) => result == MaxResult ? SCORE_PER_TICK : 0;
protected override double HealthIncreaseFor(HitResult result) => result == MaxResult ? 0.6 * base.HealthIncreaseFor(result) : 0;
} }
} }
} }

View File

@ -137,13 +137,13 @@ namespace osu.Game.Rulesets.Osu.Replays
if (!(h is Spinner)) if (!(h is Spinner))
AddFrameToReplay(new OsuReplayFrame(h.StartTime - hitWindows.WindowFor(HitResult.Meh), new Vector2(h.StackedPosition.X, h.StackedPosition.Y))); AddFrameToReplay(new OsuReplayFrame(h.StartTime - hitWindows.WindowFor(HitResult.Meh), new Vector2(h.StackedPosition.X, h.StackedPosition.Y)));
} }
else if (h.StartTime - hitWindows.WindowFor(HitResult.Good) > endTime + hitWindows.WindowFor(HitResult.Good) + 50) else if (h.StartTime - hitWindows.WindowFor(HitResult.Ok) > endTime + hitWindows.WindowFor(HitResult.Ok) + 50)
{ {
if (!(prev is Spinner) && h.StartTime - endTime < 1000) if (!(prev is Spinner) && h.StartTime - endTime < 1000)
AddFrameToReplay(new OsuReplayFrame(endTime + hitWindows.WindowFor(HitResult.Good), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y))); AddFrameToReplay(new OsuReplayFrame(endTime + hitWindows.WindowFor(HitResult.Ok), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y)));
if (!(h is Spinner)) if (!(h is Spinner))
AddFrameToReplay(new OsuReplayFrame(h.StartTime - hitWindows.WindowFor(HitResult.Good), new Vector2(h.StackedPosition.X, h.StackedPosition.Y))); AddFrameToReplay(new OsuReplayFrame(h.StartTime - hitWindows.WindowFor(HitResult.Ok), new Vector2(h.StackedPosition.X, h.StackedPosition.Y)));
} }
} }

View File

@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Osu.Scoring
private static readonly DifficultyRange[] osu_ranges = private static readonly DifficultyRange[] osu_ranges =
{ {
new DifficultyRange(HitResult.Great, 80, 50, 20), new DifficultyRange(HitResult.Great, 80, 50, 20),
new DifficultyRange(HitResult.Good, 140, 100, 60), new DifficultyRange(HitResult.Ok, 140, 100, 60),
new DifficultyRange(HitResult.Meh, 200, 150, 100), new DifficultyRange(HitResult.Meh, 200, 150, 100),
new DifficultyRange(HitResult.Miss, 400, 400, 400), new DifficultyRange(HitResult.Miss, 400, 400, 400),
}; };
@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Scoring
switch (result) switch (result)
{ {
case HitResult.Great: case HitResult.Great:
case HitResult.Good: case HitResult.Ok:
case HitResult.Meh: case HitResult.Meh:
case HitResult.Miss: case HitResult.Miss:
return true; return true;

View File

@ -25,7 +25,5 @@ namespace osu.Game.Rulesets.Osu.Scoring
return new OsuJudgementResult(hitObject, judgement); return new OsuJudgementResult(hitObject, judgement);
} }
} }
public override HitWindows CreateHitWindows() => new OsuHitWindows();
} }
} }

View File

@ -70,20 +70,30 @@ namespace osu.Game.Rulesets.Osu.Skinning
{ {
base.LoadComplete(); base.LoadComplete();
this.FadeOut();
drawableSpinner.ApplyCustomUpdateState += updateStateTransforms; drawableSpinner.ApplyCustomUpdateState += updateStateTransforms;
updateStateTransforms(drawableSpinner, drawableSpinner.State.Value);
} }
private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state) private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state)
{ {
if (!(drawableHitObject is DrawableSpinner))
return;
var spinner = (Spinner)drawableSpinner.HitObject; var spinner = (Spinner)drawableSpinner.HitObject;
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true))
this.FadeOut();
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2, true)) using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2, true))
this.FadeInFromZero(spinner.TimeFadeIn / 2); this.FadeInFromZero(spinner.TimeFadeIn / 2);
fixedMiddle.FadeColour(Color4.White); using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true))
using (BeginAbsoluteSequence(spinner.StartTime, true)) {
fixedMiddle.FadeColour(Color4.Red, spinner.Duration); fixedMiddle.FadeColour(Color4.White);
using (BeginDelayedSequence(spinner.TimePreempt, true))
fixedMiddle.FadeColour(Color4.Red, spinner.Duration);
}
} }
protected override void Update() protected override void Update()

View File

@ -24,12 +24,16 @@ namespace osu.Game.Rulesets.Osu.Skinning
private Sprite metreSprite; private Sprite metreSprite;
private Container metre; private Container metre;
private bool spinnerBlink;
private const float sprite_scale = 1 / 1.6f; private const float sprite_scale = 1 / 1.6f;
private const float final_metre_height = 692 * sprite_scale; private const float final_metre_height = 692 * sprite_scale;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(ISkinSource source, DrawableHitObject drawableObject) private void load(ISkinSource source, DrawableHitObject drawableObject)
{ {
spinnerBlink = source.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.SpinnerNoBlink)?.Value != true;
drawableSpinner = (DrawableSpinner)drawableObject; drawableSpinner = (DrawableSpinner)drawableObject;
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
@ -84,14 +88,20 @@ namespace osu.Game.Rulesets.Osu.Skinning
{ {
base.LoadComplete(); base.LoadComplete();
this.FadeOut();
drawableSpinner.ApplyCustomUpdateState += updateStateTransforms; drawableSpinner.ApplyCustomUpdateState += updateStateTransforms;
updateStateTransforms(drawableSpinner, drawableSpinner.State.Value);
} }
private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state) private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state)
{ {
if (!(drawableHitObject is DrawableSpinner))
return;
var spinner = drawableSpinner.HitObject; var spinner = drawableSpinner.HitObject;
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true))
this.FadeOut();
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2, true)) using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2, true))
this.FadeInFromZero(spinner.TimeFadeIn / 2); this.FadeInFromZero(spinner.TimeFadeIn / 2);
} }
@ -116,12 +126,15 @@ namespace osu.Game.Rulesets.Osu.Skinning
private float getMetreHeight(float progress) private float getMetreHeight(float progress)
{ {
progress = Math.Min(99, progress * 100); progress *= 100;
// the spinner should still blink at 100% progress.
if (spinnerBlink)
progress = Math.Min(99, progress);
int barCount = (int)progress / 10; int barCount = (int)progress / 10;
// todo: add SpinnerNoBlink support if (spinnerBlink && RNG.NextBool(((int)progress % 10) / 10f))
if (RNG.NextBool(((int)progress % 10) / 10f))
barCount++; barCount++;
return (float)barCount / total_bars * final_metre_height; return (float)barCount / total_bars * final_metre_height;

View File

@ -14,6 +14,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
CursorRotate, CursorRotate,
HitCircleOverlayAboveNumber, HitCircleOverlayAboveNumber,
HitCircleOverlayAboveNumer, // Some old skins will have this typo HitCircleOverlayAboveNumer, // Some old skins will have this typo
SpinnerFrequencyModulate SpinnerFrequencyModulate,
SpinnerNoBlink
} }
} }

View File

@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
createDrawableRuleset(); createDrawableRuleset();
assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Great }, TaikoMascotAnimationState.Idle); assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Great }, TaikoMascotAnimationState.Idle);
assertStateAfterResult(new JudgementResult(new StrongHitObject(), new TaikoStrongJudgement()) { Type = HitResult.Miss }, TaikoMascotAnimationState.Idle); assertStateAfterResult(new JudgementResult(new StrongHitObject(), new TaikoStrongJudgement()) { Type = HitResult.IgnoreMiss }, TaikoMascotAnimationState.Idle);
} }
[Test] [Test]
@ -102,8 +102,8 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
createDrawableRuleset(); createDrawableRuleset();
assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Good }, TaikoMascotAnimationState.Kiai); assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Ok }, TaikoMascotAnimationState.Kiai);
assertStateAfterResult(new JudgementResult(new Hit(), new TaikoStrongJudgement()) { Type = HitResult.Miss }, TaikoMascotAnimationState.Kiai); assertStateAfterResult(new JudgementResult(new Hit(), new TaikoStrongJudgement()) { Type = HitResult.IgnoreMiss }, TaikoMascotAnimationState.Kiai);
assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Miss }, TaikoMascotAnimationState.Fail); assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Miss }, TaikoMascotAnimationState.Fail);
} }
@ -117,7 +117,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Great }, TaikoMascotAnimationState.Idle); 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 Hit(), new TaikoJudgement()) { Type = HitResult.Miss }, TaikoMascotAnimationState.Fail);
assertStateAfterResult(new JudgementResult(new DrumRoll(), new TaikoDrumRollJudgement()) { Type = HitResult.Great }, TaikoMascotAnimationState.Idle); assertStateAfterResult(new JudgementResult(new DrumRoll(), new TaikoDrumRollJudgement()) { Type = HitResult.Great }, TaikoMascotAnimationState.Idle);
assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Good }, TaikoMascotAnimationState.Idle); assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Ok }, TaikoMascotAnimationState.Idle);
} }
[TestCase(true)] [TestCase(true)]
@ -130,7 +130,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
AddRepeatStep("reach 49 combo", () => applyNewResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Great }), 49); 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); assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Ok }, TaikoMascotAnimationState.Clear);
} }
[TestCase(true, TaikoMascotAnimationState.Kiai)] [TestCase(true, TaikoMascotAnimationState.Kiai)]

View File

@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
public void TestNormalHit() public void TestNormalHit()
{ {
AddStep("Great", () => SetContents(() => getContentFor(createHit(HitResult.Great)))); AddStep("Great", () => SetContents(() => getContentFor(createHit(HitResult.Great))));
AddStep("Good", () => SetContents(() => getContentFor(createHit(HitResult.Good)))); AddStep("Ok", () => SetContents(() => getContentFor(createHit(HitResult.Ok))));
AddStep("Miss", () => SetContents(() => getContentFor(createHit(HitResult.Miss)))); AddStep("Miss", () => SetContents(() => getContentFor(createHit(HitResult.Miss))));
} }
@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
public void TestStrongHit([Values(false, true)] bool hitBoth) public void TestStrongHit([Values(false, true)] bool hitBoth)
{ {
AddStep("Great", () => SetContents(() => getContentFor(createStrongHit(HitResult.Great, hitBoth)))); AddStep("Great", () => SetContents(() => getContentFor(createStrongHit(HitResult.Great, hitBoth))));
AddStep("Good", () => SetContents(() => getContentFor(createStrongHit(HitResult.Good, hitBoth)))); AddStep("Good", () => SetContents(() => getContentFor(createStrongHit(HitResult.Ok, hitBoth))));
} }
private Drawable getContentFor(DrawableTestHit hit) private Drawable getContentFor(DrawableTestHit hit)

View File

@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
})); }));
AddToggleStep("Toggle passing", passing => this.ChildrenOfType<LegacyTaikoScroller>().ForEach(s => s.LastResult.Value = AddToggleStep("Toggle passing", passing => this.ChildrenOfType<LegacyTaikoScroller>().ForEach(s => s.LastResult.Value =
new JudgementResult(null, new Judgement()) { Type = passing ? HitResult.Perfect : HitResult.Miss })); new JudgementResult(null, new Judgement()) { Type = passing ? HitResult.Great : HitResult.Miss }));
AddToggleStep("toggle playback direction", reversed => this.reversed = reversed); AddToggleStep("toggle playback direction", reversed => this.reversed = reversed);
} }

View File

@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
DrawableDrumRollTick h; DrawableDrumRollTick h;
DrawableRuleset.Playfield.Add(h = new DrawableDrumRollTick(tick) { JudgementType = hitType }); DrawableRuleset.Playfield.Add(h = new DrawableDrumRollTick(tick) { JudgementType = hitType });
((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(tick, new TaikoDrumRollTickJudgement()) { Type = HitResult.Perfect }); ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(tick, new TaikoDrumRollTickJudgement()) { Type = HitResult.Great });
} }
} }
} }

View File

@ -3,7 +3,6 @@
using System; using System;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
@ -30,8 +29,8 @@ namespace osu.Game.Rulesets.Taiko.Tests
private readonly Random rng = new Random(1337); private readonly Random rng = new Random(1337);
[BackgroundDependencyLoader] [Test]
private void load() public void TestVariousHits()
{ {
AddStep("Hit", () => addHitJudgement(false)); AddStep("Hit", () => addHitJudgement(false));
AddStep("Strong hit", () => addStrongHitJudgement(false)); AddStep("Strong hit", () => addStrongHitJudgement(false));
@ -105,7 +104,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
private void addHitJudgement(bool kiai) private void addHitJudgement(bool kiai)
{ {
HitResult hitResult = RNG.Next(2) == 0 ? HitResult.Good : HitResult.Great; HitResult hitResult = RNG.Next(2) == 0 ? HitResult.Ok : HitResult.Great;
var cpi = new ControlPointInfo(); var cpi = new ControlPointInfo();
cpi.Add(0, new EffectControlPoint { KiaiMode = kiai }); cpi.Add(0, new EffectControlPoint { KiaiMode = kiai });
@ -113,7 +112,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
Hit hit = new Hit(); Hit hit = new Hit();
hit.ApplyDefaults(cpi, new BeatmapDifficulty()); hit.ApplyDefaults(cpi, new BeatmapDifficulty());
var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Good ? -0.1f : -0.05f, hitResult == HitResult.Good ? 0.1f : 0.05f) }; var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Ok ? -0.1f : -0.05f, hitResult == HitResult.Ok ? 0.1f : 0.05f) };
DrawableRuleset.Playfield.Add(h); DrawableRuleset.Playfield.Add(h);
@ -122,7 +121,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
private void addStrongHitJudgement(bool kiai) private void addStrongHitJudgement(bool kiai)
{ {
HitResult hitResult = RNG.Next(2) == 0 ? HitResult.Good : HitResult.Great; HitResult hitResult = RNG.Next(2) == 0 ? HitResult.Ok : HitResult.Great;
var cpi = new ControlPointInfo(); var cpi = new ControlPointInfo();
cpi.Add(0, new EffectControlPoint { KiaiMode = kiai }); cpi.Add(0, new EffectControlPoint { KiaiMode = kiai });
@ -130,7 +129,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
Hit hit = new Hit(); Hit hit = new Hit();
hit.ApplyDefaults(cpi, new BeatmapDifficulty()); hit.ApplyDefaults(cpi, new BeatmapDifficulty());
var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Good ? -0.1f : -0.05f, hitResult == HitResult.Good ? 0.1f : 0.05f) }; var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Ok ? -0.1f : -0.05f, hitResult == HitResult.Ok ? 0.1f : 0.05f) };
DrawableRuleset.Playfield.Add(h); DrawableRuleset.Playfield.Add(h);

View File

@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
private Mod[] mods; private Mod[] mods;
private int countGreat; private int countGreat;
private int countGood; private int countOk;
private int countMeh; private int countMeh;
private int countMiss; private int countMiss;
@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
{ {
mods = Score.Mods; mods = Score.Mods;
countGreat = Score.Statistics.GetOrDefault(HitResult.Great); countGreat = Score.Statistics.GetOrDefault(HitResult.Great);
countGood = Score.Statistics.GetOrDefault(HitResult.Good); countOk = Score.Statistics.GetOrDefault(HitResult.Ok);
countMeh = Score.Statistics.GetOrDefault(HitResult.Meh); countMeh = Score.Statistics.GetOrDefault(HitResult.Meh);
countMiss = Score.Statistics.GetOrDefault(HitResult.Miss); countMiss = Score.Statistics.GetOrDefault(HitResult.Miss);
@ -102,6 +102,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
return accValue * Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3)); return accValue * Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3));
} }
private int totalHits => countGreat + countGood + countMeh + countMiss; private int totalHits => countGreat + countOk + countMeh + countMiss;
} }
} }

View File

@ -7,25 +7,13 @@ namespace osu.Game.Rulesets.Taiko.Judgements
{ {
public class TaikoDrumRollTickJudgement : TaikoJudgement public class TaikoDrumRollTickJudgement : TaikoJudgement
{ {
public override bool AffectsCombo => false; public override HitResult MaxResult => HitResult.SmallTickHit;
protected override int NumericResultFor(HitResult result)
{
switch (result)
{
case HitResult.Great:
return 200;
default:
return 0;
}
}
protected override double HealthIncreaseFor(HitResult result) protected override double HealthIncreaseFor(HitResult result)
{ {
switch (result) switch (result)
{ {
case HitResult.Great: case HitResult.SmallTickHit:
return 0.15; return 0.15;
default: default:

View File

@ -10,21 +10,6 @@ namespace osu.Game.Rulesets.Taiko.Judgements
{ {
public override HitResult MaxResult => HitResult.Great; public override HitResult MaxResult => HitResult.Great;
protected override int NumericResultFor(HitResult result)
{
switch (result)
{
case HitResult.Good:
return 100;
case HitResult.Great:
return 300;
default:
return 0;
}
}
protected override double HealthIncreaseFor(HitResult result) protected override double HealthIncreaseFor(HitResult result)
{ {
switch (result) switch (result)
@ -32,7 +17,7 @@ namespace osu.Game.Rulesets.Taiko.Judgements
case HitResult.Miss: case HitResult.Miss:
return -1.0; return -1.0;
case HitResult.Good: case HitResult.Ok:
return 1.1; return 1.1;
case HitResult.Great: case HitResult.Great:

View File

@ -7,9 +7,9 @@ namespace osu.Game.Rulesets.Taiko.Judgements
{ {
public class TaikoStrongJudgement : TaikoJudgement public class TaikoStrongJudgement : TaikoJudgement
{ {
public override HitResult MaxResult => HitResult.SmallBonus;
// MainObject already changes the HP // MainObject already changes the HP
protected override double HealthIncreaseFor(HitResult result) => 0; protected override double HealthIncreaseFor(HitResult result) => 0;
public override bool AffectsCombo => false;
} }
} }

View File

@ -107,7 +107,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
if (!(obj is DrawableDrumRollTick)) if (!(obj is DrawableDrumRollTick))
return; return;
if (result.Type > HitResult.Miss) if (result.IsHit)
rollingHits++; rollingHits++;
else else
rollingHits--; rollingHits--;
@ -129,10 +129,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
if (countHit >= HitObject.RequiredGoodHits) if (countHit >= HitObject.RequiredGoodHits)
{ {
ApplyResult(r => r.Type = countHit >= HitObject.RequiredGreatHits ? HitResult.Great : HitResult.Good); ApplyResult(r => r.Type = countHit >= HitObject.RequiredGreatHits ? HitResult.Great : HitResult.Ok);
} }
else else
ApplyResult(r => r.Type = HitResult.Miss); ApplyResult(r => r.Type = r.Judgement.MinResult);
} }
protected override void UpdateStateTransforms(ArmedState state) protected override void UpdateStateTransforms(ArmedState state)
@ -174,7 +174,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
if (!MainObject.Judged) if (!MainObject.Judged)
return; return;
ApplyResult(r => r.Type = MainObject.IsHit ? HitResult.Great : HitResult.Miss); ApplyResult(r => r.Type = MainObject.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult);
} }
public override bool OnPressed(TaikoAction action) => false; public override bool OnPressed(TaikoAction action) => false;

View File

@ -4,7 +4,6 @@
using System; using System;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
using osu.Game.Skinning; using osu.Game.Skinning;
@ -34,14 +33,14 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
if (!userTriggered) if (!userTriggered)
{ {
if (timeOffset > HitObject.HitWindow) if (timeOffset > HitObject.HitWindow)
ApplyResult(r => r.Type = HitResult.Miss); ApplyResult(r => r.Type = r.Judgement.MinResult);
return; return;
} }
if (Math.Abs(timeOffset) > HitObject.HitWindow) if (Math.Abs(timeOffset) > HitObject.HitWindow)
return; return;
ApplyResult(r => r.Type = HitResult.Great); ApplyResult(r => r.Type = r.Judgement.MaxResult);
} }
protected override void UpdateStateTransforms(ArmedState state) protected override void UpdateStateTransforms(ArmedState state)
@ -74,7 +73,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
if (!MainObject.Judged) if (!MainObject.Judged)
return; return;
ApplyResult(r => r.Type = MainObject.IsHit ? HitResult.Great : HitResult.Miss); ApplyResult(r => r.Type = MainObject.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult);
} }
public override bool OnPressed(TaikoAction action) => false; public override bool OnPressed(TaikoAction action) => false;

View File

@ -143,7 +143,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
if (!userTriggered) if (!userTriggered)
{ {
if (!HitObject.HitWindows.CanBeHit(timeOffset)) if (!HitObject.HitWindows.CanBeHit(timeOffset))
ApplyResult(r => r.Type = HitResult.Miss); ApplyResult(r => r.Type = r.Judgement.MinResult);
return; return;
} }
@ -152,7 +152,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
return; return;
if (!validActionPressed) if (!validActionPressed)
ApplyResult(r => r.Type = HitResult.Miss); ApplyResult(r => r.Type = r.Judgement.MinResult);
else else
ApplyResult(r => r.Type = result); ApplyResult(r => r.Type = result);
} }
@ -257,19 +257,19 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
if (!MainObject.Result.IsHit) if (!MainObject.Result.IsHit)
{ {
ApplyResult(r => r.Type = HitResult.Miss); ApplyResult(r => r.Type = r.Judgement.MinResult);
return; return;
} }
if (!userTriggered) if (!userTriggered)
{ {
if (timeOffset - MainObject.Result.TimeOffset > second_hit_window) if (timeOffset - MainObject.Result.TimeOffset > second_hit_window)
ApplyResult(r => r.Type = HitResult.Miss); ApplyResult(r => r.Type = r.Judgement.MinResult);
return; return;
} }
if (Math.Abs(timeOffset - MainObject.Result.TimeOffset) <= second_hit_window) if (Math.Abs(timeOffset - MainObject.Result.TimeOffset) <= second_hit_window)
ApplyResult(r => r.Type = MainObject.Result.Type); ApplyResult(r => r.Type = r.Judgement.MaxResult);
} }
public override bool OnPressed(TaikoAction action) public override bool OnPressed(TaikoAction action)

View File

@ -175,7 +175,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
} }
} }
nextTick?.TriggerResult(HitResult.Great); nextTick?.TriggerResult(true);
var numHits = ticks.Count(r => r.IsHit); var numHits = ticks.Count(r => r.IsHit);
@ -208,12 +208,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
continue; continue;
} }
tick.TriggerResult(HitResult.Miss); tick.TriggerResult(false);
} }
var hitResult = numHits > HitObject.RequiredHits / 2 ? HitResult.Good : HitResult.Miss; ApplyResult(r => r.Type = numHits > HitObject.RequiredHits / 2 ? HitResult.Ok : r.Judgement.MinResult);
ApplyResult(r => r.Type = hitResult);
} }
} }

View File

@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
using osu.Game.Skinning; using osu.Game.Skinning;
@ -19,10 +18,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
protected override void UpdateInitialTransforms() => this.FadeOut(); protected override void UpdateInitialTransforms() => this.FadeOut();
public void TriggerResult(HitResult type) public void TriggerResult(bool hit)
{ {
HitObject.StartTime = Time.Current; HitObject.StartTime = Time.Current;
ApplyResult(r => r.Type = type); ApplyResult(r => r.Type = hit ? r.Judgement.MaxResult : r.Judgement.MinResult);
} }
protected override void CheckForResult(bool userTriggered, double timeOffset) protected override void CheckForResult(bool userTriggered, double timeOffset)

View File

@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Taiko.Scoring
private double hpMultiplier; private double hpMultiplier;
/// <summary> /// <summary>
/// HP multiplier for a <see cref="HitResult.Miss"/>. /// HP multiplier for a <see cref="HitResult"/> that does not satisfy <see cref="HitResultExtensions.IsHit"/>.
/// </summary> /// </summary>
private double hpMissMultiplier; private double hpMissMultiplier;
@ -45,6 +45,6 @@ namespace osu.Game.Rulesets.Taiko.Scoring
} }
protected override double GetHealthIncreaseFor(JudgementResult result) protected override double GetHealthIncreaseFor(JudgementResult result)
=> base.GetHealthIncreaseFor(result) * (result.Type == HitResult.Miss ? hpMissMultiplier : hpMultiplier); => base.GetHealthIncreaseFor(result) * (result.IsHit ? hpMultiplier : hpMissMultiplier);
} }
} }

View File

@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Taiko.Scoring
private static readonly DifficultyRange[] taiko_ranges = private static readonly DifficultyRange[] taiko_ranges =
{ {
new DifficultyRange(HitResult.Great, 50, 35, 20), new DifficultyRange(HitResult.Great, 50, 35, 20),
new DifficultyRange(HitResult.Good, 120, 80, 50), new DifficultyRange(HitResult.Ok, 120, 80, 50),
new DifficultyRange(HitResult.Miss, 135, 95, 70), new DifficultyRange(HitResult.Miss, 135, 95, 70),
}; };
@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Taiko.Scoring
switch (result) switch (result)
{ {
case HitResult.Great: case HitResult.Great:
case HitResult.Good: case HitResult.Ok:
case HitResult.Miss: case HitResult.Miss:
return true; return true;
} }

View File

@ -10,7 +10,5 @@ namespace osu.Game.Rulesets.Taiko.Scoring
protected override double DefaultAccuracyPortion => 0.75; protected override double DefaultAccuracyPortion => 0.75;
protected override double DefaultComboPortion => 0.25; protected override double DefaultComboPortion => 0.25;
public override HitWindows CreateHitWindows() => new TaikoHitWindows();
} }
} }

View File

@ -1,21 +1,64 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Taiko.Objects.Drawables;
namespace osu.Game.Rulesets.Taiko.Skinning namespace osu.Game.Rulesets.Taiko.Skinning
{ {
public class LegacyHitExplosion : CompositeDrawable public class LegacyHitExplosion : CompositeDrawable
{ {
public LegacyHitExplosion(Drawable sprite) private readonly Drawable sprite;
{ private readonly Drawable strongSprite;
InternalChild = sprite;
private DrawableStrongNestedHit nestedStrongHit;
private bool switchedToStrongSprite;
/// <summary>
/// Creates a new legacy hit explosion.
/// </summary>
/// <remarks>
/// Contrary to stable's, this implementation doesn't require a frame-perfect hit
/// for the strong sprite to be displayed.
/// </remarks>
/// <param name="sprite">The normal legacy explosion sprite.</param>
/// <param name="strongSprite">The strong legacy explosion sprite.</param>
public LegacyHitExplosion(Drawable sprite, Drawable strongSprite = null)
{
this.sprite = sprite;
this.strongSprite = strongSprite;
}
[BackgroundDependencyLoader]
private void load(DrawableHitObject judgedObject)
{
Anchor = Anchor.Centre; Anchor = Anchor.Centre;
Origin = Anchor.Centre; Origin = Anchor.Centre;
AutoSizeAxes = Axes.Both; AutoSizeAxes = Axes.Both;
AddInternal(sprite.With(s =>
{
s.Anchor = Anchor.Centre;
s.Origin = Anchor.Centre;
}));
if (strongSprite != null)
{
AddInternal(strongSprite.With(s =>
{
s.Alpha = 0;
s.Anchor = Anchor.Centre;
s.Origin = Anchor.Centre;
}));
}
if (judgedObject is DrawableHit hit)
nestedStrongHit = hit.NestedHitObjects.SingleOrDefault() as DrawableStrongNestedHit;
} }
protected override void LoadComplete() protected override void LoadComplete()
@ -33,5 +76,25 @@ namespace osu.Game.Rulesets.Taiko.Skinning
Expire(true); Expire(true);
} }
protected override void Update()
{
base.Update();
if (shouldSwitchToStrongSprite() && !switchedToStrongSprite)
{
sprite.FadeOut(50, Easing.OutQuint);
strongSprite.FadeIn(50, Easing.OutQuint);
switchedToStrongSprite = true;
}
}
private bool shouldSwitchToStrongSprite()
{
if (nestedStrongHit == null || strongSprite == null)
return false;
return nestedStrongHit.IsHit;
}
} }
} }

View File

@ -42,10 +42,10 @@ namespace osu.Game.Rulesets.Taiko.Skinning
var r = result.NewValue; var r = result.NewValue;
// always ignore hitobjects that don't affect combo (drumroll ticks etc.) // always ignore hitobjects that don't affect combo (drumroll ticks etc.)
if (r?.Judgement.AffectsCombo == false) if (r?.Type.AffectsCombo() == false)
return; return;
passing = r == null || r.Type > HitResult.Miss; passing = r == null || r.IsHit;
foreach (var sprite in InternalChildren.OfType<ScrollerSprite>()) foreach (var sprite in InternalChildren.OfType<ScrollerSprite>())
sprite.Passing = passing; sprite.Passing = passing;

View File

@ -74,15 +74,23 @@ namespace osu.Game.Rulesets.Taiko.Skinning
return null; return null;
case TaikoSkinComponents.TaikoExplosionGood:
case TaikoSkinComponents.TaikoExplosionGoodStrong:
case TaikoSkinComponents.TaikoExplosionGreat:
case TaikoSkinComponents.TaikoExplosionGreatStrong:
case TaikoSkinComponents.TaikoExplosionMiss: case TaikoSkinComponents.TaikoExplosionMiss:
var sprite = this.GetAnimation(getHitName(taikoComponent.Component), true, false); var missSprite = this.GetAnimation(getHitName(taikoComponent.Component), true, false);
if (sprite != null) if (missSprite != null)
return new LegacyHitExplosion(sprite); return new LegacyHitExplosion(missSprite);
return null;
case TaikoSkinComponents.TaikoExplosionOk:
case TaikoSkinComponents.TaikoExplosionGreat:
var hitName = getHitName(taikoComponent.Component);
var hitSprite = this.GetAnimation(hitName, true, false);
var strongHitSprite = this.GetAnimation($"{hitName}k", true, false);
if (hitSprite != null)
return new LegacyHitExplosion(hitSprite, strongHitSprite);
return null; return null;
@ -106,20 +114,14 @@ namespace osu.Game.Rulesets.Taiko.Skinning
case TaikoSkinComponents.TaikoExplosionMiss: case TaikoSkinComponents.TaikoExplosionMiss:
return "taiko-hit0"; return "taiko-hit0";
case TaikoSkinComponents.TaikoExplosionGood: case TaikoSkinComponents.TaikoExplosionOk:
return "taiko-hit100"; return "taiko-hit100";
case TaikoSkinComponents.TaikoExplosionGoodStrong:
return "taiko-hit100k";
case TaikoSkinComponents.TaikoExplosionGreat: case TaikoSkinComponents.TaikoExplosionGreat:
return "taiko-hit300"; return "taiko-hit300";
case TaikoSkinComponents.TaikoExplosionGreatStrong:
return "taiko-hit300k";
} }
throw new ArgumentOutOfRangeException(nameof(component), "Invalid result type"); throw new ArgumentOutOfRangeException(nameof(component), $"Invalid component type: {component}");
} }
public override SampleChannel GetSample(ISampleInfo sampleInfo) => Source.GetSample(new LegacyTaikoSampleInfo(sampleInfo)); public override SampleChannel GetSample(ISampleInfo sampleInfo) => Source.GetSample(new LegacyTaikoSampleInfo(sampleInfo));

View File

@ -16,10 +16,8 @@ namespace osu.Game.Rulesets.Taiko
PlayfieldBackgroundRight, PlayfieldBackgroundRight,
BarLine, BarLine,
TaikoExplosionMiss, TaikoExplosionMiss,
TaikoExplosionGood, TaikoExplosionOk,
TaikoExplosionGoodStrong,
TaikoExplosionGreat, TaikoExplosionGreat,
TaikoExplosionGreatStrong,
Scroller, Scroller,
Mascot, Mascot,
} }

View File

@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Taiko.UI
Alpha = 0.15f; Alpha = 0.15f;
Masking = true; Masking = true;
if (result == HitResult.Miss) if (!result.IsHit())
return; return;
bool isRim = (judgedObject.HitObject as Hit)?.Type == HitType.Rim; bool isRim = (judgedObject.HitObject as Hit)?.Type == HitType.Rim;

View File

@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Taiko.UI
{ {
switch (Result.Type) switch (Result.Type)
{ {
case HitResult.Good: case HitResult.Ok:
JudgementBody.Colour = colours.GreenLight; JudgementBody.Colour = colours.GreenLight;
break; break;

View File

@ -11,6 +11,7 @@ using osu.Framework.Graphics.Textures;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Judgements; using osu.Game.Rulesets.Taiko.Judgements;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
@ -77,7 +78,7 @@ namespace osu.Game.Rulesets.Taiko.UI
lastObjectHit = true; lastObjectHit = true;
} }
if (!result.Judgement.AffectsCombo) if (!result.Type.AffectsCombo())
return; return;
lastObjectHit = result.IsHit; lastObjectHit = result.IsHit;
@ -115,7 +116,7 @@ namespace osu.Game.Rulesets.Taiko.UI
} }
private bool triggerComboClear(JudgementResult judgementResult) private bool triggerComboClear(JudgementResult judgementResult)
=> (judgementResult.ComboAtJudgement + 1) % 50 == 0 && judgementResult.Judgement.AffectsCombo && judgementResult.IsHit; => (judgementResult.ComboAtJudgement + 1) % 50 == 0 && judgementResult.Type.AffectsCombo() && judgementResult.IsHit;
private bool triggerSwellClear(JudgementResult judgementResult) private bool triggerSwellClear(JudgementResult judgementResult)
=> judgementResult.Judgement is TaikoSwellJudgement && judgementResult.IsHit; => judgementResult.Judgement is TaikoSwellJudgement && judgementResult.IsHit;

View File

@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Linq;
using osuTK; using osuTK;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -10,7 +9,6 @@ using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Objects.Drawables;
using osu.Game.Skinning; using osu.Game.Skinning;
namespace osu.Game.Rulesets.Taiko.UI namespace osu.Game.Rulesets.Taiko.UI
@ -50,39 +48,24 @@ namespace osu.Game.Rulesets.Taiko.UI
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
Child = skinnable = new SkinnableDrawable(new TaikoSkinComponent(getComponentName(JudgedObject)), _ => new DefaultHitExplosion(JudgedObject, result)); Child = skinnable = new SkinnableDrawable(new TaikoSkinComponent(getComponentName(result)), _ => new DefaultHitExplosion(JudgedObject, result));
} }
private TaikoSkinComponents getComponentName(DrawableHitObject judgedObject) private static TaikoSkinComponents getComponentName(HitResult result)
{ {
switch (result) switch (result)
{ {
case HitResult.Miss: case HitResult.Miss:
return TaikoSkinComponents.TaikoExplosionMiss; return TaikoSkinComponents.TaikoExplosionMiss;
case HitResult.Good: case HitResult.Ok:
return useStrongExplosion(judgedObject) return TaikoSkinComponents.TaikoExplosionOk;
? TaikoSkinComponents.TaikoExplosionGoodStrong
: TaikoSkinComponents.TaikoExplosionGood;
case HitResult.Great: case HitResult.Great:
return useStrongExplosion(judgedObject) return TaikoSkinComponents.TaikoExplosionGreat;
? TaikoSkinComponents.TaikoExplosionGreatStrong
: TaikoSkinComponents.TaikoExplosionGreat;
} }
throw new ArgumentOutOfRangeException(nameof(judgedObject), "Invalid result type"); throw new ArgumentOutOfRangeException(nameof(result), $"Invalid result type: {result}");
}
private bool useStrongExplosion(DrawableHitObject judgedObject)
{
if (!(judgedObject.HitObject is Hit))
return false;
if (!(judgedObject.NestedHitObjects.SingleOrDefault() is DrawableStrongNestedHit nestedHit))
return false;
return judgedObject.Result.Type == nestedHit.Result.Type;
} }
/// <summary> /// <summary>

View File

@ -163,16 +163,14 @@ namespace osu.Game.Rulesets.Taiko.UI
target = centreHit; target = centreHit;
back = centre; back = centre;
if (gameplayClock?.IsSeeking != true) drumSample.Centre?.Play();
drumSample.Centre?.Play();
} }
else if (action == RimAction) else if (action == RimAction)
{ {
target = rimHit; target = rimHit;
back = rim; back = rim;
if (gameplayClock?.IsSeeking != true) drumSample.Rim?.Play();
drumSample.Rim?.Play();
} }
if (target != null) if (target != null)

View File

@ -215,16 +215,12 @@ namespace osu.Game.Rulesets.Taiko.UI
private void addDrumRollHit(DrawableDrumRollTick drawableTick) => private void addDrumRollHit(DrawableDrumRollTick drawableTick) =>
drumRollHitContainer.Add(new DrawableFlyingHit(drawableTick)); drumRollHitContainer.Add(new DrawableFlyingHit(drawableTick));
/// <remarks> private void addExplosion(DrawableHitObject drawableObject, HitResult result, HitType type)
/// As legacy skins have different explosions for singular and double strong hits,
/// explosion addition is scheduled to ensure that both hits are processed if they occur on the same frame.
/// </remarks>
private void addExplosion(DrawableHitObject drawableObject, HitResult result, HitType type) => Schedule(() =>
{ {
hitExplosionContainer.Add(new HitExplosion(drawableObject, result)); hitExplosionContainer.Add(new HitExplosion(drawableObject, result));
if (drawableObject.HitObject.Kiai) if (drawableObject.HitObject.Kiai)
kiaiExplosionContainer.Add(new KiaiHitExplosion(drawableObject, type)); kiaiExplosionContainer.Add(new KiaiHitExplosion(drawableObject, type));
}); }
private class ProxyContainer : LifetimeManagementContainer private class ProxyContainer : LifetimeManagementContainer
{ {

View File

@ -12,6 +12,14 @@ namespace osu.Game.Tests.Editing
[TestFixture] [TestFixture]
public class EditorChangeHandlerTest public class EditorChangeHandlerTest
{ {
private int stateChangedFired;
[SetUp]
public void SetUp()
{
stateChangedFired = 0;
}
[Test] [Test]
public void TestSaveRestoreState() public void TestSaveRestoreState()
{ {
@ -23,6 +31,8 @@ namespace osu.Game.Tests.Editing
addArbitraryChange(beatmap); addArbitraryChange(beatmap);
handler.SaveState(); handler.SaveState();
Assert.That(stateChangedFired, Is.EqualTo(1));
Assert.That(handler.CanUndo.Value, Is.True); Assert.That(handler.CanUndo.Value, Is.True);
Assert.That(handler.CanRedo.Value, Is.False); Assert.That(handler.CanRedo.Value, Is.False);
@ -30,6 +40,8 @@ namespace osu.Game.Tests.Editing
Assert.That(handler.CanUndo.Value, Is.False); Assert.That(handler.CanUndo.Value, Is.False);
Assert.That(handler.CanRedo.Value, Is.True); Assert.That(handler.CanRedo.Value, Is.True);
Assert.That(stateChangedFired, Is.EqualTo(2));
} }
[Test] [Test]
@ -45,6 +57,7 @@ namespace osu.Game.Tests.Editing
Assert.That(handler.CanUndo.Value, Is.True); Assert.That(handler.CanUndo.Value, Is.True);
Assert.That(handler.CanRedo.Value, Is.False); Assert.That(handler.CanRedo.Value, Is.False);
Assert.That(stateChangedFired, Is.EqualTo(1));
string hash = handler.CurrentStateHash; string hash = handler.CurrentStateHash;
@ -52,6 +65,7 @@ namespace osu.Game.Tests.Editing
handler.SaveState(); handler.SaveState();
Assert.That(hash, Is.EqualTo(handler.CurrentStateHash)); Assert.That(hash, Is.EqualTo(handler.CurrentStateHash));
Assert.That(stateChangedFired, Is.EqualTo(1));
handler.RestoreState(-1); handler.RestoreState(-1);
@ -60,6 +74,7 @@ namespace osu.Game.Tests.Editing
// we should only be able to restore once even though we saved twice. // we should only be able to restore once even though we saved twice.
Assert.That(handler.CanUndo.Value, Is.False); Assert.That(handler.CanUndo.Value, Is.False);
Assert.That(handler.CanRedo.Value, Is.True); Assert.That(handler.CanRedo.Value, Is.True);
Assert.That(stateChangedFired, Is.EqualTo(2));
} }
[Test] [Test]
@ -71,6 +86,8 @@ namespace osu.Game.Tests.Editing
for (int i = 0; i < EditorChangeHandler.MAX_SAVED_STATES; i++) for (int i = 0; i < EditorChangeHandler.MAX_SAVED_STATES; i++)
{ {
Assert.That(stateChangedFired, Is.EqualTo(i));
addArbitraryChange(beatmap); addArbitraryChange(beatmap);
handler.SaveState(); handler.SaveState();
} }
@ -114,7 +131,10 @@ namespace osu.Game.Tests.Editing
{ {
var beatmap = new EditorBeatmap(new Beatmap()); var beatmap = new EditorBeatmap(new Beatmap());
return (new EditorChangeHandler(beatmap), beatmap); var changeHandler = new EditorChangeHandler(beatmap);
changeHandler.OnStateChange += () => stateChangedFired++;
return (changeHandler, beatmap);
} }
private void addArbitraryChange(EditorBeatmap beatmap) private void addArbitraryChange(EditorBeatmap beatmap)

View File

@ -170,7 +170,7 @@ namespace osu.Game.Tests.Gameplay
beatmap.HitObjects.Add(new JudgeableHitObject { StartTime = 0 }); beatmap.HitObjects.Add(new JudgeableHitObject { StartTime = 0 });
for (double time = 0; time < 5000; time += 100) for (double time = 0; time < 5000; time += 100)
beatmap.HitObjects.Add(new JudgeableHitObject(false) { StartTime = time }); beatmap.HitObjects.Add(new JudgeableHitObject(HitResult.LargeBonus) { StartTime = time });
beatmap.HitObjects.Add(new JudgeableHitObject { StartTime = 5000 }); beatmap.HitObjects.Add(new JudgeableHitObject { StartTime = 5000 });
createProcessor(beatmap); createProcessor(beatmap);
@ -236,23 +236,23 @@ namespace osu.Game.Tests.Gameplay
private class JudgeableHitObject : HitObject private class JudgeableHitObject : HitObject
{ {
private readonly bool affectsCombo; private readonly HitResult maxResult;
public JudgeableHitObject(bool affectsCombo = true) public JudgeableHitObject(HitResult maxResult = HitResult.Perfect)
{ {
this.affectsCombo = affectsCombo; this.maxResult = maxResult;
} }
public override Judgement CreateJudgement() => new TestJudgement(affectsCombo); public override Judgement CreateJudgement() => new TestJudgement(maxResult);
protected override HitWindows CreateHitWindows() => new HitWindows(); protected override HitWindows CreateHitWindows() => new HitWindows();
private class TestJudgement : Judgement private class TestJudgement : Judgement
{ {
public override bool AffectsCombo { get; } public override HitResult MaxResult { get; }
public TestJudgement(bool affectsCombo) public TestJudgement(HitResult maxResult)
{ {
AffectsCombo = affectsCombo; MaxResult = maxResult;
} }
} }
} }
@ -263,7 +263,7 @@ namespace osu.Game.Tests.Gameplay
public double Duration { get; set; } = 5000; public double Duration { get; set; } = 5000;
public JudgeableLongHitObject() public JudgeableLongHitObject()
: base(false) : base(HitResult.LargeBonus)
{ {
} }

View File

@ -17,13 +17,13 @@ namespace osu.Game.Tests.Gameplay
[Test] [Test]
public void TestNoScoreIncreaseFromMiss() public void TestNoScoreIncreaseFromMiss()
{ {
var beatmap = new Beatmap<TestHitObject> { HitObjects = { new TestHitObject() } }; var beatmap = new Beatmap<HitObject> { HitObjects = { new HitObject() } };
var scoreProcessor = new ScoreProcessor(); var scoreProcessor = new ScoreProcessor();
scoreProcessor.ApplyBeatmap(beatmap); scoreProcessor.ApplyBeatmap(beatmap);
// Apply a miss judgement // Apply a miss judgement
scoreProcessor.ApplyResult(new JudgementResult(new TestHitObject(), new TestJudgement()) { Type = HitResult.Miss }); scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new TestJudgement()) { Type = HitResult.Miss });
Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(0.0)); Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(0.0));
} }
@ -31,37 +31,25 @@ namespace osu.Game.Tests.Gameplay
[Test] [Test]
public void TestOnlyBonusScore() public void TestOnlyBonusScore()
{ {
var beatmap = new Beatmap<TestBonusHitObject> { HitObjects = { new TestBonusHitObject() } }; var beatmap = new Beatmap<HitObject> { HitObjects = { new HitObject() } };
var scoreProcessor = new ScoreProcessor(); var scoreProcessor = new ScoreProcessor();
scoreProcessor.ApplyBeatmap(beatmap); scoreProcessor.ApplyBeatmap(beatmap);
// Apply a judgement // Apply a judgement
scoreProcessor.ApplyResult(new JudgementResult(new TestBonusHitObject(), new TestBonusJudgement()) { Type = HitResult.Perfect }); scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new TestJudgement(HitResult.LargeBonus)) { Type = HitResult.LargeBonus });
Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(100)); Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(Judgement.LARGE_BONUS_SCORE));
}
private class TestHitObject : HitObject
{
public override Judgement CreateJudgement() => new TestJudgement();
} }
private class TestJudgement : Judgement private class TestJudgement : Judgement
{ {
protected override int NumericResultFor(HitResult result) => 100; public override HitResult MaxResult { get; }
}
private class TestBonusHitObject : HitObject public TestJudgement(HitResult maxResult = HitResult.Perfect)
{ {
public override Judgement CreateJudgement() => new TestBonusJudgement(); MaxResult = maxResult;
} }
private class TestBonusJudgement : Judgement
{
public override bool AffectsCombo => false;
protected override int NumericResultFor(HitResult result) => 100;
} }
} }
} }

View File

@ -139,6 +139,22 @@ namespace osu.Game.Tests.NonVisual
Assert.That(cpi.Groups.Count, Is.EqualTo(0)); Assert.That(cpi.Groups.Count, Is.EqualTo(0));
} }
[Test]
public void TestRemoveGroupAlsoRemovedControlPoints()
{
var cpi = new ControlPointInfo();
var group = cpi.GroupAt(1000, true);
group.Add(new SampleControlPoint());
Assert.That(cpi.SamplePoints.Count, Is.EqualTo(1));
cpi.RemoveGroup(group);
Assert.That(cpi.SamplePoints.Count, Is.EqualTo(0));
}
[Test] [Test]
public void TestAddControlPointToGroup() public void TestAddControlPointToGroup()
{ {

View File

@ -0,0 +1,39 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Bindables;
using osu.Framework.Timing;
using osu.Game.Screens.Play;
namespace osu.Game.Tests.NonVisual
{
[TestFixture]
public class GameplayClockTest
{
[TestCase(0)]
[TestCase(1)]
public void TestTrueGameplayRateWithZeroAdjustment(double underlyingClockRate)
{
var framedClock = new FramedClock(new ManualClock { Rate = underlyingClockRate });
var gameplayClock = new TestGameplayClock(framedClock);
gameplayClock.MutableNonGameplayAdjustments.Add(new BindableDouble());
Assert.That(gameplayClock.TrueGameplayRate, Is.EqualTo(0));
}
private class TestGameplayClock : GameplayClock
{
public List<Bindable<double>> MutableNonGameplayAdjustments { get; } = new List<Bindable<double>>();
public override IEnumerable<Bindable<double>> NonGameplayAdjustments => MutableNonGameplayAdjustments;
public TestGameplayClock(IFrameBasedClock underlyingClock)
: base(underlyingClock)
{
}
}
}
}

View File

@ -35,10 +35,10 @@ namespace osu.Game.Tests.Rulesets.Scoring
} }
[TestCase(ScoringMode.Standardised, HitResult.Meh, 750_000)] [TestCase(ScoringMode.Standardised, HitResult.Meh, 750_000)]
[TestCase(ScoringMode.Standardised, HitResult.Good, 800_000)] [TestCase(ScoringMode.Standardised, HitResult.Ok, 800_000)]
[TestCase(ScoringMode.Standardised, HitResult.Great, 1_000_000)] [TestCase(ScoringMode.Standardised, HitResult.Great, 1_000_000)]
[TestCase(ScoringMode.Classic, HitResult.Meh, 50)] [TestCase(ScoringMode.Classic, HitResult.Meh, 50)]
[TestCase(ScoringMode.Classic, HitResult.Good, 100)] [TestCase(ScoringMode.Classic, HitResult.Ok, 100)]
[TestCase(ScoringMode.Classic, HitResult.Great, 300)] [TestCase(ScoringMode.Classic, HitResult.Great, 300)]
public void TestSingleOsuHit(ScoringMode scoringMode, HitResult hitResult, int expectedScore) public void TestSingleOsuHit(ScoringMode scoringMode, HitResult hitResult, int expectedScore)
{ {

View File

@ -5,6 +5,8 @@ using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Screens;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets; using osu.Game.Rulesets;
@ -22,6 +24,9 @@ namespace osu.Game.Tests.Visual.Editing
protected override bool EditorComponentsReady => Editor.ChildrenOfType<SetupScreen>().SingleOrDefault()?.IsLoaded == true; protected override bool EditorComponentsReady => Editor.ChildrenOfType<SetupScreen>().SingleOrDefault()?.IsLoaded == true;
[Resolved]
private BeatmapManager beatmapManager { get; set; }
public override void SetUpSteps() public override void SetUpSteps()
{ {
AddStep("set dummy", () => Beatmap.Value = new DummyWorkingBeatmap(Audio, null)); AddStep("set dummy", () => Beatmap.Value = new DummyWorkingBeatmap(Audio, null));
@ -38,6 +43,15 @@ namespace osu.Game.Tests.Visual.Editing
{ {
AddStep("save beatmap", () => Editor.Save()); AddStep("save beatmap", () => Editor.Save());
AddAssert("new beatmap persisted", () => EditorBeatmap.BeatmapInfo.ID > 0); AddAssert("new beatmap persisted", () => EditorBeatmap.BeatmapInfo.ID > 0);
AddAssert("new beatmap in database", () => beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID)?.DeletePending == false);
}
[Test]
public void TestExitWithoutSave()
{
AddStep("exit without save", () => Editor.Exit());
AddUntilStep("wait for exit", () => !Editor.IsCurrentScreen());
AddAssert("new beatmap not persisted", () => beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID)?.DeletePending == true);
} }
[Test] [Test]

View File

@ -0,0 +1,50 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics.Audio;
using osu.Framework.Testing;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Objects.Drawables;
namespace osu.Game.Tests.Visual.Editing
{
public class TestSceneEditorSamplePlayback : EditorTestScene
{
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
[Test]
public void TestSlidingSampleStopsOnSeek()
{
DrawableSlider slider = null;
DrawableSample[] loopingSamples = null;
DrawableSample[] onceOffSamples = null;
AddStep("get first slider", () =>
{
slider = Editor.ChildrenOfType<DrawableSlider>().OrderBy(s => s.HitObject.StartTime).First();
onceOffSamples = slider.ChildrenOfType<DrawableSample>().Where(s => !s.Looping).ToArray();
loopingSamples = slider.ChildrenOfType<DrawableSample>().Where(s => s.Looping).ToArray();
});
AddStep("start playback", () => EditorClock.Start());
AddUntilStep("wait for slider sliding then seek", () =>
{
if (!slider.Tracking.Value)
return false;
if (!loopingSamples.Any(s => s.Playing))
return false;
EditorClock.Seek(20000);
return true;
});
AddAssert("non-looping samples are playing", () => onceOffSamples.Length == 4 && loopingSamples.All(s => s.Played || s.Playing));
AddAssert("looping samples are not playing", () => loopingSamples.Length == 1 && loopingSamples.All(s => s.Played && !s.Playing));
}
}
}

View File

@ -0,0 +1,61 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics.Audio;
using osu.Framework.Testing;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play;
using osu.Game.Skinning;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneGameplaySamplePlayback : PlayerTestScene
{
[Test]
public void TestAllSamplesStopDuringSeek()
{
DrawableSlider slider = null;
DrawableSample[] samples = null;
ISamplePlaybackDisabler gameplayClock = null;
AddStep("get variables", () =>
{
gameplayClock = Player.ChildrenOfType<FrameStabilityContainer>().First().GameplayClock;
slider = Player.ChildrenOfType<DrawableSlider>().OrderBy(s => s.HitObject.StartTime).First();
samples = slider.ChildrenOfType<DrawableSample>().ToArray();
});
AddUntilStep("wait for slider sliding then seek", () =>
{
if (!slider.Tracking.Value)
return false;
if (!samples.Any(s => s.Playing))
return false;
Player.ChildrenOfType<GameplayClockContainer>().First().Seek(40000);
return true;
});
AddAssert("sample playback disabled", () => gameplayClock.SamplePlaybackDisabled.Value);
// because we are in frame stable context, it's quite likely that not all samples are "played" at this point.
// the important thing is that at least one started, and that sample has since stopped.
AddAssert("no samples are playing", () => Player.ChildrenOfType<PausableSkinnableSound>().All(s => !s.IsPlaying));
AddAssert("sample playback still disabled", () => gameplayClock.SamplePlaybackDisabled.Value);
AddUntilStep("seek finished, sample playback enabled", () => !gameplayClock.SamplePlaybackDisabled.Value);
AddUntilStep("any sample is playing", () => Player.ChildrenOfType<PausableSkinnableSound>().Any(s => s.IsPlaying));
}
protected override bool Autoplay => true;
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
}
}

View File

@ -98,7 +98,7 @@ namespace osu.Game.Tests.Visual.Gameplay
Children = new[] Children = new[]
{ {
new OsuSpriteText { Text = $@"Great: {hitWindows?.WindowFor(HitResult.Great)}" }, new OsuSpriteText { Text = $@"Great: {hitWindows?.WindowFor(HitResult.Great)}" },
new OsuSpriteText { Text = $@"Good: {hitWindows?.WindowFor(HitResult.Good)}" }, new OsuSpriteText { Text = $@"Good: {hitWindows?.WindowFor(HitResult.Ok)}" },
new OsuSpriteText { Text = $@"Meh: {hitWindows?.WindowFor(HitResult.Meh)}" }, new OsuSpriteText { Text = $@"Meh: {hitWindows?.WindowFor(HitResult.Meh)}" },
} }
}); });

View File

@ -28,7 +28,7 @@ namespace osu.Game.Tournament.Components
{ {
base.Bindable = new Bindable<string>(); base.Bindable = new Bindable<string>();
((OsuTextBox)Control).OnCommit = (sender, newText) => ((OsuTextBox)Control).OnCommit += (sender, newText) =>
{ {
try try
{ {

View File

@ -158,6 +158,9 @@ namespace osu.Game.Beatmaps.ControlPoints
public void RemoveGroup(ControlPointGroup group) public void RemoveGroup(ControlPointGroup group)
{ {
foreach (var item in group.ControlPoints.ToArray())
group.Remove(item);
group.ItemAdded -= groupItemAdded; group.ItemAdded -= groupItemAdded;
group.ItemRemoved -= groupItemRemoved; group.ItemRemoved -= groupItemRemoved;

View File

@ -86,13 +86,24 @@ namespace osu.Game.Graphics.Backgrounds
/// </summary> /// </summary>
public float Velocity = 1; public float Velocity = 1;
private readonly Random stableRandom;
private float nextRandom() => (float)(stableRandom?.NextDouble() ?? RNG.NextSingle());
private readonly SortedList<TriangleParticle> parts = new SortedList<TriangleParticle>(Comparer<TriangleParticle>.Default); private readonly SortedList<TriangleParticle> parts = new SortedList<TriangleParticle>(Comparer<TriangleParticle>.Default);
private IShader shader; private IShader shader;
private readonly Texture texture; private readonly Texture texture;
public Triangles() /// <summary>
/// Construct a new triangle visualisation.
/// </summary>
/// <param name="seed">An optional seed to stabilise random positions / attributes. Note that this does not guarantee stable playback when seeking in time.</param>
public Triangles(int? seed = null)
{ {
if (seed != null)
stableRandom = new Random(seed.Value);
texture = Texture.WhitePixel; texture = Texture.WhitePixel;
} }
@ -175,8 +186,8 @@ namespace osu.Game.Graphics.Backgrounds
{ {
TriangleParticle particle = CreateTriangle(); TriangleParticle particle = CreateTriangle();
particle.Position = new Vector2(RNG.NextSingle(), randomY ? RNG.NextSingle() : 1); particle.Position = new Vector2(nextRandom(), randomY ? nextRandom() : 1);
particle.ColourShade = RNG.NextSingle(); particle.ColourShade = nextRandom();
particle.Colour = CreateTriangleShade(particle.ColourShade); particle.Colour = CreateTriangleShade(particle.ColourShade);
return particle; return particle;
@ -191,8 +202,8 @@ namespace osu.Game.Graphics.Backgrounds
const float std_dev = 0.16f; const float std_dev = 0.16f;
const float mean = 0.5f; const float mean = 0.5f;
float u1 = 1 - RNG.NextSingle(); //uniform(0,1] random floats float u1 = 1 - nextRandom(); //uniform(0,1] random floats
float u2 = 1 - RNG.NextSingle(); float u2 = 1 - nextRandom();
float randStdNormal = (float)(Math.Sqrt(-2.0 * Math.Log(u1)) * Math.Sin(2.0 * Math.PI * u2)); // random normal(0,1) float randStdNormal = (float)(Math.Sqrt(-2.0 * Math.Log(u1)) * Math.Sin(2.0 * Math.PI * u2)); // random normal(0,1)
var scale = Math.Max(triangleScale * (mean + std_dev * randStdNormal), 0.1f); // random normal(mean,stdDev^2) var scale = Math.Max(triangleScale * (mean + std_dev * randStdNormal), 0.1f); // random normal(mean,stdDev^2)

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