1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-15 02:42:54 +08:00

Merge branch 'master' into legacy-SpinnerNoBlink

This commit is contained in:
Dan Balasescu 2020-10-01 20:34:23 +09:00 committed by GitHub
commit df07ca445f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
150 changed files with 1737 additions and 895 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.925.0" /> <PackageReference Include="ppy.osu.Framework.Android" Version="2020.930.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

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

@ -31,6 +31,9 @@ namespace osu.Game.Rulesets.Catch.Replays
public override Replay Generate() public override Replay Generate()
{ {
if (Beatmap.HitObjects.Count == 0)
return Replay;
// todo: add support for HT DT // todo: add support for HT DT
const double dash_speed = Catcher.BASE_SPEED; const double dash_speed = Catcher.BASE_SPEED;
const double movement_speed = dash_speed / 2; const double movement_speed = dash_speed / 2;

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,7 +33,7 @@ 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.Type == HitResult.Miss)
@ -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

@ -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

@ -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

@ -2,34 +2,10 @@
// 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.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Judgements namespace osu.Game.Rulesets.Mania.Judgements
{ {
public class ManiaJudgement : Judgement public class ManiaJudgement : Judgement
{ {
protected override int NumericResultFor(HitResult result)
{
switch (result)
{
default:
return 0;
case HitResult.Meh:
return 50;
case HitResult.Ok:
return 100;
case HitResult.Good:
return 200;
case HitResult.Great:
return 300;
case HitResult.Perfect:
return 350;
}
}
} }
} }

View File

@ -239,7 +239,7 @@ 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();
} }

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

