Skip to content

Commit

Permalink
Merge pull request ppy#18402 from ggliv/mod-accuracy-challenge
Browse files Browse the repository at this point in the history
Add accuracy challenge mod
  • Loading branch information
peppy authored Jan 24, 2023
2 parents 3cd810f + b46ef67 commit a966d6c
Show file tree
Hide file tree
Showing 9 changed files with 114 additions and 9 deletions.
1 change: 1 addition & 0 deletions osu.Game.Rulesets.Catch/CatchRuleset.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ public override IEnumerable<Mod> GetModsFor(ModType type)
new MultiMod(new CatchModDoubleTime(), new CatchModNightcore()),
new CatchModHidden(),
new CatchModFlashlight(),
new ModAccuracyChallenge(),
};

case ModType.Conversion:
Expand Down
1 change: 1 addition & 0 deletions osu.Game.Rulesets.Mania/ManiaRuleset.cs
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ public override IEnumerable<Mod> GetModsFor(ModType type)
new MultiMod(new ManiaModDoubleTime(), new ManiaModNightcore()),
new MultiMod(new ManiaModFadeIn(), new ManiaModHidden()),
new ManiaModFlashlight(),
new ModAccuracyChallenge(),
};

case ModType.Conversion:
Expand Down
14 changes: 14 additions & 0 deletions osu.Game.Rulesets.Osu/Mods/OsuModAccuracyChallenge.cs
Original file line number Diff line number Diff line change
@@ -0,0 +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();
}
}
3 changes: 2 additions & 1 deletion osu.Game.Rulesets.Osu/OsuRuleset.cs
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,8 @@ public override IEnumerable<Mod> GetModsFor(ModType type)
new MultiMod(new OsuModDoubleTime(), new OsuModNightcore()),
new OsuModHidden(),
new MultiMod(new OsuModFlashlight(), new OsuModBlinds()),
new OsuModStrictTracking()
new OsuModStrictTracking(),
new OsuModAccuracyChallenge(),
};

case ModType.Conversion:
Expand Down
1 change: 1 addition & 0 deletions osu.Game.Rulesets.Taiko/TaikoRuleset.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ public override IEnumerable<Mod> GetModsFor(ModType type)
new MultiMod(new TaikoModDoubleTime(), new TaikoModNightcore()),
new TaikoModHidden(),
new TaikoModFlashlight(),
new ModAccuracyChallenge(),
};

case ModType.Conversion:
Expand Down
80 changes: 80 additions & 0 deletions osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// 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.Globalization;
using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Judgements;
using osu.Game.Scoring;

namespace osu.Game.Rulesets.Mods
{
public class ModAccuracyChallenge : ModFailCondition, IApplicableToScoreProcessor
{
public override string Name => "Accuracy Challenge";

public override string Acronym => "AC";

public override LocalisableString Description => "Fail if your accuracy drops too low!";

public override ModType Type => ModType.DifficultyIncrease;

public override double ScoreMultiplier => 1.0;

public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(ModEasyWithExtraLives), typeof(ModPerfect) }).ToArray();

public override bool RequiresConfiguration => false;

public override string SettingDescription => base.SettingDescription.Replace(MinimumAccuracy.ToString(), MinimumAccuracy.Value.ToString("##%", NumberFormatInfo.InvariantInfo));

[SettingSource("Minimum accuracy", "Trigger a failure if your accuracy goes below this value.", SettingControlType = typeof(SettingsSlider<double, PercentSlider>))]
public BindableNumber<double> MinimumAccuracy { get; } = new BindableDouble
{
MinValue = 0.60,
MaxValue = 0.99,
Precision = 0.01,
Default = 0.9,
Value = 0.9,
};

private ScoreProcessor scoreProcessor = null!;

public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) => this.scoreProcessor = scoreProcessor;

public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank;

protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result)
{
if (!result.Type.AffectsAccuracy())
return false;

return getAccuracyWithImminentResultAdded(result) < MinimumAccuracy.Value;
}

private double getAccuracyWithImminentResultAdded(JudgementResult result)
{
var score = new ScoreInfo { Ruleset = scoreProcessor.Ruleset.RulesetInfo };

// This is super ugly, but if we don't do it this way we will not have the most recent result added to the accuracy value.
// Hopefully we can improve this in the future.
scoreProcessor.PopulateScore(score);
score.Statistics[result.Type]++;

return scoreProcessor.ComputeAccuracy(score);
}
}

