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; } } }