@ -46,6 +46,9 @@ namespace osu.Game.Rulesets.Mania.Replays
public override Replay Generate() public override Replay Generate()
{ {
if (Beatmap.HitObjects.Count == 0)
return Replay;
var pointGroups = generateActionPoints().GroupBy(a => a.Time).OrderBy(g => g.First().Time); var pointGroups = generateActionPoints().GroupBy(a => a.Time).OrderBy(g => g.First().Time);
var actions = new List<ManiaAction>(); var actions = new List<ManiaAction>();

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

@ -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

@ -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.IgnoreHit && judgementResults.First().Type == HitResult.Miss;
private bool assertMidSliderJudgements() => judgementResults[^2].Type == HitResult.Great; private bool assertMidSliderJudgements() => judgementResults[^2].Type == HitResult.IgnoreHit;
private bool assertMidSliderJudgementFail() => judgementResults[^2].Type == HitResult.Miss; private bool assertMidSliderJudgementFail() => judgementResults[^2].Type == HitResult.IgnoreMiss;
private ScoreAccessibleReplayPlayer currentPlayer; private ScoreAccessibleReplayPlayer currentPlayer;

View File

@ -7,7 +7,6 @@ using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Framework.Timing; using osu.Framework.Timing;
@ -146,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);
@ -194,13 +193,7 @@ namespace osu.Game.Rulesets.Osu.Tests
addSeekStep(0); addSeekStep(0);
AddStep("adjust track rate", () => MusicController.CurrentTrack.AddAdjustment(AdjustableProperty.Tempo, new BindableDouble(rate))); AddStep("adjust track rate", () => Player.GameplayClockContainer.UserPlaybackRate.Value = rate);
// autoplay replay frames use track time;
// if a spin takes 1000ms in track time and we're playing with a 2x rate adjustment, the spin will take 500ms of *real* time.
// therefore we need to apply the rate adjustment to the replay itself to change from track time to real time,
// as real time is what we care about for spinners
// (so we're making the spin take 1000ms in real time *always*, regardless of the track clock's rate).
transformReplay(replay => applyRateAdjustment(replay, rate));
addSeekStep(1000); addSeekStep(1000);
AddAssert("progress almost same", () => Precision.AlmostEquals(expectedProgress, drawableSpinner.Progress, 0.05)); AddAssert("progress almost same", () => Precision.AlmostEquals(expectedProgress, drawableSpinner.Progress, 0.05));

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

@ -15,6 +15,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{ {
private readonly ManualSliderBody body; private readonly ManualSliderBody body;
/// <summary>
/// Offset in absolute (local) coordinates from the start of the curve.
/// </summary>
public Vector2 PathStartLocation => body.PathOffset;
public SliderBodyPiece() public SliderBodyPiece()
{ {
InternalChild = body = new ManualSliderBody InternalChild = body = new ManualSliderBody

View File

@ -190,7 +190,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
new OsuMenuItem("Add control point", MenuItemType.Standard, () => addControlPoint(rightClickPosition)), new OsuMenuItem("Add control point", MenuItemType.Standard, () => addControlPoint(rightClickPosition)),
}; };
public override Vector2 ScreenSpaceSelectionPoint => ((DrawableSlider)DrawableObject).HeadCircle.ScreenSpaceDrawQuad.Centre; public override Vector2 ScreenSpaceSelectionPoint => BodyPiece.ToScreenSpace(BodyPiece.PathStartLocation);
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => BodyPiece.ReceivePositionalInputAt(screenSpacePos); public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => BodyPiece.ReceivePositionalInputAt(screenSpacePos);

View File

@ -8,6 +8,7 @@ using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osuTK; using osuTK;
@ -20,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Edit
/// Hit objects are intentionally made to fade out at a constant slower rate than in gameplay. /// Hit objects are intentionally made to fade out at a constant slower rate than in gameplay.
/// This allows a mapper to gain better historical context and use recent hitobjects as reference / snap points. /// This allows a mapper to gain better historical context and use recent hitobjects as reference / snap points.
/// </summary> /// </summary>
private const double editor_hit_object_fade_out_extension = 500; private const double editor_hit_object_fade_out_extension = 700;
public DrawableOsuEditRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods) public DrawableOsuEditRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods)
: base(ruleset, beatmap, mods) : base(ruleset, beatmap, mods)
@ -32,20 +33,37 @@ namespace osu.Game.Rulesets.Osu.Edit
private void updateState(DrawableHitObject hitObject, ArmedState state) private void updateState(DrawableHitObject hitObject, ArmedState state)
{ {
switch (state) if (state == ArmedState.Idle)
return;
// adjust the visuals of certain object types to make them stay on screen for longer than usual.
switch (hitObject)
{ {
case ArmedState.Miss: default:
// Get the existing fade out transform // there are quite a few drawable hit types we don't want to extent (spinners, ticks etc.)
var existing = hitObject.Transforms.LastOrDefault(t => t.TargetMember == nameof(Alpha)); return;
if (existing == null)
return;
hitObject.RemoveTransform(existing); case DrawableSlider _:
// no specifics to sliders but let them fade slower below.
break;
using (hitObject.BeginAbsoluteSequence(existing.StartTime)) case DrawableHitCircle circle: // also handles slider heads
hitObject.FadeOut(editor_hit_object_fade_out_extension).Expire(); circle.ApproachCircle
.FadeOutFromOne(editor_hit_object_fade_out_extension)
.Expire();
break; break;
} }
// Get the existing fade out transform
var existing = hitObject.Transforms.LastOrDefault(t => t.TargetMember == nameof(Alpha));
if (existing == null)
return;
hitObject.RemoveTransform(existing);
using (hitObject.BeginAbsoluteSequence(existing.StartTime))
hitObject.FadeOut(editor_hit_object_fade_out_extension).Expire();
} }
protected override Playfield CreatePlayfield() => new OsuPlayfieldNoCursor(); protected override Playfield CreatePlayfield() => new OsuPlayfieldNoCursor();

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

@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Tracking.BindValueChanged(updateSlidingSample); Tracking.BindValueChanged(updateSlidingSample);
} }
private SkinnableSound slidingSample; private PausableSkinnableSound slidingSample;
protected override void LoadSamples() protected override void LoadSamples()
{ {
@ -103,19 +103,22 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
var clone = HitObject.SampleControlPoint.ApplyTo(firstSample); var clone = HitObject.SampleControlPoint.ApplyTo(firstSample);
clone.Name = "sliderslide"; clone.Name = "sliderslide";
AddInternal(slidingSample = new SkinnableSound(clone) AddInternal(slidingSample = new PausableSkinnableSound(clone)
{ {
Looping = true Looping = true
}); });
} }
} }
public override void StopAllSamples()
{
base.StopAllSamples();
slidingSample?.Stop();
}
private void updateSlidingSample(ValueChangedEvent<bool> tracking) private void updateSlidingSample(ValueChangedEvent<bool> tracking)
{ {
// note that samples will not start playing if exiting a seek operation in the middle of a slider. if (tracking.NewValue)
// may be something we want to address at a later point, but not so easy to make happen right now
// (SkinnableSound would need to expose whether the sample is already playing and this logic would need to run in Update).
if (tracking.NewValue && ShouldPlaySamples)
slidingSample?.Play(); slidingSample?.Play();
else else
slidingSample?.Stop(); slidingSample?.Stop();

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

@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
isSpinning.BindValueChanged(updateSpinningSample); isSpinning.BindValueChanged(updateSpinningSample);
} }
private SkinnableSound spinningSample; private PausableSkinnableSound spinningSample;
private const float spinning_sample_initial_frequency = 1.0f; private const float spinning_sample_initial_frequency = 1.0f;
private const float spinning_sample_modulated_base_frequency = 0.5f; private const float spinning_sample_modulated_base_frequency = 0.5f;
@ -102,7 +102,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
var clone = HitObject.SampleControlPoint.ApplyTo(firstSample); var clone = HitObject.SampleControlPoint.ApplyTo(firstSample);
clone.Name = "spinnerspin"; clone.Name = "spinnerspin";
AddInternal(spinningSample = new SkinnableSound(clone) AddInternal(spinningSample = new PausableSkinnableSound(clone)
{ {
Volume = { Value = 0 }, Volume = { Value = 0 },
Looping = true, Looping = true,
@ -113,10 +113,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private void updateSpinningSample(ValueChangedEvent<bool> tracking) private void updateSpinningSample(ValueChangedEvent<bool> tracking)
{ {
// note that samples will not start playing if exiting a seek operation in the middle of a spinner. if (tracking.NewValue)
// may be something we want to address at a later point, but not so easy to make happen right now
// (SkinnableSound would need to expose whether the sample is already playing and this logic would need to run in Update).
if (tracking.NewValue && ShouldPlaySamples)
{ {
spinningSample?.Play(); spinningSample?.Play();
spinningSample?.VolumeTo(1, 200); spinningSample?.VolumeTo(1, 200);
@ -127,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);
@ -217,7 +220,7 @@ 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)

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

@ -3,7 +3,6 @@
using System; using System;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
@ -93,7 +92,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
base.LoadComplete(); base.LoadComplete();
drawableSpinner.RotationTracker.Complete.BindValueChanged(complete => updateComplete(complete.NewValue, 200)); drawableSpinner.RotationTracker.Complete.BindValueChanged(complete => updateComplete(complete.NewValue, 200));
drawableSpinner.State.BindValueChanged(updateStateTransforms, true); drawableSpinner.ApplyCustomUpdateState += updateStateTransforms;
} }
protected override void Update() protected override void Update()
@ -123,7 +122,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
mainContainer.Rotation = drawableSpinner.RotationTracker.Rotation; mainContainer.Rotation = drawableSpinner.RotationTracker.Rotation;
} }
private void updateStateTransforms(ValueChangedEvent<ArmedState> state) private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state)
{ {
centre.ScaleTo(0); centre.ScaleTo(0);
mainContainer.ScaleTo(0); mainContainer.ScaleTo(0);
@ -144,11 +143,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
} }
// 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.NewValue == ArmedState.Hit, 0); updateComplete(state == ArmedState.Hit, 0);
using (BeginDelayedSequence(spinner.Duration, true)) using (BeginDelayedSequence(spinner.Duration, true))
{ {
switch (state.NewValue) switch (state)
{ {
case ArmedState.Hit: case ArmedState.Hit:
this.ScaleTo(Scale * 1.2f, 320, Easing.Out); this.ScaleTo(Scale * 1.2f, 320, Easing.Out);
@ -185,5 +184,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
return true; return true;
} }
} }
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (drawableSpinner != null)
drawableSpinner.ApplyCustomUpdateState -= updateStateTransforms;
}
} }
} }

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