public partial class PercentSlider : OsuSliderBar<double>
{
public PercentSlider()
{
DisplayAsPercentage = true;
}
}
}
3 changes: 3 additions & 0 deletions osu.Game/Rulesets/Mods/ModEasyWithExtraLives.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System;
using System.Linq;
using Humanizer;
using osu.Framework.Bindables;
using osu.Game.Beatmaps;
Expand All @@ -19,6 +21,7 @@ public abstract class ModEasyWithExtraLives : ModEasy, IApplicableFailOverride,
};

public override string SettingDescription => Retries.IsDefault ? string.Empty : $"{"lives".ToQuantity(Retries.Value)}";
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModAccuracyChallenge)).ToArray();

private int retries;

Expand Down
2 changes: 1 addition & 1 deletion osu.Game/Rulesets/Mods/ModPerfect.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public abstract class ModPerfect : ModFailCondition
public override double ScoreMultiplier => 1;
public override LocalisableString Description => "SS or quit.";

public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModSuddenDeath)).ToArray();
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(ModSuddenDeath), typeof(ModAccuracyChallenge) }).ToArray();

protected ModPerfect()
{
Expand Down
18 changes: 11 additions & 7 deletions osu.Game/Rulesets/Scoring/ScoreProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,11 @@ public partial class ScoreProcessor : JudgementProcessor
/// </summary>
protected virtual double ClassicScoreMultiplier => 36;

private readonly Ruleset ruleset;
/// <summary>
/// The ruleset this score processor is valid for.
/// </summary>
public readonly Ruleset Ruleset;

private readonly double accuracyPortion;
private readonly double comboPortion;

Expand Down Expand Up @@ -145,7 +149,7 @@ public Dictionary<HitResult, int> MaximumStatistics

public ScoreProcessor(Ruleset ruleset)
{
this.ruleset = ruleset;
Ruleset = ruleset;

accuracyPortion = DefaultAccuracyPortion;
comboPortion = DefaultComboPortion;
Expand Down Expand Up @@ -291,8 +295,8 @@ private void updateScore()
[Pure]
public double ComputeAccuracy(ScoreInfo scoreInfo)
{
if (!ruleset.RulesetInfo.Equals(scoreInfo.Ruleset))
throw new ArgumentException($"Unexpected score ruleset. Expected \"{ruleset.RulesetInfo.ShortName}\" but was \"{scoreInfo.Ruleset.ShortName}\".");
if (!Ruleset.RulesetInfo.Equals(scoreInfo.Ruleset))
throw new ArgumentException($"Unexpected score ruleset. Expected \"{Ruleset.RulesetInfo.ShortName}\" but was \"{scoreInfo.Ruleset.ShortName}\".");

// We only extract scoring values from the score's statistics. This is because accuracy is always relative to the point of pass or fail rather than relative to the whole beatmap.
extractScoringValues(scoreInfo.Statistics, out var current, out var maximum);
Expand All @@ -312,8 +316,8 @@ public double ComputeAccuracy(ScoreInfo scoreInfo)
[Pure]
public long ComputeScore(ScoringMode mode, ScoreInfo scoreInfo)
{
if (!ruleset.RulesetInfo.Equals(scoreInfo.Ruleset))
throw new ArgumentException($"Unexpected score ruleset. Expected \"{ruleset.RulesetInfo.ShortName}\" but was \"{scoreInfo.Ruleset.ShortName}\".");
if (!Ruleset.RulesetInfo.Equals(scoreInfo.Ruleset))
throw new ArgumentException($"Unexpected score ruleset. Expected \"{Ruleset.RulesetInfo.ShortName}\" but was \"{scoreInfo.Ruleset.ShortName}\".");

extractScoringValues(scoreInfo, out var current, out var maximum);

Expand Down Expand Up @@ -552,7 +556,7 @@ private void extractScoringValues(IReadOnlyDictionary<HitResult, int> statistics
break;

default:
maxResult = maxBasicResult ??= ruleset.GetHitResults().MaxBy(kvp => Judgement.ToNumericResult(kvp.result)).result;
maxResult = maxBasicResult ??= Ruleset.GetHitResults().MaxBy(kvp => Judgement.ToNumericResult(kvp.result)).result;
break;
}

Expand Down

0 comments on commit a966d6c

Please sign in to comment.