diff --git a/osu.Game.Rulesets.Mania/Judgements/HoldNoteTailJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/HoldNoteTailJudgement.cs
index df2f7e9e63..d5cf57a5da 100644
--- a/osu.Game.Rulesets.Mania/Judgements/HoldNoteTailJudgement.cs
+++ b/osu.Game.Rulesets.Mania/Judgements/HoldNoteTailJudgement.cs
@@ -9,5 +9,29 @@ namespace osu.Game.Rulesets.Mania.Judgements
/// Whether the hold note has been released too early and shouldn't give full score for the release.
///
public bool HasBroken;
+
+ public override int NumericResultForScore(ManiaHitResult result)
+ {
+ switch (result)
+ {
+ default:
+ return base.NumericResultForScore(result);
+ case ManiaHitResult.Great:
+ case ManiaHitResult.Perfect:
+ return base.NumericResultForScore(HasBroken ? ManiaHitResult.Good : result);
+ }
+ }
+
+ public override int NumericResultForAccuracy(ManiaHitResult result)
+ {
+ switch (result)
+ {
+ default:
+ return base.NumericResultForAccuracy(result);
+ case ManiaHitResult.Great:
+ case ManiaHitResult.Perfect:
+ return base.NumericResultForAccuracy(HasBroken ? ManiaHitResult.Good : result);
+ }
+ }
}
}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs
index bead455c13..852f97b3f2 100644
--- a/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs
+++ b/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs
@@ -5,5 +5,9 @@ namespace osu.Game.Rulesets.Mania.Judgements
{
public class HoldNoteTickJudgement : ManiaJudgement
{
+ public override bool AffectsCombo => false;
+
+ public override int NumericResultForScore(ManiaHitResult result) => 20;
+ public override int NumericResultForAccuracy(ManiaHitResult result) => 0; // Don't count ticks into accuracy
}
}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs
index 6e69da3da7..33083ca0f5 100644
--- a/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs
+++ b/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs
@@ -2,11 +2,37 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Mania.Judgements
{
public class ManiaJudgement : Judgement
{
+ ///
+ /// The maximum possible hit result.
+ ///
+ public const ManiaHitResult MAX_HIT_RESULT = ManiaHitResult.Perfect;
+
+ ///
+ /// The result value for the combo portion of the score.
+ ///
+ public int ResultValueForScore => Result == HitResult.Miss ? 0 : NumericResultForScore(ManiaResult);
+
+ ///
+ /// The result value for the accuracy portion of the score.
+ ///
+ public int ResultValueForAccuracy => Result == HitResult.Miss ? 0 : NumericResultForAccuracy(ManiaResult);
+
+ ///
+ /// The maximum result value for the combo portion of the score.
+ ///
+ public int MaxResultValueForScore => NumericResultForScore(MAX_HIT_RESULT);
+
+ ///
+ /// The maximum result value for the accuracy portion of the score.
+ ///
+ public int MaxResultValueForAccuracy => NumericResultForAccuracy(MAX_HIT_RESULT);
+
public override string ResultString => string.Empty;
public override string MaxResultString => string.Empty;
@@ -15,5 +41,42 @@ namespace osu.Game.Rulesets.Mania.Judgements
/// The hit result.
///
public ManiaHitResult ManiaResult;
+
+ public virtual int NumericResultForScore(ManiaHitResult result)
+ {
+ switch (result)
+ {
+ default:
+ return 0;
+ case ManiaHitResult.Bad:
+ return 50;
+ case ManiaHitResult.Ok:
+ return 100;
+ case ManiaHitResult.Good:
+ return 200;
+ case ManiaHitResult.Great:
+ case ManiaHitResult.Perfect:
+ return 300;
+ }
+ }
+
+ public virtual int NumericResultForAccuracy(ManiaHitResult result)
+ {
+ switch (result)
+ {
+ default:
+ return 0;
+ case ManiaHitResult.Bad:
+ return 50;
+ case ManiaHitResult.Ok:
+ return 100;
+ case ManiaHitResult.Good:
+ return 200;
+ case ManiaHitResult.Great:
+ return 300;
+ case ManiaHitResult.Perfect:
+ return 305;
+ }
+ }
}
}
diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
index 8cd757734c..fa32d46a88 100644
--- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
@@ -1,4 +1,4 @@
-// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Copyright (c) 2007-2017 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs
index 7a9572a0c7..798d4b8c5b 100644
--- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs
+++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs
@@ -1,8 +1,13 @@
// Copyright (c) 2007-2017 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using System;
+using System.Linq;
+using osu.Game.Beatmaps;
+using osu.Game.Database;
using osu.Game.Rulesets.Mania.Judgements;
using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
@@ -10,6 +15,143 @@ namespace osu.Game.Rulesets.Mania.Scoring
{
internal class ManiaScoreProcessor : ScoreProcessor
{
+ ///
+ /// The maximum score achievable.
+ /// Does _not_ include bonus score - for bonus score see .
+ ///
+ private const int max_score = 1000000;
+
+ ///
+ /// The amount of the score attributed to combo.
+ ///
+ private const double combo_portion_max = max_score * 0.2;
+
+ ///
+ /// The amount of the score attributed to accuracy.
+ ///
+ private const double accuracy_portion_max = max_score * 0.8;
+
+ ///
+ /// The factor used to determine relevance of combos.
+ ///
+ private const double combo_base = 4;
+
+ ///
+ /// The combo value at which hit objects result in the max score possible.
+ ///
+ private const int combo_relevance_cap = 400;
+
+ ///
+ /// The hit HP multiplier at OD = 0.
+ ///
+ private const double hp_multiplier_min = 0.75;
+
+ ///
+ /// The hit HP multiplier at OD = 0.
+ ///
+ private const double hp_multiplier_mid = 0.85;
+
+ ///
+ /// The hit HP multiplier at OD = 0.
+ ///
+ private const double hp_multiplier_max = 1;
+
+ ///
+ /// The default BAD hit HP increase.
+ ///
+ private const double hp_increase_bad = 0.005;
+
+ ///
+ /// The default OK hit HP increase.
+ ///
+ private const double hp_increase_ok = 0.010;
+
+ ///
+ /// The default GOOD hit HP increase.
+ ///
+ private const double hp_increase_good = 0.035;
+
+ ///
+ /// The default tick hit HP increase.
+ ///
+ private const double hp_increase_tick = 0.040;
+
+ ///
+ /// The default GREAT hit HP increase.
+ ///
+ private const double hp_increase_great = 0.055;
+
+ ///
+ /// The default PERFECT hit HP increase.
+ ///
+ private const double hp_increase_perfect = 0.065;
+
+ ///
+ /// The MISS HP multiplier at OD = 0.
+ ///
+ private const double hp_multiplier_miss_min = 0.5;
+
+ ///
+ /// The MISS HP multiplier at OD = 5.
+ ///
+ private const double hp_multiplier_miss_mid = 0.75;
+
+ ///
+ /// The MISS HP multiplier at OD = 10.
+ ///
+ private const double hp_multiplier_miss_max = 1;
+
+ ///
+ /// The default MISS HP increase.
+ ///
+ private const double hp_increase_miss = -0.125;
+
+ ///
+ /// The MISS HP multiplier. This is multiplied to the miss hp increase.
+ ///
+ private double hpMissMultiplier = 1;
+
+ ///
+ /// The HIT HP multiplier. This is multiplied to hit hp increases.
+ ///
+ private double hpMultiplier = 1;
+
+ ///
+ /// The cumulative combo portion of the score.
+ ///
+ private double comboScore => combo_portion_max * comboPortion / maxComboPortion;
+
+ ///
+ /// The cumulative accuracy portion of the score.
+ ///
+ private double accuracyScore => accuracy_portion_max * Math.Pow(Accuracy, 4) * totalHits / maxTotalHits;
+
+ ///
+ /// The cumulative bonus score.
+ /// This is added on top of , thus the total score can exceed .
+ ///
+ private double bonusScore;
+
+ ///
+ /// The achieved by a perfect playthrough.
+ ///
+ private double maxComboPortion;
+
+ ///
+ /// The portion of the score dedicated to combo.
+ ///
+ private double comboPortion;
+
+ ///
+ /// The achieved by a perfect playthrough.
+ ///
+ private int maxTotalHits;
+
+ ///
+ /// The total hits.
+ ///
+ private int totalHits;
+
public ManiaScoreProcessor()
{
}
@@ -19,8 +161,124 @@ namespace osu.Game.Rulesets.Mania.Scoring
{
}
+ protected override void ComputeTargets(Beatmap beatmap)
+ {
+ BeatmapDifficulty difficulty = beatmap.BeatmapInfo.Difficulty;
+ hpMultiplier = BeatmapDifficulty.DifficultyRange(difficulty.DrainRate, hp_multiplier_min, hp_multiplier_mid, hp_multiplier_max);
+ hpMissMultiplier = BeatmapDifficulty.DifficultyRange(difficulty.DrainRate, hp_multiplier_miss_min, hp_multiplier_miss_mid, hp_multiplier_miss_max);
+
+ while (true)
+ {
+ foreach (var obj in beatmap.HitObjects)
+ {
+ var holdNote = obj as HoldNote;
+
+ if (obj is Note)
+ {
+ AddJudgement(new ManiaJudgement
+ {
+ Result = HitResult.Hit,
+ ManiaResult = ManiaHitResult.Perfect
+ });
+ }
+ else if (holdNote != null)
+ {
+ // Head
+ AddJudgement(new ManiaJudgement
+ {
+ Result = HitResult.Hit,
+ ManiaResult = ManiaJudgement.MAX_HIT_RESULT
+ });
+
+ // Ticks
+ int tickCount = holdNote.Ticks.Count();
+ for (int i = 0; i < tickCount; i++)
+ {
+ AddJudgement(new HoldNoteTickJudgement
+ {
+ Result = HitResult.Hit,
+ ManiaResult = ManiaJudgement.MAX_HIT_RESULT,
+ });
+ }
+
+ AddJudgement(new HoldNoteTailJudgement
+ {
+ Result = HitResult.Hit,
+ ManiaResult = ManiaJudgement.MAX_HIT_RESULT
+ });
+ }
+ }
+
+ if (!HasFailed)
+ break;
+
+ hpMultiplier *= 1.01;
+ hpMissMultiplier *= 0.98;
+
+ Reset();
+ }
+
+ maxTotalHits = totalHits;
+ maxComboPortion = comboPortion;
+ }
+
protected override void OnNewJudgement(ManiaJudgement judgement)
{
+ bool isTick = judgement is HoldNoteTickJudgement;
+
+ if (!isTick)
+ totalHits++;
+
+ switch (judgement.Result)
+ {
+ case HitResult.Miss:
+ Health.Value += hpMissMultiplier * hp_increase_miss;
+ break;
+ case HitResult.Hit:
+ if (isTick)
+ {
+ Health.Value += hpMultiplier * hp_increase_tick;
+ bonusScore += judgement.ResultValueForScore;
+ }
+ else
+ {
+ switch (judgement.ManiaResult)
+ {
+ case ManiaHitResult.Bad:
+ Health.Value += hpMultiplier * hp_increase_bad;
+ break;
+ case ManiaHitResult.Ok:
+ Health.Value += hpMultiplier * hp_increase_ok;
+ break;
+ case ManiaHitResult.Good:
+ Health.Value += hpMultiplier * hp_increase_good;
+ break;
+ case ManiaHitResult.Great:
+ Health.Value += hpMultiplier * hp_increase_great;
+ break;
+ case ManiaHitResult.Perfect:
+ Health.Value += hpMultiplier * hp_increase_perfect;
+ break;
+ }
+
+ // A factor that is applied to make higher combos more relevant
+ double comboRelevance = Math.Min(Math.Max(0.5, Math.Log(Combo.Value, combo_base)), Math.Log(combo_relevance_cap, combo_base));
+ comboPortion += judgement.ResultValueForScore * comboRelevance;
+ }
+ break;
+ }
+
+ int scoreForAccuracy = 0;
+ int maxScoreForAccuracy = 0;
+
+ foreach (var j in Judgements)
+ {
+ scoreForAccuracy += j.ResultValueForAccuracy;
+ maxScoreForAccuracy += j.MaxResultValueForAccuracy;
+ }
+
+ Accuracy.Value = (double)scoreForAccuracy / maxScoreForAccuracy;
+ TotalScore.Value = comboScore + accuracyScore + bonusScore;
}
protected override void Reset()
@@ -28,6 +286,10 @@ namespace osu.Game.Rulesets.Mania.Scoring
base.Reset();
Health.Value = 1;
+
+ bonusScore = 0;
+ comboPortion = 0;
+ totalHits = 0;
}
}
}