@ -2,11 +2,13 @@
// 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 osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Screens.Play;
using osuTK; using osuTK;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
@ -77,6 +79,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
private bool rotationTransferred; private bool rotationTransferred;
[Resolved(canBeNull: true)]
private GameplayClock gameplayClock { get; set; }
protected override void Update() protected override void Update()
{ {
base.Update(); base.Update();
@ -126,7 +131,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
currentRotation += angle; currentRotation += angle;
// rate has to be applied each frame, because it's not guaranteed to be constant throughout playback // rate has to be applied each frame, because it's not guaranteed to be constant throughout playback
// (see: ModTimeRamp) // (see: ModTimeRamp)
RateAdjustedRotation += (float)(Math.Abs(angle) * Clock.Rate); RateAdjustedRotation += (float)(Math.Abs(angle) * (gameplayClock?.TrueGameplayRate ?? Clock.Rate));
} }
} }
} }

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.IgnoreHit;
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

@ -72,6 +72,9 @@ namespace osu.Game.Rulesets.Osu.Replays
public override Replay Generate() public override Replay Generate()
{ {
if (Beatmap.HitObjects.Count == 0)
return Replay;
buttonIndex = 0; buttonIndex = 0;
AddFrameToReplay(new OsuReplayFrame(-100000, new Vector2(256, 500))); AddFrameToReplay(new OsuReplayFrame(-100000, new Vector2(256, 500)));
@ -134,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

@ -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.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
@ -72,10 +71,10 @@ namespace osu.Game.Rulesets.Osu.Skinning
base.LoadComplete(); base.LoadComplete();
this.FadeOut(); this.FadeOut();
drawableSpinner.State.BindValueChanged(updateStateTransforms, true); drawableSpinner.ApplyCustomUpdateState += updateStateTransforms;
} }
private void updateStateTransforms(ValueChangedEvent<ArmedState> state) private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state)
{ {
var spinner = (Spinner)drawableSpinner.HitObject; var spinner = (Spinner)drawableSpinner.HitObject;
@ -95,5 +94,13 @@ namespace osu.Game.Rulesets.Osu.Skinning
Scale = new Vector2(final_scale * (0.8f + (float)Interpolation.ApplyEasing(Easing.Out, drawableSpinner.Progress) * 0.2f)); Scale = new Vector2(final_scale * (0.8f + (float)Interpolation.ApplyEasing(Easing.Out, drawableSpinner.Progress) * 0.2f));
} }
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (drawableSpinner != null)
drawableSpinner.ApplyCustomUpdateState -= updateStateTransforms;
}
} }
} }

View File

@ -3,7 +3,6 @@
using System; using System;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
@ -90,10 +89,10 @@ namespace osu.Game.Rulesets.Osu.Skinning
base.LoadComplete(); base.LoadComplete();
this.FadeOut(); this.FadeOut();
drawableSpinner.State.BindValueChanged(updateStateTransforms, true); drawableSpinner.ApplyCustomUpdateState += updateStateTransforms;
} }
private void updateStateTransforms(ValueChangedEvent<ArmedState> state) private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state)
{ {
var spinner = drawableSpinner.HitObject; var spinner = drawableSpinner.HitObject;
@ -134,5 +133,13 @@ namespace osu.Game.Rulesets.Osu.Skinning
return (float)barCount / total_bars * final_metre_height; return (float)barCount / total_bars * final_metre_height;
} }
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (drawableSpinner != null)
drawableSpinner.ApplyCustomUpdateState -= updateStateTransforms;
}
} }
} }

View File

@ -0,0 +1,55 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.UI;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Taiko.Tests
{
public abstract class DrawableTaikoRulesetTestScene : OsuTestScene
{
protected DrawableTaikoRuleset DrawableRuleset { get; private set; }
protected Container PlayfieldContainer { get; private set; }
[BackgroundDependencyLoader]
private void load()
{
var controlPointInfo = new ControlPointInfo();
controlPointInfo.Add(0, new TimingControlPoint());
WorkingBeatmap beatmap = CreateWorkingBeatmap(new Beatmap
{
HitObjects = new List<HitObject> { new Hit { Type = HitType.Centre } },
BeatmapInfo = new BeatmapInfo
{
BaseDifficulty = new BeatmapDifficulty(),
Metadata = new BeatmapMetadata
{
Artist = @"Unknown",
Title = @"Sample Beatmap",
AuthorString = @"peppy",
},
Ruleset = new TaikoRuleset().RulesetInfo
},
ControlPointInfo = controlPointInfo
});
Add(PlayfieldContainer = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.X,
Height = 768,
Children = new[] { DrawableRuleset = new DrawableTaikoRuleset(new TaikoRuleset(), beatmap.GetPlayableBeatmap(new TaikoRuleset().RulesetInfo)) }
});
}
}
}

View File

