1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-22 06:47:24 +08:00
osu-lazer/osu.Game.Modes.Taiko/Scoring/TaikoScoreProcessor.cs

264 lines
9.8 KiB
C#
Raw Normal View History

// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
2017-03-23 18:24:23 +08:00
using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Game.Modes.Objects.Drawables;
using osu.Game.Modes.Scoring;
using osu.Game.Modes.Taiko.Judgements;
using osu.Game.Modes.Taiko.Objects;
using osu.Game.Modes.UI;
using OpenTK;
namespace osu.Game.Modes.Taiko.Scoring
{
internal class TaikoScoreProcessor : ScoreProcessor<TaikoHitObject, TaikoJudgement>
{
2017-03-23 18:24:23 +08:00
/// <summary>
/// The maximum score achievable.
/// Does _not_ include bonus score - for bonus score see <see cref="bonusScore"/>.
/// </summary>
private const int max_score = 1000000;
/// <summary>
/// The amount of the score attributed to combo.
/// </summary>
private const double combo_portion_max = max_score * 0.2;
/// <summary>
/// The amount of the score attributed to accuracy.
/// </summary>
private const double accuracy_portion_max = max_score * 0.8;
/// <summary>
/// The factor used to determine relevance of combos.
/// </summary>
private const double combo_base = 4;
/// <summary>
/// The HP awarded by a <see cref="TaikoHitResult.Great"/> hit.
/// </summary>
private const double hp_hit_great = 0.03;
/// <summary>
/// The HP awarded for a <see cref="TaikoHitResult.Good"/> hit.
2017-03-23 18:24:23 +08:00
/// </summary>
private const double hp_hit_good = 0.011;
2017-03-23 18:24:23 +08:00
/// <summary>
/// The minimum HP deducted for a <see cref="HitResult.Miss"/>.
/// This occurs when HP Drain = 0.
2017-03-23 18:24:23 +08:00
/// </summary>
private const double hp_miss_min = -0.0018;
/// <summary>
/// The median HP deducted for a <see cref="HitResult.Miss"/>.
/// This occurs when HP Drain = 5.
2017-03-23 18:24:23 +08:00
/// </summary>
private const double hp_miss_mid = -0.0075;
/// <summary>
/// The maximum HP deducted for a <see cref="HitResult.Miss"/>.
/// This occurs when HP Drain = 10.
2017-03-23 18:24:23 +08:00
/// </summary>
private const double hp_miss_max = -0.12;
/// <summary>
/// The HP awarded for a <see cref="DrumRollTick"/> hit.
/// <para>
/// <see cref="DrumRollTick"/> hits award less HP as they're more spammable, although in hindsight
/// this probably awards too little HP and is kept at this value for now for compatibility.
/// </para>
/// </summary>
private const double hp_hit_tick = 0.00000003;
/// <summary>
/// Taiko fails at the end of the map if the player has not half-filled their HP bar.
/// </summary>
public override bool HasFailed => totalHits == maxTotalHits && Health.Value <= 0.5;
/// <summary>
/// The cumulative combo portion of the score.
2017-03-23 18:24:23 +08:00
/// </summary>
private double comboScore => combo_portion_max * comboPortion / maxComboPortion;
/// <summary>
/// The cumulative accuracy portion of the score.
2017-03-23 18:24:23 +08:00
/// </summary>
private double accuracyScore => accuracy_portion_max * Math.Pow(Accuracy, 3.6) * totalHits / maxTotalHits;
/// <summary>
/// The cumulative bonus score.
2017-03-23 18:24:23 +08:00
/// This is added on top of <see cref="max_score"/>, thus the total score can exceed <see cref="max_score"/>.
/// </summary>
private double bonusScore;
/// <summary>
/// The multiple of the original score added to the combo portion of the score
/// for correctly hitting an accented hit object with both keys.
2017-03-23 18:24:23 +08:00
/// </summary>
private double accentedHitScale;
2017-03-23 18:24:23 +08:00
private double hpIncreaseTick;
private double hpIncreaseGreat;
private double hpIncreaseGood;
private double hpIncreaseMiss;
private double maxComboPortion;
private double comboPortion;
private int maxTotalHits;
private int totalHits;
public TaikoScoreProcessor()
{
}
public TaikoScoreProcessor(HitRenderer<TaikoHitObject, TaikoJudgement> hitRenderer)
: base(hitRenderer)
{
}
2017-03-23 18:24:23 +08:00
protected override void ComputeTargets(Beatmap<TaikoHitObject> beatmap)
{
double hpMultiplierNormal = 1 / (hp_hit_great * beatmap.HitObjects.FindAll(o => o is Hit).Count * BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.Difficulty.DrainRate, 0.5, 0.75, 0.98));
hpIncreaseTick = hp_hit_tick;
hpIncreaseGreat = hpMultiplierNormal * hp_hit_great;
hpIncreaseGood = hpMultiplierNormal * hp_hit_good;
2017-03-23 18:24:23 +08:00
hpIncreaseMiss = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.Difficulty.DrainRate, hp_miss_min, hp_miss_mid, hp_miss_max);
2017-03-28 09:02:41 +08:00
var accentedHits = beatmap.HitObjects.FindAll(o => o is Hit && o.IsStrong);
2017-03-23 18:24:23 +08:00
// This is a linear function that awards:
// 10 times bonus points for hitting an accented hit object with both keys with 30 accented hit objects in the map
// 3 times bonus points for hitting an accented hit object with both keys with 120 accented hit objects in the map
accentedHitScale = -7d / 90d * MathHelper.Clamp(accentedHits.Count, 30, 120) + 111d / 9d;
2017-03-23 18:24:23 +08:00
2017-03-23 18:39:30 +08:00
foreach (var obj in beatmap.HitObjects)
2017-03-23 18:24:23 +08:00
{
if (obj is Hit)
{
2017-03-23 18:39:30 +08:00
AddJudgement(new TaikoJudgement
2017-03-23 18:24:23 +08:00
{
Result = HitResult.Hit,
TaikoResult = TaikoHitResult.Great,
2017-03-28 09:02:41 +08:00
SecondHit = obj.IsStrong
2017-03-23 18:24:23 +08:00
});
}
else if (obj is DrumRoll)
{
for (int i = 0; i < ((DrumRoll)obj).TotalTicks; i++)
{
2017-03-23 18:39:30 +08:00
AddJudgement(new TaikoDrumRollTickJudgement
2017-03-23 18:24:23 +08:00
{
Result = HitResult.Hit,
TaikoResult = TaikoHitResult.Great,
2017-03-28 09:02:41 +08:00
SecondHit = obj.IsStrong
2017-03-23 18:24:23 +08:00
});
}
2017-03-23 18:39:30 +08:00
AddJudgement(new TaikoJudgement
2017-03-23 18:24:23 +08:00
{
Result = HitResult.Hit,
TaikoResult = TaikoHitResult.Great,
2017-03-28 09:02:41 +08:00
SecondHit = obj.IsStrong
2017-03-23 18:24:23 +08:00
});
}
2017-03-25 19:57:49 +08:00
else if (obj is Swell)
2017-03-23 18:24:23 +08:00
{
2017-03-23 18:39:30 +08:00
AddJudgement(new TaikoJudgement
2017-03-23 18:24:23 +08:00
{
Result = HitResult.Hit,
TaikoResult = TaikoHitResult.Great
});
}
}
maxTotalHits = totalHits;
maxComboPortion = comboPortion;
}
protected override void OnNewJugement(TaikoJudgement judgement)
{
bool isTick = judgement is TaikoDrumRollTickJudgement;
2017-03-23 18:24:23 +08:00
// Don't consider ticks as a type of hit that counts towards map completion
2017-03-24 10:24:07 +08:00
if (!isTick)
2017-03-23 18:24:23 +08:00
totalHits++;
// Apply score changes
if (judgement.Result == HitResult.Hit)
2017-03-23 18:24:23 +08:00
{
double baseValue = judgement.ResultValueForScore;
2017-03-23 18:24:23 +08:00
// Add bonus points for hitting an accented hit object with the second key
if (judgement.SecondHit)
baseValue += baseValue * accentedHitScale;
2017-03-23 18:24:23 +08:00
// Add score to portions
2017-03-24 10:24:07 +08:00
if (isTick)
2017-03-23 18:24:23 +08:00
bonusScore += baseValue;
else
{
Combo.Value++;
// A relevance factor that needs to be applied to make higher combos more relevant
// Value is capped at 400 combo
double comboRelevance = Math.Min(Math.Log(400, combo_base), Math.Max(0.5, Math.Log(Combo.Value, combo_base)));
comboPortion += baseValue * comboRelevance;
}
}
// Apply HP changes
switch (judgement.Result)
2017-03-23 18:24:23 +08:00
{
case HitResult.Miss:
// Missing ticks shouldn't drop HP
2017-03-24 10:24:07 +08:00
if (!isTick)
2017-03-23 18:24:23 +08:00
Health.Value += hpIncreaseMiss;
break;
case HitResult.Hit:
switch (judgement.TaikoResult)
2017-03-23 18:24:23 +08:00
{
case TaikoHitResult.Good:
Health.Value += hpIncreaseGood;
break;
case TaikoHitResult.Great:
2017-03-24 10:24:07 +08:00
if (isTick)
2017-03-23 18:24:23 +08:00
Health.Value += hpIncreaseTick;
else
Health.Value += hpIncreaseGreat;
break;
}
break;
}
// Compute the new score + accuracy
int scoreForAccuracy = 0;
int maxScoreForAccuracy = 0;
2017-03-23 18:24:23 +08:00
foreach (var j in Judgements)
{
scoreForAccuracy += j.ResultValueForAccuracy;
maxScoreForAccuracy = j.MaxResultValueForAccuracy;
2017-03-23 18:24:23 +08:00
}
Accuracy.Value = (double)scoreForAccuracy / maxScoreForAccuracy;
2017-03-23 18:24:23 +08:00
TotalScore.Value = comboScore + accuracyScore + bonusScore;
}
protected override void Reset()
{
base.Reset();
Health.Value = 0;
bonusScore = 0;
comboPortion = 0;
totalHits = 0;
}
}
}