1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-25 12:30:17 +08:00

Compare commits

..

6 Commits

108 changed files with 576 additions and 1533 deletions
@@ -11,7 +11,7 @@ using osuTK;
namespace osu.Game.Rulesets.Catch.Tests.Mods
{
public partial class TestSceneCatchModPerfect : ModFailConditionTestScene
public partial class TestSceneCatchModPerfect : ModPerfectTestScene
{
protected override Ruleset CreatePlayerRuleset() => new CatchRuleset();
@@ -7,7 +7,7 @@ using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Mods;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Scoring;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
@@ -18,8 +18,6 @@ namespace osu.Game.Rulesets.Catch.Difficulty
{
internal class CatchLegacyScoreSimulator : ILegacyScoreSimulator
{
private readonly ScoreProcessor scoreProcessor = new CatchScoreProcessor();
private int legacyBonusScore;
private int standardisedBonusScore;
private int combo;
@@ -136,7 +134,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
if (isBonus)
{
legacyBonusScore += scoreIncrease;
standardisedBonusScore += scoreProcessor.GetBaseScoreForResult(bonusResult);
standardisedBonusScore += Judgement.ToNumericResult(bonusResult);
}
else
attributes.AccuracyScore += scoreIncrease;
@@ -3,7 +3,6 @@
using System;
using osu.Framework.Input.Events;
using osu.Framework.Utils;
using osu.Game.Rulesets.Catch.Edit.Blueprints.Components;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Edit;
@@ -18,7 +17,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
private double placementStartTime;
private double placementEndTime;
protected override bool IsValidForPlacement => Precision.DefinitelyBigger(HitObject.Duration, 0);
protected override bool IsValidForPlacement => HitObject.Duration > 0;
public BananaShowerPlacementBlueprint()
{
@@ -4,7 +4,6 @@
using osu.Framework.Graphics;
using osu.Framework.Input;
using osu.Framework.Input.Events;
using osu.Framework.Utils;
using osu.Game.Rulesets.Catch.Edit.Blueprints.Components;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Edit;
@@ -25,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
private InputManager inputManager = null!;
protected override bool IsValidForPlacement => Precision.DefinitelyBigger(HitObject.Duration, 0);
protected override bool IsValidForPlacement => HitObject.Duration > 0;
public JuiceStreamPlacementBlueprint()
{
@@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Catch.Scoring
}
protected override double GetComboScoreChange(JudgementResult result)
=> GetBaseScoreForResult(result.Type) * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(combo_cap, combo_base));
=> Judgement.ToNumericResult(result.Type) * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(combo_cap, combo_base));
public override ScoreRank RankFromAccuracy(double accuracy)
{
@@ -3,7 +3,6 @@
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Mods;
using osu.Game.Rulesets.Mania.Objects;
@@ -26,8 +25,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods
public void TestHitWindowWithoutDoubleTime() => CreateModTest(new ModTestData
{
PassCondition = () => Player.ScoreProcessor.JudgedHits > 0
&& Precision.AlmostEquals(Player.ScoreProcessor.Accuracy.Value, 0.9836, 0.01)
&& Player.ScoreProcessor.TotalScore.Value == 946_049,
&& Player.ScoreProcessor.Accuracy.Value == 1
&& Player.ScoreProcessor.TotalScore.Value == 1_000_000,
Autoplay = false,
Beatmap = new Beatmap
{
@@ -54,7 +53,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods
Mod = doubleTime,
PassCondition = () => Player.ScoreProcessor.JudgedHits > 0
&& Player.ScoreProcessor.Accuracy.Value == 1
&& Player.ScoreProcessor.TotalScore.Value == (long)(1_000_000 * doubleTime.ScoreMultiplier),
&& Player.ScoreProcessor.TotalScore.Value == (long)(1_000_010 * doubleTime.ScoreMultiplier),
Autoplay = false,
Beatmap = new Beatmap
{
@@ -1,19 +1,14 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Mods;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Replays;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Replays;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Mania.Tests.Mods
{
public partial class TestSceneManiaModPerfect : ModFailConditionTestScene
public partial class TestSceneManiaModPerfect : ModPerfectTestScene
{
protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
@@ -29,52 +24,5 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods
[TestCase(false)]
[TestCase(true)]
public void TestHoldNote(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new HoldNote { StartTime = 1000, EndTime = 3000 }), shouldMiss);
[Test]
public void TestGreatHit() => CreateModTest(new ModTestData
{
Mod = new ManiaModPerfect(),
PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(false),
Autoplay = false,
Beatmap = new Beatmap
{
HitObjects = new List<HitObject>
{
new Note
{
StartTime = 1000,
}
},
},
ReplayFrames = new List<ReplayFrame>
{
new ManiaReplayFrame(1020, ManiaAction.Key1),
new ManiaReplayFrame(2000)
}
});
[Test]
public void TestBreakOnHoldNote() => CreateModTest(new ModTestData
{
Mod = new ManiaModPerfect(),
PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(true) && Player.Results.Count == 2,
Autoplay = false,
Beatmap = new Beatmap
{
HitObjects = new List<HitObject>
{
new HoldNote
{
StartTime = 1000,
EndTime = 3000,
},
},
},
ReplayFrames = new List<ReplayFrame>
{
new ManiaReplayFrame(1000, ManiaAction.Key1),
new ManiaReplayFrame(2000)
}
});
}
}
@@ -1,72 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Mods;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Replays;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Replays;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Mania.Tests.Mods
{
public partial class TestSceneManiaModSuddenDeath : ModFailConditionTestScene
{
protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
public TestSceneManiaModSuddenDeath()
: base(new ManiaModSuddenDeath())
{
}
[Test]
public void TestGreatHit() => CreateModTest(new ModTestData
{
Mod = new ManiaModSuddenDeath(),
PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(false),
Autoplay = false,
Beatmap = new Beatmap
{
HitObjects = new List<HitObject>
{
new Note
{
StartTime = 1000,
}
},
},
ReplayFrames = new List<ReplayFrame>
{
new ManiaReplayFrame(1020, ManiaAction.Key1),
new ManiaReplayFrame(2000)
}
});
[Test]
public void TestBreakOnHoldNote() => CreateModTest(new ModTestData
{
Mod = new ManiaModSuddenDeath(),
PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(true) && Player.Results.Count == 2,
Autoplay = false,
Beatmap = new Beatmap
{
HitObjects = new List<HitObject>
{
new HoldNote
{
StartTime = 1000,
EndTime = 3000,
},
},
},
ReplayFrames = new List<ReplayFrame>
{
new ManiaReplayFrame(1000, ManiaAction.Key1),
new ManiaReplayFrame(2000)
}
});
}
}
@@ -200,10 +200,12 @@ namespace osu.Game.Rulesets.Mania.Tests
});
assertHeadJudgement(HitResult.Perfect);
assertComboAtJudgement(0, 1);
// judgement combo offset by perfect bonus judgement. see logic in DrawableNote.CheckForResult.
assertComboAtJudgement(1, 1);
assertTailJudgement(HitResult.Meh);
assertComboAtJudgement(1, 0);
assertComboAtJudgement(3, 1);
assertComboAtJudgement(2, 0);
// judgement combo offset by perfect bonus judgement. see logic in DrawableNote.CheckForResult.
assertComboAtJudgement(4, 1);
}
/// <summary>
@@ -54,7 +54,7 @@ namespace osu.Game.Rulesets.Mania.Tests
AddAssert("all objects perfectly judged",
() => judgementResults.Select(result => result.Type),
() => Is.EquivalentTo(judgementResults.Select(result => result.Judgement.MaxResult)));
AddAssert("score is correct", () => currentPlayer.ScoreProcessor.TotalScore.Value, () => Is.EqualTo(1_000_000));
AddAssert("score is correct", () => currentPlayer.ScoreProcessor.TotalScore.Value, () => Is.EqualTo(1_000_030));
}
[Test]
@@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Mania.Tests
AddAssert("all objects perfectly judged",
() => judgementResults.Select(result => result.Type),
() => Is.EquivalentTo(judgementResults.Select(result => result.Judgement.MaxResult)));
AddAssert("score is correct", () => currentPlayer.ScoreProcessor.TotalScore.Value, () => Is.EqualTo(1_000_000));
AddAssert("score is correct", () => currentPlayer.ScoreProcessor.TotalScore.Value, () => Is.EqualTo(1_000_040));
}
private void performTest(List<ManiaHitObject> hitObjects, List<ReplayFrame> frames)
@@ -5,7 +5,6 @@ using System;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Input.Events;
using osu.Framework.Utils;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
using osu.Game.Rulesets.Mania.Objects;
@@ -24,7 +23,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
[Resolved]
private IScrollingInfo scrollingInfo { get; set; } = null!;
protected override bool IsValidForPlacement => Precision.DefinitelyBigger(HitObject.Duration, 0);
protected override bool IsValidForPlacement => HitObject.Duration > 0;
public HoldNotePlacementBlueprint()
: base(new HoldNote())
@@ -10,10 +10,5 @@ namespace osu.Game.Rulesets.Mania.Mods
public class ManiaModDoubleTime : ModDoubleTime, IManiaRateAdjustmentMod
{
public HitWindows HitWindows { get; set; } = new ManiaHitWindows();
// For now, all rate-increasing mods should be given a 1x multiplier in mania because it doesn't always
// make the map harder and is more of a personal preference.
// In the future, we can consider adjusting this by experimenting with not applying the hitwindow leniency.
public override double ScoreMultiplier => 1;
}
}
@@ -11,10 +11,5 @@ namespace osu.Game.Rulesets.Mania.Mods
public class ManiaModNightcore : ModNightcore<ManiaHitObject>, IManiaRateAdjustmentMod
{
public HitWindows HitWindows { get; set; } = new ManiaHitWindows();
// For now, all rate-increasing mods should be given a 1x multiplier in mania because it doesn't always
// make the map any harder and is more of a personal preference.
// In the future, we can consider adjusting this by experimenting with not applying the hitwindow leniency.
public override double ScoreMultiplier => 1;
}
}
@@ -1,26 +1,11 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Mods
{
public class ManiaModPerfect : ModPerfect
{
protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result)
{
if (!isRelevantResult(result.Judgement.MinResult) && !isRelevantResult(result.Judgement.MaxResult) && !isRelevantResult(result.Type))
return false;
// Mania allows imperfect "Great" hits without failing.
if (result.Judgement.MaxResult == HitResult.Perfect)
return result.Type < HitResult.Great;
return result.Type != result.Judgement.MaxResult;
}
private bool isRelevantResult(HitResult result) => result.AffectsAccuracy() || result.AffectsCombo();
}
}
@@ -13,6 +13,8 @@ using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.Skinning.Default;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Screens.Edit;
@@ -38,6 +40,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
private Drawable headPiece;
private DrawableNotePerfectBonus perfectBonus;
public DrawableNote()
: this(null)
{
@@ -89,7 +93,10 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
if (!userTriggered)
{
if (!HitObject.HitWindows.CanBeHit(timeOffset))
{
perfectBonus.TriggerResult(false);
ApplyResult(r => r.Type = r.Judgement.MinResult);
}
return;
}
@@ -100,9 +107,16 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
result = GetCappedResult(result);
perfectBonus.TriggerResult(result == HitResult.Perfect);
ApplyResult(r => r.Type = result);
}
public override void MissForcefully()
{
perfectBonus.TriggerResult(false);
base.MissForcefully();
}
/// <summary>
/// Some objects in mania may want to limit the max result.
/// </summary>
@@ -123,6 +137,32 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
}
protected override void AddNestedHitObject(DrawableHitObject hitObject)
{
switch (hitObject)
{
case DrawableNotePerfectBonus bonus:
AddInternal(perfectBonus = bonus);
break;
}
}
protected override void ClearNestedHitObjects()
{
RemoveInternal(perfectBonus, false);
}
protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
{
switch (hitObject)
{
case NotePerfectBonus bonus:
return new DrawableNotePerfectBonus(bonus);
}
return base.CreateNestedHitObject(hitObject);
}
private void updateSnapColour()
{
if (beatmap == null || HitObject == null) return;
@@ -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.
namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
public partial class DrawableNotePerfectBonus : DrawableManiaHitObject<NotePerfectBonus>
{
public override bool DisplayResult => false;
public DrawableNotePerfectBonus()
: this(null!)
{
}
public DrawableNotePerfectBonus(NotePerfectBonus hitObject)
: base(hitObject)
{
}
/// <summary>
/// Apply a judgement result.
/// </summary>
/// <param name="hit">Whether this tick was reached.</param>
internal void TriggerResult(bool hit) => ApplyResult(r => r.Type = hit ? r.Judgement.MaxResult : r.Judgement.MinResult);
}
}
+8
View File
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Threading;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Judgements;
@@ -12,5 +13,12 @@ namespace osu.Game.Rulesets.Mania.Objects
public class Note : ManiaHitObject
{
public override Judgement CreateJudgement() => new ManiaJudgement();
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
{
base.CreateNestedHitObjects(cancellationToken);
AddNested(new NotePerfectBonus { StartTime = StartTime });
}
}
}
@@ -0,0 +1,20 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Judgements;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Objects
{
public class NotePerfectBonus : ManiaHitObject
{
public override Judgement CreateJudgement() => new NotePerfectBonusJudgement();
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
public class NotePerfectBonusJudgement : ManiaJudgement
{
public override HitResult MaxResult => HitResult.SmallBonus;
}
}
}
@@ -26,37 +26,13 @@ namespace osu.Game.Rulesets.Mania.Scoring
protected override double ComputeTotalScore(double comboProgress, double accuracyProgress, double bonusPortion)
{
return 150000 * comboProgress
+ 850000 * Math.Pow(Accuracy.Value, 2 + 2 * Accuracy.Value) * accuracyProgress
return 10000 * comboProgress
+ 990000 * Math.Pow(Accuracy.Value, 2 + 2 * Accuracy.Value) * accuracyProgress
+ bonusPortion;
}
protected override double GetComboScoreChange(JudgementResult result)
{
return getBaseComboScoreForResult(result.Type) * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(400, combo_base));
}
public override int GetBaseScoreForResult(HitResult result)
{
switch (result)
{
case HitResult.Perfect:
return 305;
}
return base.GetBaseScoreForResult(result);
}
private int getBaseComboScoreForResult(HitResult result)
{
switch (result)
{
case HitResult.Perfect:
return 300;
}
return GetBaseScoreForResult(result);
}
=> Judgement.ToNumericResult(result.Type) * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(400, combo_base));
private class JudgementOrderComparer : IComparer<HitObject>
{
+1
View File
@@ -109,6 +109,7 @@ namespace osu.Game.Rulesets.Mania.UI
TopLevelContainer.Add(HitObjectArea.Explosions.CreateProxy());
RegisterPool<Note, DrawableNote>(10, 50);
RegisterPool<NotePerfectBonus, DrawableNotePerfectBonus>(10, 50);
RegisterPool<HoldNote, DrawableHoldNote>(10, 50);
RegisterPool<HeadNote, DrawableHoldNoteHead>(10, 50);
RegisterPool<TailNote, DrawableHoldNoteTail>(10, 50);
@@ -1,21 +1,17 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Replays;
using osu.Game.Rulesets.Replays;
using osu.Game.Tests.Visual;
using osuTK;
namespace osu.Game.Rulesets.Osu.Tests.Mods
{
public partial class TestSceneOsuModPerfect : ModFailConditionTestScene
public partial class TestSceneOsuModPerfect : ModPerfectTestScene
{
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
@@ -54,30 +50,5 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
CreateHitObjectTest(new HitObjectTestData(spinner), shouldMiss);
}
[Test]
public void TestMissSliderTail() => CreateModTest(new ModTestData
{
Mod = new OsuModPerfect(),
PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(true),
Autoplay = false,
Beatmap = new Beatmap
{
HitObjects = new List<HitObject>
{
new Slider
{
Position = new Vector2(256, 192),
StartTime = 1000,
Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(100, 0), })
},
},
},
ReplayFrames = new List<ReplayFrame>
{
new OsuReplayFrame(1000, new Vector2(256, 192), OsuAction.LeftButton),
new OsuReplayFrame(1001, new Vector2(256, 192)),
}
});
}
}
@@ -1,53 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Replays;
using osu.Game.Rulesets.Replays;
using osuTK;
namespace osu.Game.Rulesets.Osu.Tests.Mods
{
public partial class TestSceneOsuModStrictTracking : OsuModTestScene
{
[Test]
public void TestSliderInput() => CreateModTest(new ModTestData
{
Mod = new OsuModStrictTracking(),
Autoplay = false,
Beatmap = new Beatmap
{
HitObjects = new List<HitObject>
{
new Slider
{
StartTime = 1000,
Path = new SliderPath
{
ControlPoints =
{
new PathControlPoint(),
new PathControlPoint(new Vector2(0, 100))
}
}
}
}
},
ReplayFrames = new List<ReplayFrame>
{
new OsuReplayFrame(0, new Vector2(), OsuAction.LeftButton),
new OsuReplayFrame(500, new Vector2(200, 0), OsuAction.LeftButton),
new OsuReplayFrame(501, new Vector2(200, 0)),
new OsuReplayFrame(1000, new Vector2(), OsuAction.LeftButton),
new OsuReplayFrame(1750, new Vector2(0, 100), OsuAction.LeftButton),
new OsuReplayFrame(1751, new Vector2(0, 100)),
},
PassCondition = () => Player.ScoreProcessor.Combo.Value == 2
});
}
}
@@ -1,77 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Replays;
using osu.Game.Rulesets.Replays;
using osu.Game.Tests.Visual;
using osuTK;
namespace osu.Game.Rulesets.Osu.Tests.Mods
{
public partial class TestSceneOsuModSuddenDeath : ModFailConditionTestScene
{
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
public TestSceneOsuModSuddenDeath()
: base(new OsuModSuddenDeath())
{
}
[Test]
public void TestMissTail() => CreateModTest(new ModTestData
{
Mod = new OsuModSuddenDeath(),
PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(false),
Autoplay = false,
Beatmap = new Beatmap
{
HitObjects = new List<HitObject>
{
new Slider
{
Position = new Vector2(256, 192),
StartTime = 1000,
Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(100, 0), })
},
},
},
ReplayFrames = new List<ReplayFrame>
{
new OsuReplayFrame(1000, new Vector2(256, 192), OsuAction.LeftButton),
new OsuReplayFrame(1001, new Vector2(256, 192)),
}
});
[Test]
public void TestMissTick() => CreateModTest(new ModTestData
{
Mod = new OsuModSuddenDeath(),
PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(true),
Autoplay = false,
Beatmap = new Beatmap
{
HitObjects = new List<HitObject>
{
new Slider
{
Position = new Vector2(256, 192),
StartTime = 1000,
Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(200, 0), })
},
},
},
ReplayFrames = new List<ReplayFrame>
{
new OsuReplayFrame(1000, new Vector2(256, 192), OsuAction.LeftButton),
new OsuReplayFrame(1001, new Vector2(256, 192)),
}
});
}
}
@@ -58,7 +58,10 @@ namespace osu.Game.Rulesets.Osu.Tests
double trackerRotationTolerance = 0;
addSeekStep(5000);
AddStep("calculate rotation tolerance", () => { trackerRotationTolerance = Math.Abs(drawableSpinner.RotationTracker.Rotation * 0.1f); });
AddStep("calculate rotation tolerance", () =>
{
trackerRotationTolerance = Math.Abs(drawableSpinner.RotationTracker.Rotation * 0.1f);
});
AddAssert("is disc rotation not almost 0", () => drawableSpinner.RotationTracker.Rotation, () => Is.Not.EqualTo(0).Within(100));
AddAssert("is disc rotation absolute not almost 0", () => drawableSpinner.Result.TotalRotation, () => Is.Not.EqualTo(0).Within(100));
@@ -130,11 +133,9 @@ namespace osu.Game.Rulesets.Osu.Tests
AddAssert("player score matching expected bonus score", () =>
{
var scoreProcessor = ((ScoreExposedPlayer)Player).ScoreProcessor;
// multipled by 2 to nullify the score multiplier. (autoplay mod selected)
long totalScore = scoreProcessor.TotalScore.Value * 2;
return totalScore == (int)(drawableSpinner.Result.TotalRotation / 360) * scoreProcessor.GetBaseScoreForResult(new SpinnerTick().CreateJudgement().MaxResult);
long totalScore = ((ScoreExposedPlayer)Player).ScoreProcessor.TotalScore.Value * 2;
return totalScore == (int)(drawableSpinner.Result.TotalRotation / 360) * new SpinnerTick().CreateJudgement().MaxNumericResult;
});
addSeekStep(0);
@@ -5,12 +5,12 @@ using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Scoring;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Scoring.Legacy;
@@ -18,8 +18,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{
internal class OsuLegacyScoreSimulator : ILegacyScoreSimulator
{
private readonly ScoreProcessor scoreProcessor = new OsuScoreProcessor();
private int legacyBonusScore;
private int standardisedBonusScore;
private int combo;
@@ -173,7 +171,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
if (isBonus)
{
legacyBonusScore += scoreIncrease;
standardisedBonusScore += scoreProcessor.GetBaseScoreForResult(bonusResult);
standardisedBonusScore += Judgement.ToNumericResult(bonusResult);
}
else
attributes.AccuracyScore += scoreIncrease;
@@ -1,11 +1,14 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Osu.Mods
{
public class OsuModAccuracyChallenge : ModAccuracyChallenge
{
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).ToArray();
}
}
@@ -16,7 +16,7 @@ using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Osu.Mods
{
public class OsuModAutopilot : Mod, IUpdatableByPlayfield, IApplicableToDrawableRuleset<OsuHitObject>
public class OsuModAutopilot : Mod, IApplicableFailOverride, IUpdatableByPlayfield, IApplicableToDrawableRuleset<OsuHitObject>
{
public override string Name => "Autopilot";
public override string Acronym => "AP";
@@ -29,12 +29,18 @@ namespace osu.Game.Rulesets.Osu.Mods
{
typeof(OsuModSpunOut),
typeof(ModRelax),
typeof(ModFailCondition),
typeof(ModNoFail),
typeof(ModAutoplay),
typeof(OsuModMagnetised),
typeof(OsuModRepel),
typeof(ModTouchDevice)
};
public bool PerformFail() => false;
public bool RestartOnFail => false;
private OsuInputManager inputManager = null!;
private List<OsuReplayFrame> replayFrames = null!;
@@ -1,11 +1,14 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Osu.Mods
{
public class OsuModNoFail : ModNoFail
{
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).ToArray();
}
}
@@ -1,11 +1,14 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Osu.Mods
{
public class OsuModPerfect : ModPerfect
{
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot) }).ToArray();
}
}
+1 -1
View File
@@ -88,7 +88,7 @@ namespace osu.Game.Rulesets.Osu.Mods
if (!slider.HeadCircle.IsHit)
handleHitCircle(slider.HeadCircle);
requiresHold |= slider.SliderInputManager.IsMouseInFollowArea(true);
requiresHold |= slider.Ball.IsHovered || h.IsHovered;
break;
case DrawableSpinner spinner:
@@ -36,9 +36,6 @@ namespace osu.Game.Rulesets.Osu.Mods
{
if (e.NewValue || slider.Judged) return;
if (slider.Time.Current < slider.HitObject.StartTime)
return;
var tail = slider.NestedHitObjects.OfType<StrictTrackingDrawableSliderTail>().First();
if (!tail.Judged)
@@ -11,6 +11,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[]
{
typeof(OsuModAutopilot),
typeof(OsuModTargetPractice),
}).ToArray();
}
@@ -34,6 +34,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private Drawable scaleContainer;
public override bool DisplayResult => false;
public DrawableSliderRepeat()
: base(null)
{
@@ -24,6 +24,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected DrawableSlider DrawableSlider => (DrawableSlider)ParentHitObject;
/// <summary>
/// The judgement text is provided by the <see cref="DrawableSlider"/>.
/// </summary>
public override bool DisplayResult => false;
/// <summary>
/// Whether the hit samples only play on successful hits.
/// If <c>false</c>, the hit samples will also play on misses.
@@ -20,6 +20,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private const float default_tick_size = 16;
public override bool DisplayResult => false;
protected DrawableSlider DrawableSlider => (DrawableSlider)ParentHitObject;
private SkinnableDrawable scaleContainer;
@@ -17,7 +17,6 @@ using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Rulesets.Osu.Scoring;
using osu.Game.Rulesets.Osu.Skinning;
using osu.Game.Rulesets.Osu.Skinning.Default;
using osu.Game.Rulesets.Scoring;
@@ -313,7 +312,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
updateBonusScore();
}
private static readonly int score_per_tick = new OsuScoreProcessor().GetBaseScoreForResult(new SpinnerBonusTick.OsuSpinnerBonusTickJudgement().MaxResult);
private static readonly int score_per_tick = new SpinnerBonusTick.OsuSpinnerBonusTickJudgement().MaxNumericResult;
private void updateBonusScore()
{
@@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected override void Update()
{
base.Update();
updateTracking(IsMouseInFollowArea(Tracking));
updateTracking(isMouseInFollowArea(Tracking));
}
public void PostProcessHeadJudgement(DrawableSliderHead head)
@@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
if (!head.Judged || !head.Result.IsHit)
return;
if (!IsMouseInFollowArea(true))
if (!isMouseInFollowArea(true))
return;
Debug.Assert(screenSpaceMousePosition != null);
@@ -129,7 +129,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
// If all ticks were hit so far, enable tracking the full extent.
// If any ticks were missed, assume tracking would've broken at some point, and should only activate if the cursor is within the slider ball.
// For the second case, this may be the last chance we have to enable tracking before other objects get judged, otherwise the same would normally happen via Update().
updateTracking(allTicksInRange || IsMouseInFollowArea(false));
updateTracking(allTicksInRange || isMouseInFollowArea(false));
}
public void TryJudgeNestedObject(DrawableOsuHitObject nestedObject, double timeOffset)
@@ -174,7 +174,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
/// Whether the mouse is currently in the follow area.
/// </summary>
/// <param name="expanded">Whether to test against the maximum area of the follow circle.</param>
public bool IsMouseInFollowArea(bool expanded)
private bool isMouseInFollowArea(bool expanded)
{
if (screenSpaceMousePosition is not Vector2 pos)
return false;
-2
View File
@@ -48,8 +48,6 @@ namespace osu.Game.Rulesets.Osu
public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor();
public override HealthProcessor CreateHealthProcessor(double drainStartTime) => new OsuHealthProcessor(drainStartTime);
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap, this);
public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new OsuBeatmapProcessor(beatmap);
@@ -1,127 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Scoring
{
public partial class OsuHealthProcessor : DrainingHealthProcessor
{
private ComboResult currentComboResult = ComboResult.Perfect;
public OsuHealthProcessor(double drainStartTime, double drainLenience = 0)
: base(drainStartTime, drainLenience)
{
}
protected override double GetHealthIncreaseFor(JudgementResult result)
{
if (IsSimulating)
return getHealthIncreaseFor(result);
if (result.HitObject is not IHasComboInformation combo)
return getHealthIncreaseFor(result);
if (combo.NewCombo)
currentComboResult = ComboResult.Perfect;
switch (result.Type)
{
case HitResult.LargeTickMiss:
case HitResult.Ok:
setComboResult(ComboResult.Good);
break;
case HitResult.Meh:
case HitResult.Miss:
setComboResult(ComboResult.None);
break;
}
// The slider tail has a special judgement that can't accurately be described above.
if (result.HitObject is SliderTailCircle && !result.IsHit)
setComboResult(ComboResult.Good);
if (combo.LastInCombo && result.Type.IsHit())
{
switch (currentComboResult)
{
case ComboResult.Perfect:
return getHealthIncreaseFor(result) + 0.07;
case ComboResult.Good:
return getHealthIncreaseFor(result) + 0.05;
default:
return getHealthIncreaseFor(result) + 0.03;
}
}
return getHealthIncreaseFor(result);
void setComboResult(ComboResult comboResult) => currentComboResult = (ComboResult)Math.Min((int)currentComboResult, (int)comboResult);
}
protected override void Reset(bool storeResults)
{
base.Reset(storeResults);
currentComboResult = ComboResult.Perfect;
}
private double getHealthIncreaseFor(JudgementResult result)
{
switch (result.Type)
{
case HitResult.SmallTickMiss:
return IBeatmapDifficultyInfo.DifficultyRange(Beatmap.Difficulty.DrainRate, -0.02, -0.075, -0.14);
case HitResult.LargeTickMiss:
return IBeatmapDifficultyInfo.DifficultyRange(Beatmap.Difficulty.DrainRate, -0.02, -0.075, -0.14);
case HitResult.Miss:
return IBeatmapDifficultyInfo.DifficultyRange(Beatmap.Difficulty.DrainRate, -0.03, -0.125, -0.2);
case HitResult.SmallTickHit:
// When classic slider mechanics are enabled, this result comes from the tail.
return 0.02;
case HitResult.LargeTickHit:
switch (result.HitObject)
{
case SliderTick:
return 0.015;
case SliderHeadCircle:
case SliderTailCircle:
case SliderRepeat:
return 0.02;
}
break;
case HitResult.Meh:
return 0.002;
case HitResult.Ok:
return 0.011;
case HitResult.Great:
return 0.03;
case HitResult.SmallBonus:
return 0.0085;
case HitResult.LargeBonus:
return 0.01;
}
return base.GetHealthIncreaseFor(result);
}
}
}
@@ -62,23 +62,25 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
/// </remarks>
public virtual void PlayAnimation()
{
if (Result.IsMiss())
switch (Result)
{
this.ScaleTo(1.6f);
this.ScaleTo(1, 100, Easing.In);
default:
JudgementText
.FadeInFromZero(300, Easing.OutQuint)
.ScaleTo(Vector2.One)
.ScaleTo(new Vector2(1.2f), 1800, Easing.OutQuint);
break;
this.MoveTo(Vector2.Zero);
this.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint);
case HitResult.Miss:
this.ScaleTo(1.6f);
this.ScaleTo(1, 100, Easing.In);
this.RotateTo(0);
this.RotateTo(40, 800, Easing.InQuint);
}
else
{
JudgementText
.FadeInFromZero(300, Easing.OutQuint)
.ScaleTo(Vector2.One)
.ScaleTo(new Vector2(1.2f), 1800, Easing.OutQuint);
this.MoveTo(Vector2.Zero);
this.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint);
this.RotateTo(0);
this.RotateTo(40, 800, Easing.InQuint);
break;
}
this.FadeOutFromOne(800);
@@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
{
case GameplaySkinComponentLookup<HitResult> resultComponent:
// This should eventually be moved to a skin setting, when supported.
if (Skin is ArgonProSkin && (resultComponent.Component == HitResult.Great || resultComponent.Component == HitResult.Perfect))
if (Skin is ArgonProSkin && resultComponent.Component >= HitResult.Great)
return Drawable.Empty();
return new ArgonJudgementPiece(resultComponent.Component);
@@ -160,7 +160,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
{
decimal? legacyVersion = skin.GetConfig<SkinConfiguration.LegacySetting, decimal>(SkinConfiguration.LegacySetting.Version)?.Value;
if (legacyVersion > 1.0m)
if (legacyVersion >= 2.0m)
// legacy skins of version 2.0 and newer only apply very short fade out to the number piece.
hitCircleText.FadeOut(legacy_fade_duration / 4);
else
+4 -19
View File
@@ -20,6 +20,7 @@ using osu.Game.Rulesets.Osu.Configuration;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables.Connections;
using osu.Game.Rulesets.Osu.Scoring;
using osu.Game.Rulesets.Osu.UI.Cursor;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
@@ -65,21 +66,8 @@ namespace osu.Game.Rulesets.Osu.UI
HitPolicy = new StartTimeOrderedHitPolicy();
foreach (var result in Enum.GetValues<HitResult>().Where(r =>
{
switch (r)
{
case HitResult.Great:
case HitResult.Ok:
case HitResult.Meh:
case HitResult.Miss:
case HitResult.LargeTickMiss:
case HitResult.IgnoreMiss:
return true;
}
return false;
}))
var hitWindows = new OsuHitWindows();
foreach (var result in Enum.GetValues<HitResult>().Where(r => r > HitResult.None && hitWindows.IsHitResultAllowed(r)))
poolDictionary.Add(result, new DrawableJudgementPool(result, onJudgementLoaded));
AddRangeInternal(poolDictionary.Values);
@@ -182,10 +170,7 @@ namespace osu.Game.Rulesets.Osu.UI
if (!judgedObject.DisplayResult || !DisplayJudgements.Value)
return;
if (!poolDictionary.TryGetValue(result.Type, out var pool))
return;
DrawableOsuJudgement explosion = pool.Get(doj => doj.Apply(result, judgedObject));
DrawableOsuJudgement explosion = poolDictionary[result.Type].Get(doj => doj.Apply(result, judgedObject));
judgementLayer.Add(explosion);
@@ -10,7 +10,7 @@ using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Taiko.Tests.Mods
{
public partial class TestSceneTaikoModPerfect : ModFailConditionTestScene
public partial class TestSceneTaikoModPerfect : ModPerfectTestScene
{
protected override Ruleset CreatePlayerRuleset() => new TestTaikoRuleset();
@@ -1,41 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Judgements;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Scoring;
namespace osu.Game.Rulesets.Taiko.Tests
{
[TestFixture]
public class TaikoScoreProcessorTest
{
[Test]
public void TestInaccurateHitScore()
{
var beatmap = new Beatmap<HitObject>
{
HitObjects =
{
new Hit(),
new Hit { StartTime = 1000 }
}
};
var scoreProcessor = new TaikoScoreProcessor();
scoreProcessor.ApplyBeatmap(beatmap);
// Apply a miss judgement
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], new TaikoJudgement()) { Type = HitResult.Great });
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[1], new TaikoJudgement()) { Type = HitResult.Ok });
Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(453745));
Assert.That(scoreProcessor.Accuracy.Value, Is.EqualTo(0.75).Within(0.0001));
}
}
}
@@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
@@ -12,14 +13,11 @@ using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Scoring.Legacy;
using osu.Game.Rulesets.Taiko.Mods;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Scoring;
namespace osu.Game.Rulesets.Taiko.Difficulty
{
internal class TaikoLegacyScoreSimulator : ILegacyScoreSimulator
{
private readonly ScoreProcessor scoreProcessor = new TaikoScoreProcessor();
private int legacyBonusScore;
private int standardisedBonusScore;
private int combo;
@@ -193,7 +191,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
if (isBonus)
{
legacyBonusScore += scoreIncrease;
standardisedBonusScore += scoreProcessor.GetBaseScoreForResult(bonusResult);
standardisedBonusScore += Judgement.ToNumericResult(bonusResult);
}
else
attributes.AccuracyScore += scoreIncrease;
@@ -6,7 +6,6 @@
using System;
using osu.Framework.Graphics;
using osu.Framework.Input.Events;
using osu.Framework.Utils;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
@@ -26,7 +25,7 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
private readonly IHasDuration spanPlacementObject;
protected override bool IsValidForPlacement => Precision.DefinitelyBigger(spanPlacementObject.Duration, 0);
protected override bool IsValidForPlacement => spanPlacementObject.Duration > 0;
public TaikoSpanPlacementBlueprint(HitObject hitObject)
: base(hitObject)
@@ -28,22 +28,11 @@ namespace osu.Game.Rulesets.Taiko.Scoring
protected override double GetComboScoreChange(JudgementResult result)
{
return GetBaseScoreForResult(result.Type)
return Judgement.ToNumericResult(result.Type)
* Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(400, combo_base))
* strongScaleValue(result);
}
public override int GetBaseScoreForResult(HitResult result)
{
switch (result)
{
case HitResult.Ok:
return 150;
}
return base.GetBaseScoreForResult(result);
}
private double strongScaleValue(JudgementResult result)
{
if (result.HitObject is StrongNestedHitObject strong)
@@ -219,8 +219,6 @@ namespace osu.Game.Tests.Beatmaps.Formats
{
new OsuModDoubleTime { SpeedChange = { Value = 1.1 } }
};
scoreInfo.OnlineID = 123123;
scoreInfo.ClientVersion = "2023.1221.0";
var beatmap = new TestBeatmap(ruleset);
var score = new Score
@@ -239,11 +237,9 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.Multiple(() =>
{
Assert.That(decodedAfterEncode.ScoreInfo.OnlineID, Is.EqualTo(123123));
Assert.That(decodedAfterEncode.ScoreInfo.Statistics, Is.EqualTo(scoreInfo.Statistics));
Assert.That(decodedAfterEncode.ScoreInfo.MaximumStatistics, Is.EqualTo(scoreInfo.MaximumStatistics));
Assert.That(decodedAfterEncode.ScoreInfo.Mods, Is.EqualTo(scoreInfo.Mods));
Assert.That(decodedAfterEncode.ScoreInfo.ClientVersion, Is.EqualTo("2023.1221.0"));
});
}
@@ -48,7 +48,7 @@ namespace osu.Game.Tests.Gameplay
// Apply a judgement
scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new TestJudgement(HitResult.LargeBonus)) { Type = HitResult.LargeBonus });
Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(scoreProcessor.GetBaseScoreForResult(HitResult.LargeBonus)));
Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(Judgement.LARGE_BONUS_SCORE));
}
[Test]
@@ -196,7 +196,6 @@ namespace osu.Game.Tests.Scores.IO
User = new APIUser { Username = "Test user" },
BeatmapInfo = beatmap.Beatmaps.First(),
Ruleset = new OsuRuleset().RulesetInfo,
ClientVersion = "12345",
Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() },
};
@@ -204,7 +203,6 @@ namespace osu.Game.Tests.Scores.IO
Assert.IsTrue(imported.Mods.Any(m => m is OsuModHardRock));
Assert.IsTrue(imported.Mods.Any(m => m is OsuModDoubleTime));
Assert.That(imported.ClientVersion, Is.EqualTo(toImport.ClientVersion));
}
finally
{
@@ -41,9 +41,6 @@ namespace osu.Game.Tests.Visual.Gameplay
private BeatmapSetInfo? importedSet;
[Resolved]
private OsuGameBase osu { get; set; } = null!;
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
{
@@ -156,7 +153,6 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen);
AddUntilStep("score in database", () => Realm.Run(r => r.Find<ScoreInfo>(Player.Score.ScoreInfo.ID) != null));
AddUntilStep("score has correct version", () => Realm.Run(r => r.Find<ScoreInfo>(Player.Score.ScoreInfo.ID)!.ClientVersion), () => Is.EqualTo(osu.Version));
}
[Test]
@@ -81,21 +81,6 @@ namespace osu.Game.Tests.Visual.Online
AddStep("End watching user presence", () => metadataClient.EndWatchingUserPresence());
}
[Test]
public void TestUserWasPlayingBeforeWatchingUserPresence()
{
AddStep("User began playing", () => spectatorClient.SendStartPlay(streamingUser.Id, 0));
AddStep("Begin watching user presence", () => metadataClient.BeginWatchingUserPresence());
AddStep("Add online user", () => metadataClient.UserPresenceUpdated(streamingUser.Id, new UserPresence { Status = UserStatus.Online, Activity = new UserActivity.ChoosingBeatmap() }));
AddUntilStep("Panel loaded", () => currentlyOnline.ChildrenOfType<UserGridPanel>().FirstOrDefault()?.User.Id == 2);
AddAssert("Spectate button enabled", () => currentlyOnline.ChildrenOfType<PurpleRoundedButton>().First().Enabled.Value, () => Is.True);
AddStep("User finished playing", () => spectatorClient.SendEndPlay(streamingUser.Id));
AddAssert("Spectate button disabled", () => currentlyOnline.ChildrenOfType<PurpleRoundedButton>().First().Enabled.Value, () => Is.False);
AddStep("Remove playing user", () => metadataClient.UserPresenceUpdated(streamingUser.Id, null));
AddStep("End watching user presence", () => metadataClient.EndWatchingUserPresence());
}
internal partial class TestUserLookupCache : UserLookupCache
{
private static readonly string[] usernames =
@@ -3,7 +3,6 @@
#nullable disable
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
@@ -15,7 +14,6 @@ using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Extensions;
using osu.Game.Graphics.Sprites;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Rulesets;
@@ -196,36 +194,6 @@ namespace osu.Game.Tests.Visual.SongSelect
});
}
[TestCase]
public void TestLengthUpdates()
{
IBeatmap beatmap = createTestBeatmap(new OsuRuleset().RulesetInfo);
double drain = beatmap.CalculateDrainLength();
beatmap.BeatmapInfo.Length = drain;
OsuModDoubleTime doubleTime = null;
selectBeatmap(beatmap);
checkDisplayedLength(drain);
AddStep("select DT", () => SelectedMods.Value = new[] { doubleTime = new OsuModDoubleTime() });
checkDisplayedLength(Math.Round(drain / 1.5f));
AddStep("change DT rate", () => doubleTime.SpeedChange.Value = 2);
checkDisplayedLength(Math.Round(drain / 2));
}
private void checkDisplayedLength(double drain)
{
var displayedLength = drain.ToFormattedDuration();
AddUntilStep($"check map drain ({displayedLength})", () =>
{
var label = infoWedge.DisplayedContent.ChildrenOfType<BeatmapInfoWedge.WedgeInfoText.InfoLabel>().Single(l => l.Statistic.Name == BeatmapsetsStrings.ShowStatsTotalLength(displayedLength));
return label.Statistic.Content == displayedLength.ToString();
});
}
private void setRuleset(RulesetInfo rulesetInfo)
{
Container containerBefore = null;
@@ -420,7 +420,6 @@ namespace osu.Game.Tests.Visual.SongSelect
}
[Test]
[Ignore("temporary while peppy investigates. probably realm batching related.")]
public void TestSelectionRetainedOnBeatmapUpdate()
{
createSongSelect();
+4 -1
View File
@@ -340,12 +340,15 @@ namespace osu.Game
try
{
var score = scoreManager.Query(s => s.ID == id);
long newTotalScore = StandardisedScoreMigrationTools.ConvertFromLegacyTotalScore(score, beatmapManager);
// Can't use async overload because we're not on the update thread.
// ReSharper disable once MethodHasAsyncOverload
realmAccess.Write(r =>
{
ScoreInfo s = r.Find<ScoreInfo>(id)!;
StandardisedScoreMigrationTools.UpdateFromLegacy(s, beatmapManager);
s.TotalScore = newTotalScore;
s.TotalScoreVersion = LegacyScoreEncoder.LATEST_VERSION;
});
+1 -1
View File
@@ -37,7 +37,7 @@ namespace osu.Game.Configuration
SetDefault(OsuSetting.Ruleset, string.Empty);
SetDefault(OsuSetting.Skin, SkinInfo.ARGON_SKIN.ToString());
SetDefault(OsuSetting.BeatmapDetailTab, PlayBeatmapDetailArea.TabType.Local);
SetDefault(OsuSetting.BeatmapDetailTab, PlayBeatmapDetailArea.TabType.Details);
SetDefault(OsuSetting.BeatmapDetailModsFilter, false);
SetDefault(OsuSetting.ShowConvertedBeatmaps, true);
+1 -2
View File
@@ -90,9 +90,8 @@ namespace osu.Game.Database
/// 36 2023-10-26 Add LegacyOnlineID to ScoreInfo. Move osu_scores_*_high IDs stored in OnlineID to LegacyOnlineID. Reset anomalous OnlineIDs.
/// 38 2023-12-10 Add EndTimeObjectCount and TotalObjectCount to BeatmapInfo.
/// 39 2023-12-19 Migrate any EndTimeObjectCount and TotalObjectCount values of 0 to -1 to better identify non-calculated values.
/// 40 2023-12-21 Add ScoreInfo.Version to keep track of which build scores were set on.
/// </summary>
private const int schema_version = 40;
private const int schema_version = 39;
/// <summary>
/// Lock object which is held during <see cref="BlockAllOperations"/> sections, blocking realm retrieval during blocking periods.
@@ -57,14 +57,14 @@ namespace osu.Game.Database
// We are constructing a "best possible" score from the statistics provided because it's the best we can do.
List<HitResult> sortedHits = score.Statistics
.Where(kvp => kvp.Key.AffectsCombo())
.OrderByDescending(kvp => processor.GetBaseScoreForResult(kvp.Key))
.OrderByDescending(kvp => Judgement.ToNumericResult(kvp.Key))
.SelectMany(kvp => Enumerable.Repeat(kvp.Key, kvp.Value))
.ToList();
// Attempt to use maximum statistics from the database.
var maximumJudgements = score.MaximumStatistics
.Where(kvp => kvp.Key.AffectsCombo())
.OrderByDescending(kvp => processor.GetBaseScoreForResult(kvp.Key))
.OrderByDescending(kvp => Judgement.ToNumericResult(kvp.Key))
.SelectMany(kvp => Enumerable.Repeat(new FakeJudgement(kvp.Key), kvp.Value))
.ToList();
@@ -169,10 +169,10 @@ namespace osu.Game.Database
public static long GetOldStandardised(ScoreInfo score)
{
double accuracyScore =
(double)score.Statistics.Where(kvp => kvp.Key.AffectsAccuracy()).Sum(kvp => numericScoreFor(kvp.Key) * kvp.Value)
/ score.MaximumStatistics.Where(kvp => kvp.Key.AffectsAccuracy()).Sum(kvp => numericScoreFor(kvp.Key) * kvp.Value);
(double)score.Statistics.Where(kvp => kvp.Key.AffectsAccuracy()).Sum(kvp => Judgement.ToNumericResult(kvp.Key) * kvp.Value)
/ score.MaximumStatistics.Where(kvp => kvp.Key.AffectsAccuracy()).Sum(kvp => Judgement.ToNumericResult(kvp.Key) * kvp.Value);
double comboScore = (double)score.MaxCombo / score.MaximumStatistics.Where(kvp => kvp.Key.AffectsCombo()).Sum(kvp => kvp.Value);
double bonusScore = score.Statistics.Where(kvp => kvp.Key.IsBonus()).Sum(kvp => numericScoreFor(kvp.Key) * kvp.Value);
double bonusScore = score.Statistics.Where(kvp => kvp.Key.IsBonus()).Sum(kvp => Judgement.ToNumericResult(kvp.Key) * kvp.Value);
double accuracyPortion = 0.3;
@@ -193,65 +193,6 @@ namespace osu.Game.Database
modMultiplier *= mod.ScoreMultiplier;
return (long)Math.Round((1000000 * (accuracyPortion * accuracyScore + (1 - accuracyPortion) * comboScore) + bonusScore) * modMultiplier);
static int numericScoreFor(HitResult result)
{
switch (result)
{
default:
return 0;
case HitResult.SmallTickHit:
return 10;
case HitResult.LargeTickHit:
return 30;
case HitResult.Meh:
return 50;
case HitResult.Ok:
return 100;
case HitResult.Good:
return 200;
case HitResult.Great:
return 300;
case HitResult.Perfect:
return 315;
case HitResult.SmallBonus:
return 10;
case HitResult.LargeBonus:
return 50;
}
}
}
/// <summary>
/// Updates a legacy <see cref="ScoreInfo"/> to standardised scoring.
/// </summary>
/// <param name="score">The score to update.</param>
/// <param name="beatmaps">A <see cref="BeatmapManager"/> used for <see cref="WorkingBeatmap"/> lookups.</param>
public static void UpdateFromLegacy(ScoreInfo score, BeatmapManager beatmaps)
{
score.TotalScore = convertFromLegacyTotalScore(score, beatmaps);
score.Accuracy = ComputeAccuracy(score);
}
/// <summary>
/// Updates a legacy <see cref="ScoreInfo"/> to standardised scoring.
/// </summary>
/// <param name="score">The score to update.</param>
/// <param name="difficulty">The beatmap difficulty.</param>
/// <param name="attributes">The legacy scoring attributes for the beatmap which the score was set on.</param>
public static void UpdateFromLegacy(ScoreInfo score, LegacyBeatmapConversionDifficultyInfo difficulty, LegacyScoreAttributes attributes)
{
score.TotalScore = convertFromLegacyTotalScore(score, difficulty, attributes);
score.Accuracy = ComputeAccuracy(score);
}
/// <summary>
@@ -260,7 +201,7 @@ namespace osu.Game.Database
/// <param name="score">The score to convert the total score of.</param>
/// <param name="beatmaps">A <see cref="BeatmapManager"/> used for <see cref="WorkingBeatmap"/> lookups.</param>
/// <returns>The standardised total score.</returns>
private static long convertFromLegacyTotalScore(ScoreInfo score, BeatmapManager beatmaps)
public static long ConvertFromLegacyTotalScore(ScoreInfo score, BeatmapManager beatmaps)
{
if (!score.IsLegacyScore)
return score.TotalScore;
@@ -283,7 +224,7 @@ namespace osu.Game.Database
ILegacyScoreSimulator sv1Simulator = legacyRuleset.CreateLegacyScoreSimulator();
LegacyScoreAttributes attributes = sv1Simulator.Simulate(beatmap, playableBeatmap);
return convertFromLegacyTotalScore(score, LegacyBeatmapConversionDifficultyInfo.FromBeatmap(beatmap.Beatmap), attributes);
return ConvertFromLegacyTotalScore(score, LegacyBeatmapConversionDifficultyInfo.FromBeatmap(beatmap.Beatmap), attributes);
}
/// <summary>
@@ -293,7 +234,7 @@ namespace osu.Game.Database
/// <param name="difficulty">The beatmap difficulty.</param>
/// <param name="attributes">The legacy scoring attributes for the beatmap which the score was set on.</param>
/// <returns>The standardised total score.</returns>
private static long convertFromLegacyTotalScore(ScoreInfo score, LegacyBeatmapConversionDifficultyInfo difficulty, LegacyScoreAttributes attributes)
public static long ConvertFromLegacyTotalScore(ScoreInfo score, LegacyBeatmapConversionDifficultyInfo difficulty, LegacyScoreAttributes attributes)
{
if (!score.IsLegacyScore)
return score.TotalScore;
@@ -312,12 +253,8 @@ namespace osu.Game.Database
double legacyAccScore = maximumLegacyAccuracyScore * score.Accuracy;
// We can not separate the ComboScore from the BonusScore, so we keep the bonus in the ratio.
// Note that `maximumLegacyComboScore + maximumLegacyBonusScore` can actually be 0
// when playing a beatmap with no bonus objects, with mods that have a 0.0x multiplier on stable (relax/autopilot).
// In such cases, just assume 0.
double comboProportion = maximumLegacyComboScore + maximumLegacyBonusScore > 0
? ((double)score.LegacyTotalScore - legacyAccScore) / (maximumLegacyComboScore + maximumLegacyBonusScore)
: 0;
double comboProportion =
((double)score.LegacyTotalScore - legacyAccScore) / (maximumLegacyComboScore + maximumLegacyBonusScore);
// We assume the bonus proportion only makes up the rest of the score that exceeds maximumLegacyBaseScore.
long maximumLegacyBaseScore = maximumLegacyAccuracyScore + maximumLegacyComboScore;
@@ -325,8 +262,6 @@ namespace osu.Game.Database
double modMultiplier = score.Mods.Select(m => m.ScoreMultiplier).Aggregate(1.0, (c, n) => c * n);
long convertedTotalScore;
switch (score.Ruleset.OnlineID)
{
case 0:
@@ -423,55 +358,32 @@ namespace osu.Game.Database
double newComboScoreProportion = estimatedComboPortionInStandardisedScore / maximumAchievableComboPortionInStandardisedScore;
convertedTotalScore = (long)Math.Round((
return (long)Math.Round((
500000 * newComboScoreProportion * score.Accuracy
+ 500000 * Math.Pow(score.Accuracy, 5)
+ bonusProportion) * modMultiplier);
break;
case 1:
convertedTotalScore = (long)Math.Round((
return (long)Math.Round((
250000 * comboProportion
+ 750000 * Math.Pow(score.Accuracy, 3.6)
+ bonusProportion) * modMultiplier);
break;
case 2:
convertedTotalScore = (long)Math.Round((
return (long)Math.Round((
600000 * comboProportion
+ 400000 * score.Accuracy
+ bonusProportion) * modMultiplier);
break;
case 3:
convertedTotalScore = (long)Math.Round((
850000 * comboProportion
+ 150000 * Math.Pow(score.Accuracy, 2 + 2 * score.Accuracy)
return (long)Math.Round((
990000 * comboProportion
+ 10000 * Math.Pow(score.Accuracy, 2 + 2 * score.Accuracy)
+ bonusProportion) * modMultiplier);
break;
default:
convertedTotalScore = score.TotalScore;
break;
return score.TotalScore;
}
if (convertedTotalScore < 0)
throw new InvalidOperationException($"Total score conversion operation returned invalid total of {convertedTotalScore}");
return convertedTotalScore;
}
public static double ComputeAccuracy(ScoreInfo scoreInfo)
{
Ruleset ruleset = scoreInfo.Ruleset.CreateInstance();
ScoreProcessor scoreProcessor = ruleset.CreateScoreProcessor();
int baseScore = scoreInfo.Statistics.Where(kvp => kvp.Key.AffectsAccuracy())
.Sum(kvp => kvp.Value * scoreProcessor.GetBaseScoreForResult(kvp.Key));
int maxBaseScore = scoreInfo.MaximumStatistics.Where(kvp => kvp.Key.AffectsAccuracy())
.Sum(kvp => kvp.Value * scoreProcessor.GetBaseScoreForResult(kvp.Key));
return maxBaseScore == 0 ? 1 : baseScore / (double)maxBaseScore;
}
/// <summary>
+1 -4
View File
@@ -75,12 +75,9 @@ namespace osu.Game.Graphics
{
switch (result)
{
case HitResult.IgnoreMiss:
case HitResult.SmallTickMiss:
return Orange1;
case HitResult.Miss:
case HitResult.LargeTickMiss:
case HitResult.Miss:
case HitResult.ComboBreak:
return Red;
@@ -160,8 +160,6 @@ namespace osu.Game.Input.Bindings
new KeyBinding(InputKey.Enter, GlobalAction.ToggleChatFocus),
new KeyBinding(InputKey.F1, GlobalAction.SaveReplay),
new KeyBinding(InputKey.F2, GlobalAction.ExportReplay),
new KeyBinding(InputKey.Plus, GlobalAction.IncreaseOffset),
new KeyBinding(InputKey.Minus, GlobalAction.DecreaseOffset),
};
private static IEnumerable<KeyBinding> replayKeyBindings => new[]
@@ -406,12 +404,6 @@ namespace osu.Game.Input.Bindings
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorToggleRotateControl))]
EditorToggleRotateControl,
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.IncreaseOffset))]
IncreaseOffset,
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.DecreaseOffset))]
DecreaseOffset
}
public enum GlobalActionCategory
@@ -344,16 +344,6 @@ namespace osu.Game.Localisation
/// </summary>
public static LocalisableString ExportReplay => new TranslatableString(getKey(@"export_replay"), @"Export replay");
/// <summary>
/// "Increase offset"
/// </summary>
public static LocalisableString IncreaseOffset => new TranslatableString(getKey(@"increase_offset"), @"Increase offset");
/// <summary>
/// "Decrease offset"
/// </summary>
public static LocalisableString DecreaseOffset => new TranslatableString(getKey(@"decrease_offset"), @"Decrease offset");
/// <summary>
/// "Toggle rotate control"
/// </summary>
@@ -97,11 +97,8 @@ namespace osu.Game.Online.Metadata
{
if (!connected.NewValue)
{
Schedule(() =>
{
isWatchingUserPresence.Value = false;
userStates.Clear();
});
isWatchingUserPresence.Value = false;
userStates.Clear();
return;
}
@@ -190,13 +187,13 @@ namespace osu.Game.Online.Metadata
public override Task UserPresenceUpdated(int userId, UserPresence? presence)
{
Schedule(() =>
lock (userStates)
{
if (presence != null)
userStates[userId] = presence.Value;
else
userStates.Remove(userId);
});
}
return Task.CompletedTask;
}
@@ -208,7 +205,7 @@ namespace osu.Game.Online.Metadata
Debug.Assert(connection != null);
await connection.InvokeAsync(nameof(IMetadataServer.BeginWatchingUserPresence)).ConfigureAwait(false);
Schedule(() => isWatchingUserPresence.Value = true);
isWatchingUserPresence.Value = true;
}
public override async Task EndWatchingUserPresence()
@@ -218,14 +215,14 @@ namespace osu.Game.Online.Metadata
if (connector?.IsConnected.Value != true)
throw new OperationCanceledException();
// must be scheduled before any remote calls to avoid mis-ordering.
Schedule(() => userStates.Clear());
// must happen synchronously before any remote calls to avoid misordering.
userStates.Clear();
Debug.Assert(connection != null);
await connection.InvokeAsync(nameof(IMetadataServer.EndWatchingUserPresence)).ConfigureAwait(false);
}
finally
{
Schedule(() => isWatchingUserPresence.Value = false);
isWatchingUserPresence.Value = false;
}
}
+2 -9
View File
@@ -527,21 +527,14 @@ namespace osu.Game
{
ManualResetEventSlim readyToRun = new ManualResetEventSlim();
bool success = false;
Scheduler.Add(() =>
{
try
{
realmBlocker = realm.BlockAllOperations("migration");
success = true;
}
catch { }
realmBlocker = realm.BlockAllOperations("migration");
readyToRun.Set();
}, false);
if (!readyToRun.Wait(30000) || !success)
if (!readyToRun.Wait(30000))
throw new TimeoutException("Attempting to block for migration took too long.");
bool? cleanupSucceded = (Storage as OsuStorage)?.Migrate(Host.GetStorage(path));
@@ -6,7 +6,6 @@
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
@@ -219,7 +218,6 @@ namespace osu.Game.Overlays.Dashboard
{
panel.Anchor = Anchor.TopCentre;
panel.Origin = Anchor.TopCentre;
panel.CanSpectate.Value = playingUsers.Contains(user.Id);
});
public partial class OnlineUserPanel : CompositeDrawable, IFilterable
@@ -232,14 +232,6 @@ namespace osu.Game.Overlays.Profile.Header
bool expanded = coverToggle.CoverExpanded.Value;
cover.ResizeHeightTo(expanded ? 250 : 0, transition_duration, Easing.OutQuint);
// Without this a very tiny slither of the cover will be visible even with a size of zero.
// Integer masking woes, no doubt.
if (expanded)
cover.FadeIn(transition_duration, Easing.OutQuint);
else
cover.FadeOut(transition_duration, Easing.InQuint);
avatar.ResizeTo(new Vector2(expanded ? 120 : content_height), transition_duration, Easing.OutQuint);
avatar.TransformTo(nameof(avatar.CornerRadius), expanded ? 40f : 20f, transition_duration, Easing.OutQuint);
flow.TransformTo(nameof(flow.Spacing), new Vector2(expanded ? 20f : 10f), transition_duration, Easing.OutQuint);
@@ -136,10 +136,9 @@ namespace osu.Game.Overlays.SkinEditor
{
base.Update();
Vector2 scale = drawable.DrawInfo.MatrixInverse.ExtractScale().Xy;
drawableQuad = drawable.ToScreenSpace(
drawable.DrawRectangle
.Inflate(SkinSelectionHandler.INFLATE_SIZE * scale));
.Inflate(SkinSelectionHandler.INFLATE_SIZE));
var localSpaceQuad = ToLocalSpace(drawableQuad);
+13 -14
View File
@@ -157,15 +157,6 @@ namespace osu.Game.Overlays.Toolbar
};
}
[BackgroundDependencyLoader]
private void load()
{
if (Hotkey != null)
{
realm.SubscribeToPropertyChanged(r => r.All<RealmKeyBinding>().FirstOrDefault(rkb => rkb.RulesetName == null && rkb.ActionInt == (int)Hotkey.Value), kb => kb.KeyCombinationString, updateKeyBindingTooltip);
}
}
protected override bool OnMouseDown(MouseDownEvent e) => false;
protected override bool OnClick(ClickEvent e)
@@ -177,6 +168,8 @@ namespace osu.Game.Overlays.Toolbar
protected override bool OnHover(HoverEvent e)
{
updateKeyBindingTooltip();
HoverBackground.FadeIn(200);
tooltipContainer.FadeIn(100);
@@ -204,13 +197,19 @@ namespace osu.Game.Overlays.Toolbar
{
}
private void updateKeyBindingTooltip(string keyCombination)
private void updateKeyBindingTooltip()
{
string keyBindingString = keyCombinationProvider.GetReadableString(keyCombination);
if (Hotkey == null) return;
keyBindingTooltip.Text = !string.IsNullOrEmpty(keyBindingString)
? $" ({keyBindingString})"
: string.Empty;
var realmKeyBinding = realm.Realm.All<RealmKeyBinding>().FirstOrDefault(rkb => rkb.RulesetName == null && rkb.ActionInt == (int)Hotkey.Value);
if (realmKeyBinding != null)
{
string keyBindingString = keyCombinationProvider.GetReadableString(realmKeyBinding.KeyCombination);
if (!string.IsNullOrEmpty(keyBindingString))
keyBindingTooltip.Text = $" ({keyBindingString})";
}
}
}
@@ -38,16 +38,18 @@ namespace osu.Game.Rulesets.Judgements
/// </remarks>
public virtual void PlayAnimation()
{
if (Result != HitResult.None && !Result.IsHit())
switch (Result)
{
this.ScaleTo(1.6f);
this.ScaleTo(1, 100, Easing.In);
case HitResult.Miss:
this.ScaleTo(1.6f);
this.ScaleTo(1, 100, Easing.In);
this.MoveTo(Vector2.Zero);
this.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint);
this.MoveTo(Vector2.Zero);
this.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint);
this.RotateTo(0);
this.RotateTo(40, 800, Easing.InQuint);
this.RotateTo(0);
this.RotateTo(40, 800, Easing.InQuint);
break;
}
this.FadeOutFromOne(800);
@@ -133,11 +133,12 @@ namespace osu.Game.Rulesets.Judgements
case HitResult.None:
break;
case HitResult.Miss:
ApplyMissAnimations();
break;
default:
if (Result.Type.IsHit())
ApplyHitAnimations();
else
ApplyMissAnimations();
ApplyHitAnimations();
break;
}
+58 -1
View File
@@ -11,6 +11,16 @@ namespace osu.Game.Rulesets.Judgements
/// </summary>
public class Judgement
{
/// <summary>
/// The score awarded for a small bonus.
/// </summary>
public const int SMALL_BONUS_SCORE = 10;
/// <summary>
/// The score awarded for a large bonus.
/// </summary>
public const int LARGE_BONUS_SCORE = 50;
/// <summary>
/// The default health increase for a maximum judgement, as a proportion of total health.
/// By default, each maximum judgement restores 5% of total health.
@@ -81,11 +91,23 @@ namespace osu.Game.Rulesets.Judgements
}
}
/// <summary>
/// The numeric score representation for the maximum achievable result.
/// </summary>
public int MaxNumericResult => ToNumericResult(MaxResult);
/// <summary>
/// The health increase for the maximum achievable result.
/// </summary>
public double MaxHealthIncrease => HealthIncreaseFor(MaxResult);
/// <summary>
/// Retrieves the numeric score representation of a <see cref="JudgementResult"/>.
/// </summary>
/// <param name="result">The <see cref="JudgementResult"/> to find the numeric score representation for.</param>
/// <returns>The numeric score representation of <paramref name="result"/>.</returns>
public int NumericResultFor(JudgementResult result) => ToNumericResult(result.Type);
/// <summary>
/// Retrieves the numeric health increase of a <see cref="HitResult"/>.
/// </summary>
@@ -143,6 +165,41 @@ namespace osu.Game.Rulesets.Judgements
/// <returns>The numeric health increase of <paramref name="result"/>.</returns>
public double HealthIncreaseFor(JudgementResult result) => HealthIncreaseFor(result.Type);
public override string ToString() => $"MaxResult:{MaxResult}";
public override string ToString() => $"MaxResult:{MaxResult} MaxScore:{MaxNumericResult}";
public static int ToNumericResult(HitResult result)
{
switch (result)
{
default:
return 0;
case HitResult.SmallTickHit:
return 10;
case HitResult.LargeTickHit:
return 30;
case HitResult.Meh:
return 50;
case HitResult.Ok:
return 100;
case HitResult.Good:
return 200;
case HitResult.Great:
// Perfect doesn't actually give more score / accuracy directly.
case HitResult.Perfect:
return 300;
case HitResult.SmallBonus:
return SMALL_BONUS_SCORE;
case HitResult.LargeBonus:
return LARGE_BONUS_SCORE;
}
}
}
}
@@ -112,6 +112,6 @@ namespace osu.Game.Rulesets.Judgements
RawTime = null;
}
public override string ToString() => $"{Type} ({Judgement})";
public override string ToString() => $"{Type} (Score:{Judgement.NumericResultFor(this)} HP:{Judgement.HealthIncreaseFor(this)} {Judgement})";
}
}
+31
View File
@@ -0,0 +1,31 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Bindables;
using osu.Game.Configuration;
using osu.Game.Screens.Play;
namespace osu.Game.Rulesets.Mods
{
public abstract class ModBlockFail : Mod, IApplicableFailOverride, IApplicableToHUD, IReadFromConfig
{
private readonly Bindable<bool> showHealthBar = new Bindable<bool>();
/// <summary>
/// We never fail, 'yo.
/// </summary>
public bool PerformFail() => false;
public bool RestartOnFail => false;
public void ReadFromConfig(OsuConfigManager config)
{
config.BindWith(OsuSetting.ShowHealthDisplayWhenCantFail, showHealthBar);
}
public void ApplyToHUD(HUDOverlay overlay)
{
overlay.ShowHealthBar.BindTo(showHealthBar);
}
}
}
+2 -6
View File
@@ -23,14 +23,14 @@ namespace osu.Game.Rulesets.Mods
}
}
public class ModCinema : ModAutoplay, IApplicableToHUD, IApplicableToPlayer, IApplicableFailOverride
public class ModCinema : ModAutoplay, IApplicableToHUD, IApplicableToPlayer
{
public override string Name => "Cinema";
public override string Acronym => "CN";
public override IconUsage? Icon => OsuIcon.ModCinema;
public override LocalisableString Description => "Watch the video without visual distractions.";
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(ModAutoplay), typeof(ModNoFail) }).ToArray();
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModAutoplay)).ToArray();
public void ApplyToHUD(HUDOverlay overlay)
{
@@ -45,9 +45,5 @@ namespace osu.Game.Rulesets.Mods
player.BreakOverlay.Hide();
}
public bool PerformFail() => false;
public bool RestartOnFail => false;
}
}
+1 -1
View File
@@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Mods
public override string Acronym => "CL";
public override double ScoreMultiplier => 0.96;
public override double ScoreMultiplier => 0.5;
public override IconUsage? Icon => FontAwesome.Solid.History;
+1 -1
View File
@@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Mods
{
public abstract class ModFailCondition : Mod, IApplicableToHealthProcessor, IApplicableFailOverride
{
public override Type[] IncompatibleMods => new[] { typeof(ModNoFail) };
public override Type[] IncompatibleMods => new[] { typeof(ModNoFail), typeof(ModRelax) };
[SettingSource("Restart on fail", "Automatically restarts when failed.")]
public BindableBool Restart { get; } = new BindableBool();
+2 -24
View File
@@ -2,16 +2,13 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Screens.Play;
namespace osu.Game.Rulesets.Mods
{
public abstract class ModNoFail : Mod, IApplicableFailOverride, IApplicableToHUD, IReadFromConfig
public abstract class ModNoFail : ModBlockFail
{
public override string Name => "No Fail";
public override string Acronym => "NF";
@@ -19,25 +16,6 @@ namespace osu.Game.Rulesets.Mods
public override ModType Type => ModType.DifficultyReduction;
public override LocalisableString Description => "You can't fail, no matter what.";
public override double ScoreMultiplier => 0.5;
public override Type[] IncompatibleMods => new[] { typeof(ModFailCondition), typeof(ModCinema) };
private readonly Bindable<bool> showHealthBar = new Bindable<bool>();
/// <summary>
/// We never fail, 'yo.
/// </summary>
public bool PerformFail() => false;
public bool RestartOnFail => false;
public void ReadFromConfig(OsuConfigManager config)
{
config.BindWith(OsuSetting.ShowHealthDisplayWhenCantFail, showHealthBar);
}
public void ApplyToHUD(HUDOverlay overlay)
{
overlay.ShowHealthBar.BindTo(showHealthBar);
}
public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModFailCondition) };
}
}
+1 -3
View File
@@ -28,9 +28,7 @@ namespace osu.Game.Rulesets.Mods
}
protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result)
=> (isRelevantResult(result.Judgement.MinResult) || isRelevantResult(result.Judgement.MaxResult) || isRelevantResult(result.Type))
=> result.Type.AffectsAccuracy()
&& result.Type != result.Judgement.MaxResult;
private bool isRelevantResult(HitResult result) => result.AffectsAccuracy() || result.AffectsCombo();
}
}
+2 -2
View File
@@ -7,13 +7,13 @@ using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mods
{
public abstract class ModRelax : Mod
public abstract class ModRelax : ModBlockFail
{
public override string Name => "Relax";
public override string Acronym => "RX";
public override IconUsage? Icon => OsuIcon.ModRelax;
public override ModType Type => ModType.Automation;
public override double ScoreMultiplier => 0.1;
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay) };
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModNoFail), typeof(ModFailCondition) };
}
}
+1 -1
View File
@@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Objects
/// </summary>
public readonly Bindable<double?> ExpectedDistance = new Bindable<double?>();
public bool HasValidLength => Precision.DefinitelyBigger(Distance, 0);
public bool HasValidLength => Distance > 0;
/// <summary>
/// The control points of the path.
@@ -61,8 +61,7 @@ namespace osu.Game.Rulesets.Scoring
/// </summary>
protected readonly double DrainLenience;
private readonly List<HealthIncrease> healthIncreases = new List<HealthIncrease>();
private readonly List<(double time, double health)> healthIncreases = new List<(double, double)>();
private double gameplayEndTime;
private double targetMinimumHealth;
@@ -134,12 +133,8 @@ namespace osu.Game.Rulesets.Scoring
{
base.ApplyResultInternal(result);
if (IsSimulating && !result.Type.IsBonus())
{
healthIncreases.Add(new HealthIncrease(
result.HitObject.GetEndTime() + result.TimeOffset,
GetHealthIncreaseFor(result)));
}
if (!result.Type.IsBonus())
healthIncreases.Add((result.HitObject.GetEndTime() + result.TimeOffset, GetHealthIncreaseFor(result)));
}
protected override void Reset(bool storeResults)
@@ -170,8 +165,8 @@ namespace osu.Game.Rulesets.Scoring
for (int i = 0; i < healthIncreases.Count; i++)
{
double currentTime = healthIncreases[i].Time;
double lastTime = i > 0 ? healthIncreases[i - 1].Time : DrainStartTime;
double currentTime = healthIncreases[i].time;
double lastTime = i > 0 ? healthIncreases[i - 1].time : DrainStartTime;
while (currentBreak < Beatmap.Breaks.Count && Beatmap.Breaks[currentBreak].EndTime <= currentTime)
{
@@ -185,7 +180,7 @@ namespace osu.Game.Rulesets.Scoring
// Apply health adjustments
currentHealth -= (currentTime - lastTime) * result;
lowestHealth = Math.Min(lowestHealth, currentHealth);
currentHealth = Math.Min(1, currentHealth + healthIncreases[i].Amount);
currentHealth = Math.Min(1, currentHealth + healthIncreases[i].health);
// Common scenario for when the drain rate is definitely too harsh
if (lowestHealth < 0)
@@ -203,7 +198,5 @@ namespace osu.Game.Rulesets.Scoring
return result;
}
private record struct HealthIncrease(double Time, double Amount);
}
}
-27
View File
@@ -86,7 +86,6 @@ namespace osu.Game.Rulesets.Scoring
/// Indicates a large tick miss.
/// </summary>
[EnumMember(Value = "large_tick_miss")]
[Description(@"x")]
[Order(10)]
LargeTickMiss,
@@ -118,7 +117,6 @@ namespace osu.Game.Rulesets.Scoring
/// Indicates a miss that should be ignored for scoring purposes.
/// </summary>
[EnumMember(Value = "ignore_miss")]
[Description("x")]
[Order(13)]
IgnoreMiss,
@@ -269,34 +267,9 @@ namespace osu.Game.Rulesets.Scoring
}
}
/// <summary>
/// Whether a <see cref="HitResult"/> represents a miss of any type.
/// </summary>
/// <remarks>
/// Of note, both <see cref="IsMiss"/> and <see cref="IsHit"/> return <see langword="false"/> for <see cref="HitResult.None"/>.
/// </remarks>
public static bool IsMiss(this HitResult result)
{
switch (result)
{
case HitResult.IgnoreMiss:
case HitResult.Miss:
case HitResult.SmallTickMiss:
case HitResult.LargeTickMiss:
case HitResult.ComboBreak:
return true;
default:
return false;
}
}
/// <summary>
/// Whether a <see cref="HitResult"/> represents a successful hit.
/// </summary>
/// <remarks>
/// Of note, both <see cref="IsMiss"/> and <see cref="IsHit"/> return <see langword="false"/> for <see cref="HitResult.None"/>.
/// </remarks>
public static bool IsHit(this HitResult result)
{
switch (result)
+7 -49
View File
@@ -227,12 +227,12 @@ namespace osu.Game.Rulesets.Scoring
if (result.Judgement.MaxResult.AffectsAccuracy())
{
currentMaximumBaseScore += GetBaseScoreForResult(result.Judgement.MaxResult);
currentMaximumBaseScore += Judgement.ToNumericResult(result.Judgement.MaxResult);
currentAccuracyJudgementCount++;
}
if (result.Type.AffectsAccuracy())
currentBaseScore += GetBaseScoreForResult(result.Type);
currentBaseScore += Judgement.ToNumericResult(result.Type);
if (result.Type.IsBonus())
currentBonusPortion += GetBonusScoreChange(result);
@@ -276,12 +276,12 @@ namespace osu.Game.Rulesets.Scoring
if (result.Judgement.MaxResult.AffectsAccuracy())
{
currentMaximumBaseScore -= GetBaseScoreForResult(result.Judgement.MaxResult);
currentMaximumBaseScore -= Judgement.ToNumericResult(result.Judgement.MaxResult);
currentAccuracyJudgementCount--;
}
if (result.Type.AffectsAccuracy())
currentBaseScore -= GetBaseScoreForResult(result.Type);
currentBaseScore -= Judgement.ToNumericResult(result.Type);
if (result.Type.IsBonus())
currentBonusPortion -= GetBonusScoreChange(result);
@@ -297,51 +297,9 @@ namespace osu.Game.Rulesets.Scoring
updateScore();
}
/// <summary>
/// Gets the final score change to be applied to the bonus portion of the score.
/// </summary>
/// <param name="result">The judgement result.</param>
protected virtual double GetBonusScoreChange(JudgementResult result) => GetBaseScoreForResult(result.Type);
protected virtual double GetBonusScoreChange(JudgementResult result) => Judgement.ToNumericResult(result.Type);
/// <summary>
/// Gets the final score change to be applied to the combo portion of the score.
/// </summary>
/// <param name="result">The judgement result.</param>
protected virtual double GetComboScoreChange(JudgementResult result) => GetBaseScoreForResult(result.Judgement.MaxResult) * Math.Pow(result.ComboAfterJudgement, COMBO_EXPONENT);
public virtual int GetBaseScoreForResult(HitResult result)
{
switch (result)
{
default:
return 0;
case HitResult.SmallTickHit:
return 10;
case HitResult.LargeTickHit:
return 30;
case HitResult.Meh:
return 50;
case HitResult.Ok:
return 100;
case HitResult.Good:
return 200;
case HitResult.Great:
case HitResult.Perfect: // Perfect doesn't actually give more score / accuracy directly.
return 300;
case HitResult.SmallBonus:
return 10;
case HitResult.LargeBonus:
return 50;
}
}
protected virtual double GetComboScoreChange(JudgementResult result) => Judgement.ToNumericResult(result.Judgement.MaxResult) * Math.Pow(result.ComboAfterJudgement, COMBO_EXPONENT);
protected virtual void ApplyScoreChange(JudgementResult result)
{
@@ -570,7 +528,7 @@ namespace osu.Game.Rulesets.Scoring
/// </summary>
/// <remarks>
/// Used to compute accuracy.
/// See: <see cref="HitResultExtensions.IsBasic"/> and <see cref="ScoreProcessor.GetBaseScoreForResult"/>.
/// See: <see cref="HitResultExtensions.IsBasic"/> and <see cref="Judgement.ToNumericResult"/>.
/// </remarks>
[Key(0)]
public double BaseScore { get; set; }
@@ -35,16 +35,12 @@ namespace osu.Game.Scoring.Legacy
[JsonProperty("maximum_statistics")]
public Dictionary<HitResult, int> MaximumStatistics { get; set; } = new Dictionary<HitResult, int>();
[JsonProperty("client_version")]
public string ClientVersion = string.Empty;
public static LegacyReplaySoloScoreInfo FromScore(ScoreInfo score) => new LegacyReplaySoloScoreInfo
{
OnlineID = score.OnlineID,
Mods = score.APIMods,
Statistics = score.Statistics.Where(kvp => kvp.Value != 0).ToDictionary(kvp => kvp.Key, kvp => kvp.Value),
MaximumStatistics = score.MaximumStatistics.Where(kvp => kvp.Value != 0).ToDictionary(kvp => kvp.Key, kvp => kvp.Value),
ClientVersion = score.ClientVersion,
};
}
}
@@ -125,7 +125,6 @@ namespace osu.Game.Scoring.Legacy
score.ScoreInfo.Statistics = readScore.Statistics;
score.ScoreInfo.MaximumStatistics = readScore.MaximumStatistics;
score.ScoreInfo.Mods = readScore.Mods.Select(m => m.ToMod(currentRuleset)).ToArray();
score.ScoreInfo.ClientVersion = readScore.ClientVersion;
});
}
}
@@ -33,12 +33,9 @@ namespace osu.Game.Scoring.Legacy
/// <item><description>30000004: Fixed mod multipliers during legacy score conversion. Reconvert all scores.</description></item>
/// <item><description>30000005: Introduce combo exponent in the osu! gamemode. Reconvert all scores.</description></item>
/// <item><description>30000006: Fix edge cases in conversion after combo exponent introduction that lead to NaNs. Reconvert all scores.</description></item>
/// <item><description>30000007: Adjust osu!mania combo and accuracy portions and judgement scoring values. Reconvert all scores.</description></item>
/// <item><description>30000008: Add accuracy conversion. Reconvert all scores.</description></item>
/// <item><description>30000009: Fix edge cases in conversion for scores which have 0.0x mod multiplier on stable. Reconvert all scores.</description></item>
/// </list>
/// </remarks>
public const int LATEST_VERSION = 30000009;
public const int LATEST_VERSION = 30000006;
/// <summary>
/// The first stable-compatible YYYYMMDD format version given to lazer usage of replays.
+3 -3
View File
@@ -17,6 +17,7 @@ using osu.Game.Scoring.Legacy;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
using Realms;
@@ -106,7 +107,7 @@ namespace osu.Game.Scoring
else if (model.IsLegacyScore)
{
model.LegacyTotalScore = model.TotalScore;
StandardisedScoreMigrationTools.UpdateFromLegacy(model, beatmaps());
model.TotalScore = StandardisedScoreMigrationTools.ConvertFromLegacyTotalScore(model, beatmaps());
}
}
@@ -124,14 +125,13 @@ namespace osu.Game.Scoring
var beatmap = score.BeatmapInfo!.Detach();
var ruleset = score.Ruleset.Detach();
var rulesetInstance = ruleset.CreateInstance();
var scoreProcessor = rulesetInstance.CreateScoreProcessor();
Debug.Assert(rulesetInstance != null);
// Populate the maximum statistics.
HitResult maxBasicResult = rulesetInstance.GetHitResults()
.Select(h => h.result)
.Where(h => h.IsBasic()).MaxBy(scoreProcessor.GetBaseScoreForResult);
.Where(h => h.IsBasic()).MaxBy(Judgement.ToNumericResult);
foreach ((HitResult result, int count) in score.Statistics)
{
-6
View File
@@ -46,12 +46,6 @@ namespace osu.Game.Scoring
/// </remarks>
public BeatmapInfo? BeatmapInfo { get; set; }
/// <summary>
/// The version of the client this score was set using.
/// Sourced from <see cref="OsuGameBase.Version"/> at the point of score submission.
/// </summary>
public string ClientVersion { get; set; } = string.Empty;
/// <summary>
/// The <see cref="osu.Game.Beatmaps.BeatmapInfo.Hash"/> at the point in time when the score was set.
/// </summary>
@@ -409,7 +409,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
double lengthOfOneRepeat = repeatHitObject.Duration / (repeatHitObject.RepeatCount + 1);
int proposedCount = Math.Max(0, (int)Math.Round(proposedDuration / lengthOfOneRepeat) - 1);
if (proposedCount == repeatHitObject.RepeatCount || Precision.AlmostEquals(lengthOfOneRepeat, 0))
if (proposedCount == repeatHitObject.RepeatCount || lengthOfOneRepeat == 0)
return;
repeatHitObject.RepeatCount = proposedCount;
@@ -1,107 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Threading;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Overlays;
using osu.Game.Screens.Play.PlayerSettings;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Screens.Play
{
/// <summary>
/// This provides the ability to change the offset while in gameplay.
/// Eventually this should be replaced with all settings from PlayerLoader being accessible from the game.
/// </summary>
internal partial class GameplayOffsetControl : VisibilityContainer
{
protected override bool StartHidden => true;
public override bool PropagateNonPositionalInputSubTree => true;
// Disable interaction for now to avoid any funny business with slider bar dragging.
public override bool PropagatePositionalInputSubTree => false;
private BeatmapOffsetControl offsetControl = null!;
private OsuTextFlowContainer text = null!;
private ScheduledDelegate? hideOp;
public GameplayOffsetControl()
{
AutoSizeAxes = Axes.Y;
Width = SettingsToolboxGroup.CONTAINER_WIDTH;
Masking = true;
CornerRadius = 5;
// Allow BeatmapOffsetControl to handle keyboard input.
AlwaysPresent = true;
Anchor = Anchor.CentreRight;
Origin = Anchor.CentreRight;
X = 100;
}
[BackgroundDependencyLoader]
private void load(OverlayColourProvider? colourProvider)
{
InternalChildren = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0.8f,
Colour = colourProvider?.Background4 ?? Color4.Black,
},
new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding(10),
Spacing = new Vector2(5),
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
offsetControl = new BeatmapOffsetControl(),
text = new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(weight: FontWeight.SemiBold))
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
TextAnchor = Anchor.TopCentre,
}
}
},
};
offsetControl.Current.BindValueChanged(val =>
{
text.Text = BeatmapOffsetControl.GetOffsetExplanatoryText(val.NewValue);
Show();
hideOp?.Cancel();
hideOp = Scheduler.AddDelayed(Hide, 500);
});
}
protected override void PopIn()
{
this.FadeIn(500, Easing.OutQuint)
.MoveToX(0, 500, Easing.OutQuint);
}
protected override void PopOut()
{
this.FadeOut(500, Easing.InQuint)
.MoveToX(100, 500, Easing.InQuint);
}
}
}
+1 -14
View File
@@ -109,9 +109,6 @@ namespace osu.Game.Screens.Play
[Resolved]
private MusicController musicController { get; set; }
[Resolved]
private OsuGameBase game { get; set; }
public GameplayState GameplayState { get; private set; }
private Ruleset ruleset;
@@ -461,12 +458,6 @@ namespace osu.Game.Screens.Play
OnRetry = () => Restart(),
OnQuit = () => PerformExit(true),
},
new GameplayOffsetControl
{
Margin = new MarginPadding(20),
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
}
},
};
@@ -1164,11 +1155,7 @@ namespace osu.Game.Screens.Play
/// <returns>The <see cref="Scoring.Score"/>.</returns>
protected virtual Score CreateScore(IBeatmap beatmap) => new Score
{
ScoreInfo = new ScoreInfo
{
User = api.LocalUser.Value,
ClientVersion = game.Version,
},
ScoreInfo = new ScoreInfo { User = api.LocalUser.Value },
};
/// <summary>
@@ -9,8 +9,6 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
@@ -18,7 +16,6 @@ using osu.Game.Database;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Input.Bindings;
using osu.Game.Localisation;
using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Mods;
@@ -29,7 +26,7 @@ using osuTK;
namespace osu.Game.Screens.Play.PlayerSettings
{
public partial class BeatmapOffsetControl : CompositeDrawable, IKeyBindingHandler<GlobalAction>
public partial class BeatmapOffsetControl : CompositeDrawable
{
public Bindable<ScoreInfo?> ReferenceScore { get; } = new Bindable<ScoreInfo?>();
@@ -51,12 +48,6 @@ namespace osu.Game.Screens.Play.PlayerSettings
[Resolved]
private OsuColour colours { get; set; } = null!;
[Resolved]
private Player? player { get; set; }
[Resolved]
private IGameplayClock? gameplayClock { get; set; }
private double lastPlayAverage;
private double lastPlayBeatmapOffset;
private HitEventTimingDistributionGraph? lastPlayGraph;
@@ -97,6 +88,28 @@ namespace osu.Game.Screens.Play.PlayerSettings
};
}
public partial class OffsetSliderBar : PlayerSliderBar<double>
{
protected override Drawable CreateControl() => new CustomSliderBar();
protected partial class CustomSliderBar : SliderBar
{
public override LocalisableString TooltipText =>
Current.Value == 0
? LocalisableString.Interpolate($@"{base.TooltipText} ms")
: LocalisableString.Interpolate($@"{base.TooltipText} ms {getEarlyLateText(Current.Value)}");
private LocalisableString getEarlyLateText(double value)
{
Debug.Assert(value != 0);
return value > 0
? BeatmapOffsetControlStrings.HitObjectsAppearEarlier
: BeatmapOffsetControlStrings.HitObjectsAppearLater;
}
}
}
protected override void LoadComplete()
{
base.LoadComplete();
@@ -230,68 +243,5 @@ namespace osu.Game.Screens.Play.PlayerSettings
base.Dispose(isDisposing);
beatmapOffsetSubscription?.Dispose();
}
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
{
// General limitations to ensure players don't do anything too weird.
// These match stable for now.
if (player is SubmittingPlayer)
{
// TODO: the blocking conditions should probably display a message.
if (player?.IsBreakTime.Value == false && gameplayClock?.CurrentTime - gameplayClock?.StartTime > 10000)
return false;
if (gameplayClock?.IsPaused.Value == true)
return false;
}
// To match stable, this should adjust by 5 ms, or 1 ms when holding alt.
// But that is hard to make work with global actions due to the operating mode.
// Let's use the more precise as a default for now.
const double amount = 1;
switch (e.Action)
{
case GlobalAction.IncreaseOffset:
Current.Value += amount;
return true;
case GlobalAction.DecreaseOffset:
Current.Value -= amount;
return true;
}
return false;
}
public void OnReleased(KeyBindingReleaseEvent<GlobalAction> e)
{
}
public static LocalisableString GetOffsetExplanatoryText(double offset)
{
return offset == 0
? LocalisableString.Interpolate($@"{offset:0.0} ms")
: LocalisableString.Interpolate($@"{offset:0.0} ms {getEarlyLateText(offset)}");
LocalisableString getEarlyLateText(double value)
{
Debug.Assert(value != 0);
return value > 0
? BeatmapOffsetControlStrings.HitObjectsAppearEarlier
: BeatmapOffsetControlStrings.HitObjectsAppearLater;
}
}
public partial class OffsetSliderBar : PlayerSliderBar<double>
{
protected override Drawable CreateControl() => new CustomSliderBar();
protected partial class CustomSliderBar : SliderBar
{
public override LocalisableString TooltipText => GetOffsetExplanatoryText(Current.Value);
}
}
}
}
@@ -102,9 +102,6 @@ namespace osu.Game.Screens.Play
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
{
if (e.Repeat)
return false;
switch (e.Action)
{
case GlobalAction.SaveReplay:
+77 -107
View File
@@ -301,9 +301,6 @@ namespace osu.Game.Screens.Select
if (loadedTestBeatmaps)
return;
var setsRequiringUpdate = new HashSet<BeatmapSetInfo>();
var setsRequiringRemoval = new HashSet<Guid>();
if (changes == null)
{
// During initial population, we must manually account for the fact that our original query was done on an async thread.
@@ -317,80 +314,67 @@ namespace osu.Game.Screens.Select
foreach (var id in realmSets)
{
if (!root.BeatmapSetsByID.ContainsKey(id))
setsRequiringUpdate.Add(realm.Realm.Find<BeatmapSetInfo>(id)!.Detach());
updateBeatmapSet(realm.Realm.Find<BeatmapSetInfo>(id)!.Detach());
}
foreach (var id in root.BeatmapSetsByID.Keys)
{
if (!realmSets.Contains(id))
setsRequiringRemoval.Add(id);
removeBeatmapSet(id);
}
}
else
{
foreach (int i in changes.NewModifiedIndices)
setsRequiringUpdate.Add(sender[i].Detach());
foreach (int i in changes.InsertedIndices)
setsRequiringUpdate.Add(sender[i].Detach());
invalidateAfterChange();
BeatmapSetsLoaded = true;
return;
}
// All local operations must be scheduled.
//
// If we don't schedule, beatmaps getting changed while song select is suspended (ie. last played being updated)
// will cause unexpected sounds and operations to occur in the background.
Schedule(() =>
foreach (int i in changes.NewModifiedIndices)
updateBeatmapSet(sender[i].Detach());
foreach (int i in changes.InsertedIndices)
updateBeatmapSet(sender[i].Detach());
if (changes.DeletedIndices.Length > 0 && SelectedBeatmapInfo != null)
{
try
// If SelectedBeatmapInfo is non-null, the set should also be non-null.
Debug.Assert(SelectedBeatmapSet != null);
// To handle the beatmap update flow, attempt to track selection changes across delete-insert transactions.
// When an update occurs, the previous beatmap set is either soft or hard deleted.
// Check if the current selection was potentially deleted by re-querying its validity.
bool selectedSetMarkedDeleted = sender.Realm.Find<BeatmapSetInfo>(SelectedBeatmapSet.ID)?.DeletePending != false;
int[] modifiedAndInserted = changes.NewModifiedIndices.Concat(changes.InsertedIndices).ToArray();
if (selectedSetMarkedDeleted && modifiedAndInserted.Any())
{
foreach (var set in setsRequiringRemoval)
removeBeatmapSet(set);
foreach (var set in setsRequiringUpdate)
updateBeatmapSet(set);
if (changes?.DeletedIndices.Length > 0 && SelectedBeatmapInfo != null)
// If it is no longer valid, make the bold assumption that an updated version will be available in the modified/inserted indices.
// This relies on the full update operation being in a single transaction, so please don't change that.
foreach (int i in modifiedAndInserted)
{
// If SelectedBeatmapInfo is non-null, the set should also be non-null.
Debug.Assert(SelectedBeatmapSet != null);
var beatmapSetInfo = sender[i];
// To handle the beatmap update flow, attempt to track selection changes across delete-insert transactions.
// When an update occurs, the previous beatmap set is either soft or hard deleted.
// Check if the current selection was potentially deleted by re-querying its validity.
bool selectedSetMarkedDeleted = realm.Run(r => r.Find<BeatmapSetInfo>(SelectedBeatmapSet.ID)?.DeletePending != false);
if (selectedSetMarkedDeleted && setsRequiringUpdate.Any())
foreach (var beatmapInfo in beatmapSetInfo.Beatmaps)
{
// If it is no longer valid, make the bold assumption that an updated version will be available in the modified/inserted indices.
// This relies on the full update operation being in a single transaction, so please don't change that.
foreach (var set in setsRequiringUpdate)
if (!((IBeatmapMetadataInfo)beatmapInfo.Metadata).Equals(SelectedBeatmapInfo.Metadata))
continue;
// Best effort matching. We can't use ID because in the update flow a new version will get its own GUID.
if (beatmapInfo.DifficultyName == SelectedBeatmapInfo.DifficultyName)
{
foreach (var beatmapInfo in set.Beatmaps)
{
if (!((IBeatmapMetadataInfo)beatmapInfo.Metadata).Equals(SelectedBeatmapInfo.Metadata))
continue;
// Best effort matching. We can't use ID because in the update flow a new version will get its own GUID.
if (beatmapInfo.DifficultyName == SelectedBeatmapInfo.DifficultyName)
{
SelectBeatmap(beatmapInfo);
return;
}
}
SelectBeatmap(beatmapInfo);
return;
}
// If a direct selection couldn't be made, it's feasible that the difficulty name (or beatmap metadata) changed.
// Let's attempt to follow set-level selection anyway.
SelectBeatmap(setsRequiringUpdate.First().Beatmaps.First());
}
}
// If a direct selection couldn't be made, it's feasible that the difficulty name (or beatmap metadata) changed.
// Let's attempt to follow set-level selection anyway.
SelectBeatmap(sender[modifiedAndInserted.First()].Beatmaps.First());
}
finally
{
BeatmapSetsLoaded = true;
invalidateAfterChange();
}
});
}
invalidateAfterChange();
}
private void beatmapsChanged(IRealmCollection<BeatmapInfo> sender, ChangeSet? changes)
@@ -455,13 +439,30 @@ namespace osu.Game.Screens.Select
private void updateBeatmapSet(BeatmapSetInfo beatmapSet)
{
Guid? previouslySelectedID = null;
originalBeatmapSetsDetached.RemoveAll(set => set.ID == beatmapSet.ID);
originalBeatmapSetsDetached.Add(beatmapSet.Detach());
var newSets = new List<CarouselBeatmapSet>();
// If the selected beatmap is about to be removed, store its ID so it can be re-selected if required
if (selectedBeatmapSet?.BeatmapSet.ID == beatmapSet.ID)
previouslySelectedID = selectedBeatmap?.BeatmapInfo.ID;
var removedSets = root.RemoveItemsByID(beatmapSet.ID);
foreach (var removedSet in removedSets)
{
// If we don't remove this here, it may remain in a hidden state until scrolled off screen.
// Doesn't really affect anything during actual user interaction, but makes testing annoying.
var removedDrawable = Scroll.FirstOrDefault(c => c.Item == removedSet);
if (removedDrawable != null)
expirePanelImmediately(removedDrawable);
}
if (beatmapsSplitOut)
{
var newSets = new List<CarouselBeatmapSet>();
foreach (var beatmap in beatmapSet.Beatmaps)
{
var newSet = createCarouselSet(new BeatmapSetInfo(new[] { beatmap })
@@ -472,7 +473,18 @@ namespace osu.Game.Screens.Select
});
if (newSet != null)
{
newSets.Add(newSet);
root.AddItem(newSet);
}
}
// check if we can/need to maintain our current selection.
if (previouslySelectedID != null)
{
var toSelect = newSets.FirstOrDefault(s => s.Beatmaps.Any(b => b.BeatmapInfo.ID == previouslySelectedID))
?? newSets.FirstOrDefault();
select(toSelect);
}
}
else
@@ -480,18 +492,13 @@ namespace osu.Game.Screens.Select
var newSet = createCarouselSet(beatmapSet);
if (newSet != null)
newSets.Add(newSet);
}
{
root.AddItem(newSet);
var removedSets = root.ReplaceItem(beatmapSet, newSets);
// If we don't remove these here, it may remain in a hidden state until scrolled off screen.
// Doesn't really affect anything during actual user interaction, but makes testing annoying.
foreach (var removedSet in removedSets)
{
var removedDrawable = Scroll.FirstOrDefault(c => c.Item == removedSet);
if (removedDrawable != null)
expirePanelImmediately(removedDrawable);
// check if we can/need to maintain our current selection.
if (previouslySelectedID != null)
select((CarouselItem?)newSet.Beatmaps.FirstOrDefault(b => b.BeatmapInfo.ID == previouslySelectedID) ?? newSet);
}
}
}
@@ -1184,43 +1191,6 @@ namespace osu.Game.Screens.Select
base.AddItem(i);
}
/// <summary>
/// A special method to handle replace operations (general for updating a beatmap).
/// Avoids event-driven selection flip-flopping during the remove/add process.
/// </summary>
/// <param name="oldItem">The beatmap set to be replaced.</param>
/// <param name="newItems">All new items to replace the removed beatmap set.</param>
/// <returns>All removed items, for any further processing.</returns>
public IEnumerable<CarouselBeatmapSet> ReplaceItem(BeatmapSetInfo oldItem, List<CarouselBeatmapSet> newItems)
{
var previousSelection = (LastSelected as CarouselBeatmapSet)?.Beatmaps
.FirstOrDefault(s => s.State.Value == CarouselItemState.Selected)
?.BeatmapInfo;
bool wasSelected = previousSelection?.BeatmapSet?.ID == oldItem.ID;
// Without doing this, the removal of the old beatmap will cause carousel's eager selection
// logic to invoke, causing one unnecessary selection.
DisableSelection = true;
var removedSets = RemoveItemsByID(oldItem.ID);
DisableSelection = false;
foreach (var set in newItems)
AddItem(set);
// Check if we can/need to maintain our current selection.
if (wasSelected)
{
CarouselBeatmap? matchingBeatmap = newItems.SelectMany(s => s.Beatmaps)
.FirstOrDefault(b => b.BeatmapInfo.ID == previousSelection?.ID);
if (matchingBeatmap != null)
matchingBeatmap.State.Value = CarouselItemState.Selected;
}
return removedSets;
}
public IEnumerable<CarouselBeatmapSet> RemoveItemsByID(Guid beatmapSetID)
{
if (BeatmapSetsByID.TryGetValue(beatmapSetID, out var carouselBeatmapSets))
+17 -6
View File
@@ -3,6 +3,7 @@
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
@@ -18,6 +19,7 @@ using osu.Game.Overlays.BeatmapSet;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Screens.Select.Details;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Screens.Select
{
@@ -26,6 +28,7 @@ namespace osu.Game.Screens.Select
private const float spacing = 10;
private const float transition_duration = 250;
private readonly AdvancedStats advanced;
private readonly UserRatings ratingsDisplay;
private readonly MetadataSection description, source, tags;
private readonly Container failRetryContainer;
@@ -65,15 +68,12 @@ namespace osu.Game.Screens.Select
public BeatmapDetails()
{
CornerRadius = 10;
Masking = true;
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Colour4.Black.Opacity(0.3f),
Colour = Color4.Black.Opacity(0.5f),
},
new Container
{
@@ -109,6 +109,12 @@ namespace osu.Game.Screens.Select
Padding = new MarginPadding { Right = spacing / 2 },
Children = new[]
{
new DetailBox().WithChild(advanced = new AdvancedStats
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding { Horizontal = spacing, Top = spacing * 2, Bottom = spacing },
}),
new DetailBox().WithChild(new OnlineViewContainer(string.Empty)
{
RelativeSizeAxes = Axes.X,
@@ -123,8 +129,7 @@ namespace osu.Game.Screens.Select
},
new OsuScrollContainer
{
RelativeSizeAxes = Axes.X,
Height = 250,
RelativeSizeAxes = Axes.Both,
Width = 0.5f,
ScrollbarVisible = false,
Padding = new MarginPadding { Left = spacing / 2 },
@@ -175,6 +180,7 @@ namespace osu.Game.Screens.Select
private void updateStatistics()
{
advanced.BeatmapInfo = BeatmapInfo;
description.Metadata = BeatmapInfo?.DifficultyName ?? string.Empty;
source.Metadata = BeatmapInfo?.Metadata.Source ?? string.Empty;
tags.Metadata = BeatmapInfo?.Metadata.Tags ?? string.Empty;
@@ -273,6 +279,11 @@ namespace osu.Game.Screens.Select
InternalChildren = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black.Opacity(0.5f),
},
content = new Container
{
RelativeSizeAxes = Axes.X,
+10 -19
View File
@@ -61,7 +61,7 @@ namespace osu.Game.Screens.Select
{
Type = EdgeEffectType.Glow,
Colour = new Color4(130, 204, 255, 150),
Radius = 15,
Radius = 20,
Roundness = 15,
};
}
@@ -161,7 +161,6 @@ namespace osu.Game.Screens.Select
private ILocalisedBindableString artistBinding;
private FillFlowContainer infoLabelContainer;
private Container bpmLabelContainer;
private Container lengthLabelContainer;
private readonly WorkingBeatmap working;
private readonly RulesetInfo ruleset;
@@ -306,7 +305,7 @@ namespace osu.Game.Screens.Select
},
infoLabelContainer = new FillFlowContainer
{
Margin = new MarginPadding { Top = 8 },
Margin = new MarginPadding { Top = 20 },
Spacing = new Vector2(20, 0),
AutoSizeAxes = Axes.Both,
}
@@ -342,10 +341,10 @@ namespace osu.Game.Screens.Select
{
settingChangeTracker?.Dispose();
refreshBPMAndLengthLabel();
refreshBPMLabel();
settingChangeTracker = new ModSettingChangeTracker(m.NewValue);
settingChangeTracker.SettingChanged += _ => refreshBPMAndLengthLabel();
settingChangeTracker.SettingChanged += _ => refreshBPMLabel();
}, true);
}
@@ -371,10 +370,12 @@ namespace osu.Game.Screens.Select
infoLabelContainer.Children = new Drawable[]
{
lengthLabelContainer = new Container
new InfoLabel(new BeatmapStatistic
{
AutoSizeAxes = Axes.Both,
},
Name = BeatmapsetsStrings.ShowStatsTotalLength(playableBeatmap.CalculateDrainLength().ToFormattedDuration()),
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Length),
Content = working.BeatmapInfo.Length.ToFormattedDuration().ToString(),
}),
bpmLabelContainer = new Container
{
AutoSizeAxes = Axes.Both,
@@ -393,7 +394,7 @@ namespace osu.Game.Screens.Select
}
}
private void refreshBPMAndLengthLabel()
private void refreshBPMLabel()
{
var beatmap = working.Beatmap;
@@ -419,16 +420,6 @@ namespace osu.Game.Screens.Select
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Bpm),
Content = labelText
});
double drainLength = Math.Round(beatmap.CalculateDrainLength() / rate);
double hitLength = Math.Round(beatmap.BeatmapInfo.Length / rate);
lengthLabelContainer.Child = new InfoLabel(new BeatmapStatistic
{
Name = BeatmapsetsStrings.ShowStatsTotalLength(drainLength.ToFormattedDuration()),
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Length),
Content = hitLength.ToFormattedDuration().ToString(),
});
}
private Drawable getMapper(BeatmapMetadata metadata)
@@ -45,7 +45,7 @@ namespace osu.Game.Screens.Select.Carousel
.ForEach(AddItem);
}
public override CarouselItem? GetNextToSelect()
protected override CarouselItem? GetNextToSelect()
{
if (LastSelected == null || LastSelected.Filtered.Value)
{
@@ -36,13 +36,13 @@ namespace osu.Game.Screens.Select.Carousel
/// items have been filtered. This bool will be true during the base <see cref="Filter(FilterCriteria)"/>
/// operation.
/// </summary>
protected bool DisableSelection;
private bool filteringItems;
public override void Filter(FilterCriteria criteria)
{
DisableSelection = true;
filteringItems = true;
base.Filter(criteria);
DisableSelection = false;
filteringItems = false;
attemptSelection();
}
@@ -95,7 +95,7 @@ namespace osu.Game.Screens.Select.Carousel
private void attemptSelection()
{
if (DisableSelection) return;
if (filteringItems) return;
// we only perform eager selection if we are a currently selected group.
if (State.Value != CarouselItemState.Selected) return;
@@ -110,7 +110,7 @@ namespace osu.Game.Screens.Select.Carousel
/// Finds the item this group would select next if it attempted selection
/// </summary>
/// <returns>An unfiltered item nearest to the last selected one or null if all items are filtered</returns>
public virtual CarouselItem? GetNextToSelect()
protected virtual CarouselItem? GetNextToSelect()
{
if (Items.Count == 0)
return null;
@@ -91,7 +91,7 @@ namespace osu.Game.Screens.Select.Carousel
if (songSelect != null)
{
mainMenuItems = songSelect.CreateForwardNavigationMenuItemsForBeatmap(() => beatmapInfo);
mainMenuItems = songSelect.CreateForwardNavigationMenuItemsForBeatmap(beatmapInfo);
selectRequested = b => songSelect.FinaliseSelection(b);
}
@@ -44,8 +44,6 @@ namespace osu.Game.Screens.Select.Carousel
private Task? beatmapsLoadTask;
private MenuItem[]? mainMenuItems;
[Resolved]
private BeatmapManager manager { get; set; } = null!;
@@ -59,11 +57,8 @@ namespace osu.Game.Screens.Select.Carousel
}
[BackgroundDependencyLoader]
private void load(BeatmapSetOverlay? beatmapOverlay, SongSelect? songSelect)
private void load(BeatmapSetOverlay? beatmapOverlay)
{
if (songSelect != null)
mainMenuItems = songSelect.CreateForwardNavigationMenuItemsForBeatmap(() => (((CarouselBeatmapSet)Item!).GetNextToSelect() as CarouselBeatmap)!.BeatmapInfo);
restoreHiddenRequested = s =>
{
foreach (var b in s.Beatmaps)
@@ -227,9 +222,6 @@ namespace osu.Game.Screens.Select.Carousel
if (Item?.State.Value == CarouselItemState.NotSelected)
items.Add(new OsuMenuItem("Expand", MenuItemType.Highlighted, () => Item.State.Value = CarouselItemState.Selected));
if (mainMenuItems != null)
items.AddRange(mainMenuItems);
if (beatmapSet.OnlineID > 0 && viewDetails != null)
items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => viewDetails(beatmapSet.OnlineID)));

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