@ -22,6 +22,12 @@ namespace osu.Game.Rulesets.Taiko.Tests
HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
} }
protected override void UpdateInitialTransforms()
{
// base implementation in DrawableHitObject forces alpha to 1.
// suppress locally to allow hiding the visuals wherever necessary.
}
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {

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)]
@ -212,7 +212,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
foreach (var playfield in playfields) foreach (var playfield in playfields)
{ {
var hit = new DrawableTestHit(new Hit(), judgementResult.Type); var hit = new DrawableTestHit(new Hit(), judgementResult.Type);
Add(hit); playfield.Add(hit);
playfield.OnNewResult(hit, judgementResult); playfield.OnNewResult(hit, judgementResult);
} }

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)
@ -35,7 +35,9 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Children = new Drawable[] Children = new Drawable[]
{ {
hit, // the hit needs to be added to hierarchy in order for nested objects to be created correctly.
// setting zero alpha is supposed to prevent the test from looking broken.
hit.With(h => h.Alpha = 0),
new HitExplosion(hit, hit.Type) new HitExplosion(hit, hit.Type)
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,

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

@ -0,0 +1,48 @@
// 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.Testing;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Judgements;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Objects.Drawables;
using osu.Game.Rulesets.Taiko.UI;
namespace osu.Game.Rulesets.Taiko.Tests
{
[TestFixture]
public class TestSceneFlyingHits : DrawableTaikoRulesetTestScene
{
[TestCase(HitType.Centre)]
[TestCase(HitType.Rim)]
public void TestFlyingHits(HitType hitType)
{
DrawableFlyingHit flyingHit = null;
AddStep("add flying hit", () =>
{
addFlyingHit(hitType);
// flying hits all land in one common scrolling container (and stay there for rewind purposes),
// so we need to manually get the latest one.
flyingHit = this.ChildrenOfType<DrawableFlyingHit>()
.OrderByDescending(h => h.HitObject.StartTime)
.FirstOrDefault();
});
AddAssert("hit type is correct", () => flyingHit.HitObject.Type == hitType);
}
private void addFlyingHit(HitType hitType)
{
var tick = new DrumRollTick { HitWindows = HitWindows.Empty, StartTime = DrawableRuleset.Playfield.Time.Current };
DrawableDrumRollTick h;
DrawableRuleset.Playfield.Add(h = new DrawableDrumRollTick(tick) { JudgementType = hitType });
((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(tick, new TaikoDrumRollTickJudgement()) { Type = HitResult.Great });
}
}
}

View File

@ -2,11 +2,9 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Collections.Generic;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
@ -18,13 +16,12 @@ using osu.Game.Rulesets.Taiko.Judgements;
using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Objects.Drawables; using osu.Game.Rulesets.Taiko.Objects.Drawables;
using osu.Game.Rulesets.Taiko.UI; using osu.Game.Rulesets.Taiko.UI;
using osu.Game.Tests.Visual;
using osuTK; using osuTK;
namespace osu.Game.Rulesets.Taiko.Tests namespace osu.Game.Rulesets.Taiko.Tests
{ {
[TestFixture] [TestFixture]
public class TestSceneHits : OsuTestScene public class TestSceneHits : DrawableTaikoRulesetTestScene
{ {
private const double default_duration = 3000; private const double default_duration = 3000;
private const float scroll_time = 1000; private const float scroll_time = 1000;
@ -32,8 +29,6 @@ namespace osu.Game.Rulesets.Taiko.Tests
protected override double TimePerAction => default_duration * 2; protected override double TimePerAction => default_duration * 2;
private readonly Random rng = new Random(1337); private readonly Random rng = new Random(1337);
private DrawableTaikoRuleset drawableRuleset;
private Container playfieldContainer;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
@ -64,35 +59,6 @@ namespace osu.Game.Rulesets.Taiko.Tests
AddStep("Height test 4", () => changePlayfieldSize(4)); AddStep("Height test 4", () => changePlayfieldSize(4));
AddStep("Height test 5", () => changePlayfieldSize(5)); AddStep("Height test 5", () => changePlayfieldSize(5));
AddStep("Reset height", () => changePlayfieldSize(6)); AddStep("Reset height", () => changePlayfieldSize(6));
var controlPointInfo = new ControlPointInfo();
controlPointInfo.Add(0, new TimingControlPoint());
WorkingBeatmap beatmap = CreateWorkingBeatmap(new Beatmap
{
HitObjects = new List<HitObject> { new Hit { Type = HitType.Centre } },
BeatmapInfo = new BeatmapInfo
{
BaseDifficulty = new BeatmapDifficulty(),
Metadata = new BeatmapMetadata
{
Artist = @"Unknown",
Title = @"Sample Beatmap",
AuthorString = @"peppy",
},
Ruleset = new TaikoRuleset().RulesetInfo
},
ControlPointInfo = controlPointInfo
});
Add(playfieldContainer = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.X,
Height = 768,
Children = new[] { drawableRuleset = new DrawableTaikoRuleset(new TaikoRuleset(), beatmap.GetPlayableBeatmap(new TaikoRuleset().RulesetInfo)) }
});
} }
private void changePlayfieldSize(int step) private void changePlayfieldSize(int step)
@ -128,18 +94,18 @@ namespace osu.Game.Rulesets.Taiko.Tests
switch (step) switch (step)
{ {
default: default:
playfieldContainer.Delay(delay).ResizeTo(new Vector2(1, rng.Next(25, 400)), 500); PlayfieldContainer.Delay(delay).ResizeTo(new Vector2(1, rng.Next(25, 400)), 500);
break; break;
case 6: case 6:
playfieldContainer.Delay(delay).ResizeTo(new Vector2(1, TaikoPlayfield.DEFAULT_HEIGHT), 500); PlayfieldContainer.Delay(delay).ResizeTo(new Vector2(1, TaikoPlayfield.DEFAULT_HEIGHT), 500);
break; break;
} }
} }
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 });
@ -147,16 +113,16 @@ 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) };
Add(h); DrawableRuleset.Playfield.Add(h);
((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = hitResult }); ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = hitResult });
} }
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 });
@ -164,39 +130,39 @@ 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) };
Add(h); DrawableRuleset.Playfield.Add(h);
((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = hitResult }); ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = hitResult });
((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(new TestStrongNestedHit(h), new JudgementResult(new HitObject(), new TaikoStrongJudgement()) { Type = HitResult.Great }); ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(new TestStrongNestedHit(h), new JudgementResult(new HitObject(), new TaikoStrongJudgement()) { Type = HitResult.Great });
} }
private void addMissJudgement() private void addMissJudgement()
{ {
DrawableTestHit h; DrawableTestHit h;
Add(h = new DrawableTestHit(new Hit(), HitResult.Miss)); DrawableRuleset.Playfield.Add(h = new DrawableTestHit(new Hit(), HitResult.Miss));
((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = HitResult.Miss }); ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = HitResult.Miss });
} }
private void addBarLine(bool major, double delay = scroll_time) private void addBarLine(bool major, double delay = scroll_time)
{ {
BarLine bl = new BarLine { StartTime = drawableRuleset.Playfield.Time.Current + delay }; BarLine bl = new BarLine { StartTime = DrawableRuleset.Playfield.Time.Current + delay };
drawableRuleset.Playfield.Add(major ? new DrawableBarLineMajor(bl) : new DrawableBarLine(bl)); DrawableRuleset.Playfield.Add(major ? new DrawableBarLineMajor(bl) : new DrawableBarLine(bl));
} }
private void addSwell(double duration = default_duration) private void addSwell(double duration = default_duration)
{ {
var swell = new Swell var swell = new Swell
{ {
StartTime = drawableRuleset.Playfield.Time.Current + scroll_time, StartTime = DrawableRuleset.Playfield.Time.Current + scroll_time,
Duration = duration, Duration = duration,
}; };
swell.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); swell.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
drawableRuleset.Playfield.Add(new DrawableSwell(swell)); DrawableRuleset.Playfield.Add(new DrawableSwell(swell));
} }
private void addDrumRoll(bool strong, double duration = default_duration, bool kiai = false) private void addDrumRoll(bool strong, double duration = default_duration, bool kiai = false)
@ -206,7 +172,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
var d = new DrumRoll var d = new DrumRoll
{ {
StartTime = drawableRuleset.Playfield.Time.Current + scroll_time, StartTime = DrawableRuleset.Playfield.Time.Current + scroll_time,
IsStrong = strong, IsStrong = strong,
Duration = duration, Duration = duration,
TickRate = 8, TickRate = 8,
@ -217,33 +183,33 @@ namespace osu.Game.Rulesets.Taiko.Tests
d.ApplyDefaults(cpi, new BeatmapDifficulty()); d.ApplyDefaults(cpi, new BeatmapDifficulty());
drawableRuleset.Playfield.Add(new DrawableDrumRoll(d)); DrawableRuleset.Playfield.Add(new DrawableDrumRoll(d));
} }
private void addCentreHit(bool strong) private void addCentreHit(bool strong)
{ {
Hit h = new Hit Hit h = new Hit
{ {
StartTime = drawableRuleset.Playfield.Time.Current + scroll_time, StartTime = DrawableRuleset.Playfield.Time.Current + scroll_time,
IsStrong = strong IsStrong = strong
}; };
h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
drawableRuleset.Playfield.Add(new DrawableHit(h)); DrawableRuleset.Playfield.Add(new DrawableHit(h));
} }
private void addRimHit(bool strong) private void addRimHit(bool strong)
{ {
Hit h = new Hit Hit h = new Hit
{ {
StartTime = drawableRuleset.Playfield.Time.Current + scroll_time, StartTime = DrawableRuleset.Playfield.Time.Current + scroll_time,
IsStrong = strong IsStrong = strong
}; };
h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
drawableRuleset.Playfield.Add(new DrawableHit(h)); DrawableRuleset.Playfield.Add(new DrawableHit(h));
} }
private class TestStrongNestedHit : DrawableStrongNestedHit private class TestStrongNestedHit : DrawableStrongNestedHit

View File

@ -42,9 +42,9 @@ namespace osu.Game.Rulesets.Taiko.Audio
} }
} }
private SkinnableSound addSound(HitSampleInfo hitSampleInfo, double lifetimeStart, double lifetimeEnd) private PausableSkinnableSound addSound(HitSampleInfo hitSampleInfo, double lifetimeStart, double lifetimeEnd)
{ {
var drawable = new SkinnableSound(hitSampleInfo) var drawable = new PausableSkinnableSound(hitSampleInfo)
{ {
LifetimeStart = lifetimeStart, LifetimeStart = lifetimeStart,
LifetimeEnd = lifetimeEnd LifetimeEnd = lifetimeEnd
@ -57,8 +57,8 @@ namespace osu.Game.Rulesets.Taiko.Audio
public class DrumSample public class DrumSample
{ {
public SkinnableSound Centre; public PausableSkinnableSound Centre;
public SkinnableSound Rim; public PausableSkinnableSound Rim;
} }
} }
} }

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

@ -57,7 +57,13 @@ namespace osu.Game.Rulesets.Taiko.Edit
ChangeHandler.BeginChange(); ChangeHandler.BeginChange();
foreach (var h in hits) foreach (var h in hits)
h.IsStrong = state; {
if (h.IsStrong != state)
{
h.IsStrong = state;
EditorBeatmap.UpdateHitObject(h);
}
}
ChangeHandler.EndChange(); ChangeHandler.EndChange();
} }

View File

@ -7,19 +7,7 @@ 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)
{ {

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

@ -129,7 +129,7 @@ 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 = HitResult.Miss);
@ -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

@ -27,5 +27,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
base.LoadComplete(); base.LoadComplete();
ApplyResult(r => r.Type = r.Judgement.MaxResult); ApplyResult(r => r.Type = r.Judgement.MaxResult);
} }
protected override void LoadSamples()
{
// block base call - flying hits are not supposed to play samples
// the base call could overwrite the type of this hit
}
} }
} }

View File

@ -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,10 +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; var hitResult = numHits > HitObject.RequiredHits / 2 ? HitResult.Ok : HitResult.Miss;
ApplyResult(r => r.Type = hitResult); 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

@ -30,6 +30,9 @@ namespace osu.Game.Rulesets.Taiko.Replays
public override Replay Generate() public override Replay Generate()
{ {
if (Beatmap.HitObjects.Count == 0)
return Replay;
bool hitButton = true; bool hitButton = true;
Frames.Add(new TaikoReplayFrame(-100000)); Frames.Add(new TaikoReplayFrame(-100000));

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,7 +42,7 @@ 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.Type > HitResult.Miss;

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

@ -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

@ -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

@ -2,7 +2,9 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Utils;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit; using osu.Game.Screens.Edit;
namespace osu.Game.Tests.Editing namespace osu.Game.Tests.Editing
@ -13,11 +15,12 @@ namespace osu.Game.Tests.Editing
[Test] [Test]
public void TestSaveRestoreState() public void TestSaveRestoreState()
{ {
var handler = new EditorChangeHandler(new EditorBeatmap(new Beatmap())); var (handler, beatmap) = createChangeHandler();
Assert.That(handler.CanUndo.Value, Is.False); Assert.That(handler.CanUndo.Value, Is.False);
Assert.That(handler.CanRedo.Value, Is.False); Assert.That(handler.CanRedo.Value, Is.False);
addArbitraryChange(beatmap);
handler.SaveState(); handler.SaveState();
Assert.That(handler.CanUndo.Value, Is.True); Assert.That(handler.CanUndo.Value, Is.True);
@ -29,15 +32,48 @@ namespace osu.Game.Tests.Editing
Assert.That(handler.CanRedo.Value, Is.True); Assert.That(handler.CanRedo.Value, Is.True);
} }
[Test]
public void TestSaveSameStateDoesNotSave()
{
var (handler, beatmap) = createChangeHandler();
Assert.That(handler.CanUndo.Value, Is.False);
Assert.That(handler.CanRedo.Value, Is.False);
addArbitraryChange(beatmap);
handler.SaveState();
Assert.That(handler.CanUndo.Value, Is.True);
Assert.That(handler.CanRedo.Value, Is.False);
string hash = handler.CurrentStateHash;
// save a save without making any changes
handler.SaveState();
Assert.That(hash, Is.EqualTo(handler.CurrentStateHash));
handler.RestoreState(-1);
Assert.That(hash, Is.Not.EqualTo(handler.CurrentStateHash));
// we should only be able to restore once even though we saved twice.
Assert.That(handler.CanUndo.Value, Is.False);
Assert.That(handler.CanRedo.Value, Is.True);
}
[Test] [Test]
public void TestMaxStatesSaved() public void TestMaxStatesSaved()
{ {
var handler = new EditorChangeHandler(new EditorBeatmap(new Beatmap())); var (handler, beatmap) = createChangeHandler();
Assert.That(handler.CanUndo.Value, Is.False); Assert.That(handler.CanUndo.Value, Is.False);
for (int i = 0; i < EditorChangeHandler.MAX_SAVED_STATES; i++) for (int i = 0; i < EditorChangeHandler.MAX_SAVED_STATES; i++)
{
addArbitraryChange(beatmap);
handler.SaveState(); handler.SaveState();
}
Assert.That(handler.CanUndo.Value, Is.True); Assert.That(handler.CanUndo.Value, Is.True);
@ -53,12 +89,15 @@ namespace osu.Game.Tests.Editing
[Test] [Test]
public void TestMaxStatesExceeded() public void TestMaxStatesExceeded()
{ {
var handler = new EditorChangeHandler(new EditorBeatmap(new Beatmap())); var (handler, beatmap) = createChangeHandler();
Assert.That(handler.CanUndo.Value, Is.False); Assert.That(handler.CanUndo.Value, Is.False);
for (int i = 0; i < EditorChangeHandler.MAX_SAVED_STATES * 2; i++) for (int i = 0; i < EditorChangeHandler.MAX_SAVED_STATES * 2; i++)
{
addArbitraryChange(beatmap);
handler.SaveState(); handler.SaveState();
}
Assert.That(handler.CanUndo.Value, Is.True); Assert.That(handler.CanUndo.Value, Is.True);
@ -70,5 +109,17 @@ namespace osu.Game.Tests.Editing
Assert.That(handler.CanUndo.Value, Is.False); Assert.That(handler.CanUndo.Value, Is.False);
} }
private (EditorChangeHandler, EditorBeatmap) createChangeHandler()
{
var beatmap = new EditorBeatmap(new Beatmap());
return (new EditorChangeHandler(beatmap), beatmap);
}
private void addArbitraryChange(EditorBeatmap beatmap)
{
beatmap.Add(new HitCircle { StartTime = RNG.Next(0, 100000) });
}
} }
} }

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

@ -4,10 +4,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Audio.Sample; using osu.Framework.Audio.Sample;
using osu.Framework.Graphics.Audio;
using osu.Framework.IO.Stores; using osu.Framework.IO.Stores;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Audio; using osu.Game.Audio;
@ -106,9 +108,14 @@ namespace osu.Game.Tests.Gameplay
Beatmap.Value = new TestCustomSkinWorkingBeatmap(new OsuRuleset().RulesetInfo, Audio); Beatmap.Value = new TestCustomSkinWorkingBeatmap(new OsuRuleset().RulesetInfo, Audio);
SelectedMods.Value = new[] { testedMod }; SelectedMods.Value = new[] { testedMod };
Add(gameplayContainer = new GameplayClockContainer(Beatmap.Value, 0)); var beatmapSkinSourceContainer = new BeatmapSkinProvidingContainer(Beatmap.Value.Skin);
gameplayContainer.Add(sample = new TestDrawableStoryboardSample(new StoryboardSampleInfo("test-sample", 1, 1)) Add(gameplayContainer = new GameplayClockContainer(Beatmap.Value, 0)
{
Child = beatmapSkinSourceContainer
});
beatmapSkinSourceContainer.Add(sample = new TestDrawableStoryboardSample(new StoryboardSampleInfo("test-sample", 1, 1))
{ {
Clock = gameplayContainer.GameplayClock Clock = gameplayContainer.GameplayClock
}); });
@ -116,7 +123,7 @@ namespace osu.Game.Tests.Gameplay
AddStep("start", () => gameplayContainer.Start()); AddStep("start", () => gameplayContainer.Start());
AddAssert("sample playback rate matches mod rates", () => sample.Channel.AggregateFrequency.Value == expectedRate); AddAssert("sample playback rate matches mod rates", () => sample.ChildrenOfType<DrawableSample>().First().AggregateFrequency.Value == expectedRate);
} }
private class TestSkin : LegacySkin private class TestSkin : LegacySkin
@ -168,8 +175,6 @@ namespace osu.Game.Tests.Gameplay
: base(sampleInfo) : base(sampleInfo)
{ {
} }
public new SampleChannel Channel => base.Channel;
} }
} }
} }

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

@ -0,0 +1,47 @@
// 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[] samples = null;
AddStep("get first slider", () =>
{
slider = Editor.ChildrenOfType<DrawableSlider>().OrderBy(s => s.HitObject.StartTime).First();
samples = slider.ChildrenOfType<DrawableSample>().ToArray();
});
AddStep("start playback", () => EditorClock.Start());
AddUntilStep("wait for slider sliding then seek", () =>
{
if (!slider.Tracking.Value)
return false;
if (!samples.Any(s => s.Playing))
return false;
EditorClock.Seek(20000);
return true;
});
AddAssert("slider samples are not playing", () => samples.Length == 5 && samples.All(s => s.Played && !s.Playing));
}
}
}

View File

@ -4,7 +4,7 @@
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Edit; using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Timing; using osu.Game.Screens.Edit.Timing;
@ -17,16 +17,26 @@ namespace osu.Game.Tests.Visual.Editing
[Cached(typeof(IBeatSnapProvider))] [Cached(typeof(IBeatSnapProvider))]
private readonly EditorBeatmap editorBeatmap; private readonly EditorBeatmap editorBeatmap;
protected override bool ScrollUsingMouseWheel => false;
public TestSceneTimingScreen() public TestSceneTimingScreen()
{ {
editorBeatmap = new EditorBeatmap(new OsuBeatmap()); editorBeatmap = new EditorBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo));
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap); Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap);
Beatmap.Disabled = true;
Child = new TimingScreen(); Child = new TimingScreen();
} }
protected override void Dispose(bool isDisposing)
{
Beatmap.Disabled = false;
base.Dispose(isDisposing);
}
} }
} }

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

@ -12,6 +12,14 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
protected new OverlayTestPlayer Player => base.Player as OverlayTestPlayer; protected new OverlayTestPlayer Player => base.Player as OverlayTestPlayer;
public override void SetUpSteps()
{
base.SetUpSteps();
AddUntilStep("gameplay has started",
() => Player.GameplayClockContainer.GameplayClock.CurrentTime > Player.DrawableRuleset.GameplayStartTime);
}
[Test] [Test]
public void TestGameplayOverlayActivation() public void TestGameplayOverlayActivation()
{ {
@ -21,7 +29,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test] [Test]
public void TestGameplayOverlayActivationPaused() public void TestGameplayOverlayActivationPaused()
{ {
AddUntilStep("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled); AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
AddStep("pause gameplay", () => Player.Pause()); AddStep("pause gameplay", () => Player.Pause());
AddUntilStep("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered); AddUntilStep("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered);
} }

View File

@ -158,7 +158,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public void TestQuickRetryFromFailedGameplay() public void TestQuickRetryFromFailedGameplay()
{ {
AddUntilStep("wait for fail", () => Player.HasFailed); AddUntilStep("wait for fail", () => Player.HasFailed);
AddStep("quick retry", () => Player.GameplayClockContainer.OfType<HotkeyRetryOverlay>().First().Action?.Invoke()); AddStep("quick retry", () => Player.GameplayClockContainer.ChildrenOfType<HotkeyRetryOverlay>().First().Action?.Invoke());
confirmExited(); confirmExited();
} }
@ -167,7 +167,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public void TestQuickExitFromFailedGameplay() public void TestQuickExitFromFailedGameplay()
{ {
AddUntilStep("wait for fail", () => Player.HasFailed); AddUntilStep("wait for fail", () => Player.HasFailed);
AddStep("quick exit", () => Player.GameplayClockContainer.OfType<HotkeyExitOverlay>().First().Action?.Invoke()); AddStep("quick exit", () => Player.GameplayClockContainer.ChildrenOfType<HotkeyExitOverlay>().First().Action?.Invoke());
confirmExited(); confirmExited();
} }
@ -183,7 +183,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test] [Test]
public void TestQuickExitFromGameplay() public void TestQuickExitFromGameplay()
{ {
AddStep("quick exit", () => Player.GameplayClockContainer.OfType<HotkeyExitOverlay>().First().Action?.Invoke()); AddStep("quick exit", () => Player.GameplayClockContainer.ChildrenOfType<HotkeyExitOverlay>().First().Action?.Invoke());
confirmExited(); confirmExited();
} }

View File

@ -22,11 +22,11 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
public class TestSceneSkinnableSound : OsuTestScene public class TestSceneSkinnableSound : OsuTestScene
{ {
[Cached] [Cached(typeof(ISamplePlaybackDisabler))]
private GameplayClock gameplayClock = new GameplayClock(new FramedClock()); private GameplayClock gameplayClock = new GameplayClock(new FramedClock());
private TestSkinSourceContainer skinSource; private TestSkinSourceContainer skinSource;
private SkinnableSound skinnableSound; private PausableSkinnableSound skinnableSound;
[SetUp] [SetUp]
public void SetUp() => Schedule(() => public void SetUp() => Schedule(() =>
@ -39,7 +39,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
Clock = gameplayClock, Clock = gameplayClock,
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Child = skinnableSound = new SkinnableSound(new SampleInfo("normal-sliderslide")) Child = skinnableSound = new PausableSkinnableSound(new SampleInfo("normal-sliderslide"))
}, },
}; };
}); });

View File

@ -0,0 +1,26 @@
// 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;
namespace osu.Game.Extensions
{
public static class EditorDisplayExtensions
{
/// <summary>
/// Get an editor formatted string (mm:ss:mss)
/// </summary>
/// <param name="milliseconds">A time value in milliseconds.</param>
/// <returns>An editor formatted display string.</returns>
public static string ToEditorFormattedString(this double milliseconds) =>
ToEditorFormattedString(TimeSpan.FromMilliseconds(milliseconds));
/// <summary>
/// Get an editor formatted string (mm:ss:mss)
/// </summary>
/// <param name="timeSpan">A time value.</param>
/// <returns>An editor formatted display string.</returns>
public static string ToEditorFormattedString(this TimeSpan timeSpan) =>
$"{(timeSpan < TimeSpan.Zero ? "-" : string.Empty)}{timeSpan:mm\\:ss\\:fff}";
}
}

View File

@ -112,6 +112,9 @@ namespace osu.Game.Graphics.Containers
CornerRadius = 5; CornerRadius = 5;
// needs to be set initially for the ResizeTo to respect minimum size
Size = new Vector2(SCROLL_BAR_HEIGHT);
const float margin = 3; const float margin = 3;
Margin = new MarginPadding Margin = new MarginPadding

View File

@ -38,6 +38,7 @@ namespace osu.Game.Online.API.Requests.Responses
Rank = Rank, Rank = Rank,
Ruleset = ruleset, Ruleset = ruleset,
Mods = mods, Mods = mods,
IsLegacyScore = true
}; };
if (Statistics != null) if (Statistics != null)

View File

@ -11,9 +11,11 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Online.Leaderboards; using osu.Game.Online.Leaderboards;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Scoring; using osu.Game.Scoring;
using osu.Game.Users.Drawables; using osu.Game.Users.Drawables;
using osu.Game.Utils;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
@ -55,6 +57,11 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
highAccuracyColour = colours.GreenLight; highAccuracyColour = colours.GreenLight;
} }
/// <summary>
/// The statistics that appear in the table, in order of appearance.
/// </summary>
private readonly List<HitResult> statisticResultTypes = new List<HitResult>();
private bool showPerformancePoints; private bool showPerformancePoints;
public void DisplayScores(IReadOnlyList<ScoreInfo> scores, bool showPerformanceColumn) public void DisplayScores(IReadOnlyList<ScoreInfo> scores, bool showPerformanceColumn)
@ -65,11 +72,12 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
return; return;
showPerformancePoints = showPerformanceColumn; showPerformancePoints = showPerformanceColumn;
statisticResultTypes.Clear();
for (int i = 0; i < scores.Count; i++) for (int i = 0; i < scores.Count; i++)
backgroundFlow.Add(new ScoreTableRowBackground(i, scores[i], row_height)); backgroundFlow.Add(new ScoreTableRowBackground(i, scores[i], row_height));
Columns = createHeaders(scores.FirstOrDefault()); Columns = createHeaders(scores);
Content = scores.Select((s, i) => createContent(i, s)).ToArray().ToRectangular(); Content = scores.Select((s, i) => createContent(i, s)).ToArray().ToRectangular();
} }
@ -79,7 +87,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
backgroundFlow.Clear(); backgroundFlow.Clear();
} }
private TableColumn[] createHeaders(ScoreInfo score) private TableColumn[] createHeaders(IReadOnlyList<ScoreInfo> scores)
{ {
var columns = new List<TableColumn> var columns = new List<TableColumn>
{ {
@ -92,10 +100,17 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
new TableColumn("max combo", Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 70, maxSize: 120)) new TableColumn("max combo", Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 70, maxSize: 120))
}; };
foreach (var statistic in score.SortedStatistics.Take(score.SortedStatistics.Count() - 1)) // All statistics across all scores, unordered.
columns.Add(new TableColumn(statistic.Key.GetDescription(), Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 35, maxSize: 60))); var allScoreStatistics = scores.SelectMany(s => s.GetStatisticsForDisplay().Select(stat => stat.result)).ToHashSet();
columns.Add(new TableColumn(score.SortedStatistics.LastOrDefault().Key.GetDescription(), Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 45, maxSize: 95))); foreach (var result in OrderAttributeUtils.GetValuesInOrder<HitResult>())
{
if (!allScoreStatistics.Contains(result))
continue;
columns.Add(new TableColumn(result.GetDescription(), Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 35, maxSize: 60)));
statisticResultTypes.Add(result);
}
if (showPerformancePoints) if (showPerformancePoints)
columns.Add(new TableColumn("pp", Anchor.CentreLeft, new Dimension(GridSizeMode.Absolute, 30))); columns.Add(new TableColumn("pp", Anchor.CentreLeft, new Dimension(GridSizeMode.Absolute, 30)));
@ -148,13 +163,18 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
} }
}; };
foreach (var kvp in score.SortedStatistics) var availableStatistics = score.GetStatisticsForDisplay().ToDictionary(tuple => tuple.result);
foreach (var result in statisticResultTypes)
{ {
if (!availableStatistics.TryGetValue(result, out var stat))
stat = (result, 0, null);
content.Add(new OsuSpriteText content.Add(new OsuSpriteText
{ {
Text = $"{kvp.Value}", Text = stat.maxCount == null ? $"{stat.count}" : $"{stat.count}/{stat.maxCount}",
Font = OsuFont.GetFont(size: text_size), Font = OsuFont.GetFont(size: text_size),
Colour = kvp.Value == 0 ? Color4.Gray : Color4.White Colour = stat.count == 0 ? Color4.Gray : Color4.White
}); });
} }

View File

@ -117,7 +117,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
ppColumn.Alpha = value.Beatmap?.Status == BeatmapSetOnlineStatus.Ranked ? 1 : 0; ppColumn.Alpha = value.Beatmap?.Status == BeatmapSetOnlineStatus.Ranked ? 1 : 0;
ppColumn.Text = $@"{value.PP:N0}"; ppColumn.Text = $@"{value.PP:N0}";
statisticsColumns.ChildrenEnumerable = value.SortedStatistics.Select(kvp => createStatisticsColumn(kvp.Key, kvp.Value)); statisticsColumns.ChildrenEnumerable = value.GetStatisticsForDisplay().Select(s => createStatisticsColumn(s.result, s.count, s.maxCount));
modsColumn.Mods = value.Mods; modsColumn.Mods = value.Mods;
if (scoreManager != null) if (scoreManager != null)
@ -125,9 +125,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
} }
} }
private TextColumn createStatisticsColumn(HitResult hitResult, int count) => new TextColumn(hitResult.GetDescription(), smallFont, bottom_columns_min_width) private TextColumn createStatisticsColumn(HitResult hitResult, int count, int? maxCount) => new TextColumn(hitResult.GetDescription(), smallFont, bottom_columns_min_width)
{ {
Text = count.ToString() Text = maxCount == null ? $"{count}" : $"{count}/{maxCount}"
}; };
private class InfoColumn : CompositeDrawable private class InfoColumn : CompositeDrawable

View File

@ -45,15 +45,21 @@ namespace osu.Game.Rulesets.Edit
base.LoadComplete(); base.LoadComplete();
beatmap.HitObjectAdded += addHitObject; beatmap.HitObjectAdded += addHitObject;
beatmap.HitObjectUpdated += updateReplay;
beatmap.HitObjectRemoved += removeHitObject; beatmap.HitObjectRemoved += removeHitObject;
} }
private void updateReplay(HitObject obj = null) =>
drawableRuleset.RegenerateAutoplay();
private void addHitObject(HitObject hitObject) private void addHitObject(HitObject hitObject)
{ {
var drawableObject = drawableRuleset.CreateDrawableRepresentation((TObject)hitObject); var drawableObject = drawableRuleset.CreateDrawableRepresentation((TObject)hitObject);
drawableRuleset.Playfield.Add(drawableObject); drawableRuleset.Playfield.Add(drawableObject);
drawableRuleset.Playfield.PostProcess(); drawableRuleset.Playfield.PostProcess();
updateReplay();
} }
private void removeHitObject(HitObject hitObject) private void removeHitObject(HitObject hitObject)
@ -62,6 +68,8 @@ namespace osu.Game.Rulesets.Edit
drawableRuleset.Playfield.Remove(drawableObject); drawableRuleset.Playfield.Remove(drawableObject);
drawableRuleset.Playfield.PostProcess(); drawableRuleset.Playfield.PostProcess();
drawableRuleset.RegenerateAutoplay();
} }
public override bool PropagatePositionalInputSubTree => false; public override bool PropagatePositionalInputSubTree => false;

View File

@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Edit
try try
{ {
drawableRulesetWrapper = new DrawableEditRulesetWrapper<TObject>(CreateDrawableRuleset(Ruleset, EditorBeatmap.PlayableBeatmap)) drawableRulesetWrapper = new DrawableEditRulesetWrapper<TObject>(CreateDrawableRuleset(Ruleset, EditorBeatmap.PlayableBeatmap, new[] { Ruleset.GetAutoplayMod() }))
{ {
Clock = EditorClock, Clock = EditorClock,
ProcessCustomClock = false ProcessCustomClock = false

View File

@ -7,10 +7,6 @@ namespace osu.Game.Rulesets.Judgements
{ {
public class IgnoreJudgement : Judgement public class IgnoreJudgement : Judgement
{ {
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;
} }
} }

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