mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 23:47:24 +08:00
Merge pull request #1250 from smoogipooo/scoring-revamp
Revamp scoring game-wide for more intuitive exponential and standardised scoring calculations
This commit is contained in:
commit
b08844d2db
@ -9,8 +9,6 @@ using osu.Game.Rulesets.Mods;
|
|||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Catch.Scoring;
|
|
||||||
using osu.Game.Rulesets.Scoring;
|
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch
|
namespace osu.Game.Rulesets.Catch
|
||||||
@ -99,8 +97,6 @@ namespace osu.Game.Rulesets.Catch
|
|||||||
|
|
||||||
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap) => new CatchDifficultyCalculator(beatmap);
|
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap) => new CatchDifficultyCalculator(beatmap);
|
||||||
|
|
||||||
public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor();
|
|
||||||
|
|
||||||
public override int LegacyID => 2;
|
public override int LegacyID => 2;
|
||||||
|
|
||||||
public CatchRuleset(RulesetInfo rulesetInfo)
|
public CatchRuleset(RulesetInfo rulesetInfo)
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
using osu.Game.Rulesets.Judgements;
|
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
|
|
||||||
@ -18,17 +17,5 @@ namespace osu.Game.Rulesets.Catch.Scoring
|
|||||||
: base(rulesetContainer)
|
: base(rulesetContainer)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Reset()
|
|
||||||
{
|
|
||||||
base.Reset();
|
|
||||||
|
|
||||||
Health.Value = 1;
|
|
||||||
Accuracy.Value = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnNewJudgement(Judgement judgement)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,17 +23,5 @@ namespace osu.Game.Rulesets.Mania.Judgements
|
|||||||
return base.NumericResultFor(HasBroken ? HitResult.Good : result);
|
return base.NumericResultFor(HasBroken ? HitResult.Good : result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override int NumericResultForAccuracy(HitResult result)
|
|
||||||
{
|
|
||||||
switch (result)
|
|
||||||
{
|
|
||||||
default:
|
|
||||||
return base.NumericResultForAccuracy(result);
|
|
||||||
case HitResult.Great:
|
|
||||||
case HitResult.Perfect:
|
|
||||||
return base.NumericResultForAccuracy(HasBroken ? HitResult.Good : result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -10,6 +10,5 @@ namespace osu.Game.Rulesets.Mania.Judgements
|
|||||||
public override bool AffectsCombo => false;
|
public override bool AffectsCombo => false;
|
||||||
|
|
||||||
protected override int NumericResultFor(HitResult result) => 20;
|
protected override int NumericResultFor(HitResult result) => 20;
|
||||||
protected override int NumericResultForAccuracy(HitResult result) => 0; // Don't count ticks into accuracy
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -8,11 +8,6 @@ namespace osu.Game.Rulesets.Mania.Judgements
|
|||||||
{
|
{
|
||||||
public class ManiaJudgement : Judgement
|
public class ManiaJudgement : Judgement
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// The maximum result value for the accuracy portion of the score.
|
|
||||||
/// </summary>
|
|
||||||
public int MaxNumericAccuracyResult => NumericResultForAccuracy(HitResult.Perfect);
|
|
||||||
|
|
||||||
protected override int NumericResultFor(HitResult result)
|
protected override int NumericResultFor(HitResult result)
|
||||||
{
|
{
|
||||||
switch (result)
|
switch (result)
|
||||||
@ -30,29 +25,5 @@ namespace osu.Game.Rulesets.Mania.Judgements
|
|||||||
return 300;
|
return 300;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int NumericAccuracyResult => NumericResultForAccuracy(Result);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The result value for the accuracy portion of the score.
|
|
||||||
/// </summary>
|
|
||||||
protected virtual int NumericResultForAccuracy(HitResult result)
|
|
||||||
{
|
|
||||||
switch (result)
|
|
||||||
{
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
case HitResult.Meh:
|
|
||||||
return 50;
|
|
||||||
case HitResult.Ok:
|
|
||||||
return 100;
|
|
||||||
case HitResult.Good:
|
|
||||||
return 200;
|
|
||||||
case HitResult.Great:
|
|
||||||
return 300;
|
|
||||||
case HitResult.Perfect:
|
|
||||||
return 305;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,8 +10,6 @@ using System.Collections.Generic;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Mania.Scoring;
|
|
||||||
using osu.Game.Rulesets.Scoring;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania
|
namespace osu.Game.Rulesets.Mania
|
||||||
{
|
{
|
||||||
@ -111,8 +109,6 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
|
|
||||||
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap) => new ManiaDifficultyCalculator(beatmap);
|
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap) => new ManiaDifficultyCalculator(beatmap);
|
||||||
|
|
||||||
public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor();
|
|
||||||
|
|
||||||
public override int LegacyID => 3;
|
public override int LegacyID => 3;
|
||||||
|
|
||||||
public ManiaRuleset(RulesetInfo rulesetInfo)
|
public ManiaRuleset(RulesetInfo rulesetInfo)
|
||||||
|
@ -78,8 +78,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ManiaJudgement CreateJudgement() => new HoldNoteTickJudgement();
|
|
||||||
|
|
||||||
protected override void CheckForJudgements(bool userTriggered, double timeOffset)
|
protected override void CheckForJudgements(bool userTriggered, double timeOffset)
|
||||||
{
|
{
|
||||||
if (!userTriggered)
|
if (!userTriggered)
|
||||||
@ -91,7 +89,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
if (HoldStartTime?.Invoke() > HitObject.StartTime)
|
if (HoldStartTime?.Invoke() > HitObject.StartTime)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
AddJudgement(new ManiaJudgement { Result = HitResult.Perfect });
|
AddJudgement(new HoldNoteTickJudgement { Result = HitResult.Perfect });
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateState(ArmedState state)
|
protected override void UpdateState(ArmedState state)
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
@ -15,32 +14,6 @@ namespace osu.Game.Rulesets.Mania.Scoring
|
|||||||
{
|
{
|
||||||
internal class ManiaScoreProcessor : ScoreProcessor<ManiaHitObject>
|
internal class ManiaScoreProcessor : ScoreProcessor<ManiaHitObject>
|
||||||
{
|
{
|
||||||
/// <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 combo value at which hit objects result in the max score possible.
|
|
||||||
/// </summary>
|
|
||||||
private const int combo_relevance_cap = 400;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The hit HP multiplier at OD = 0.
|
/// The hit HP multiplier at OD = 0.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -116,42 +89,6 @@ namespace osu.Game.Rulesets.Mania.Scoring
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private double hpMultiplier = 1;
|
private double hpMultiplier = 1;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The cumulative combo portion of the score.
|
|
||||||
/// </summary>
|
|
||||||
private double comboScore => combo_portion_max * comboPortion / maxComboPortion;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The cumulative accuracy portion of the score.
|
|
||||||
/// </summary>
|
|
||||||
private double accuracyScore => accuracy_portion_max * Math.Pow(Accuracy, 4) * totalHits / maxTotalHits;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The cumulative bonus score.
|
|
||||||
/// 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 <see cref="comboPortion"/> achieved by a perfect playthrough.
|
|
||||||
/// </summary>
|
|
||||||
private double maxComboPortion;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The portion of the score dedicated to combo.
|
|
||||||
/// </summary>
|
|
||||||
private double comboPortion;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The <see cref="totalHits"/> achieved by a perfect playthrough.
|
|
||||||
/// </summary>
|
|
||||||
private int maxTotalHits;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The total hits.
|
|
||||||
/// </summary>
|
|
||||||
private int totalHits;
|
|
||||||
|
|
||||||
public ManiaScoreProcessor()
|
public ManiaScoreProcessor()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -161,7 +98,7 @@ namespace osu.Game.Rulesets.Mania.Scoring
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void ComputeTargets(Beatmap<ManiaHitObject> beatmap)
|
protected override void SimulateAutoplay(Beatmap<ManiaHitObject> beatmap)
|
||||||
{
|
{
|
||||||
BeatmapDifficulty difficulty = beatmap.BeatmapInfo.Difficulty;
|
BeatmapDifficulty difficulty = beatmap.BeatmapInfo.Difficulty;
|
||||||
hpMultiplier = BeatmapDifficulty.DifficultyRange(difficulty.DrainRate, hp_multiplier_min, hp_multiplier_mid, hp_multiplier_max);
|
hpMultiplier = BeatmapDifficulty.DifficultyRange(difficulty.DrainRate, hp_multiplier_min, hp_multiplier_mid, hp_multiplier_max);
|
||||||
@ -173,11 +110,7 @@ namespace osu.Game.Rulesets.Mania.Scoring
|
|||||||
{
|
{
|
||||||
var holdNote = obj as HoldNote;
|
var holdNote = obj as HoldNote;
|
||||||
|
|
||||||
if (obj is Note)
|
if (holdNote != null)
|
||||||
{
|
|
||||||
AddJudgement(new ManiaJudgement { Result = HitResult.Perfect });
|
|
||||||
}
|
|
||||||
else if (holdNote != null)
|
|
||||||
{
|
{
|
||||||
// Head
|
// Head
|
||||||
AddJudgement(new ManiaJudgement { Result = HitResult.Perfect });
|
AddJudgement(new ManiaJudgement { Result = HitResult.Perfect });
|
||||||
@ -186,9 +119,9 @@ namespace osu.Game.Rulesets.Mania.Scoring
|
|||||||
int tickCount = holdNote.Ticks.Count();
|
int tickCount = holdNote.Ticks.Count();
|
||||||
for (int i = 0; i < tickCount; i++)
|
for (int i = 0; i < tickCount; i++)
|
||||||
AddJudgement(new HoldNoteTickJudgement { Result = HitResult.Perfect });
|
AddJudgement(new HoldNoteTickJudgement { Result = HitResult.Perfect });
|
||||||
|
|
||||||
AddJudgement(new HoldNoteTailJudgement { Result = HitResult.Perfect });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AddJudgement(new ManiaJudgement { Result = HitResult.Perfect });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!HasFailed)
|
if (!HasFailed)
|
||||||
@ -197,29 +130,23 @@ namespace osu.Game.Rulesets.Mania.Scoring
|
|||||||
hpMultiplier *= 1.01;
|
hpMultiplier *= 1.01;
|
||||||
hpMissMultiplier *= 0.98;
|
hpMissMultiplier *= 0.98;
|
||||||
|
|
||||||
Reset();
|
Reset(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
maxTotalHits = totalHits;
|
|
||||||
maxComboPortion = comboPortion;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnNewJudgement(Judgement judgement)
|
protected override void OnNewJudgement(Judgement judgement)
|
||||||
{
|
{
|
||||||
|
base.OnNewJudgement(judgement);
|
||||||
|
|
||||||
bool isTick = judgement is HoldNoteTickJudgement;
|
bool isTick = judgement is HoldNoteTickJudgement;
|
||||||
|
|
||||||
if (isTick)
|
if (isTick)
|
||||||
{
|
{
|
||||||
if (judgement.IsHit)
|
if (judgement.IsHit)
|
||||||
{
|
|
||||||
Health.Value += hpMultiplier * hp_increase_tick;
|
Health.Value += hpMultiplier * hp_increase_tick;
|
||||||
bonusScore += judgement.NumericResult;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
totalHits++;
|
|
||||||
|
|
||||||
switch (judgement.Result)
|
switch (judgement.Result)
|
||||||
{
|
{
|
||||||
case HitResult.Miss:
|
case HitResult.Miss:
|
||||||
@ -241,40 +168,7 @@ namespace osu.Game.Rulesets.Mania.Scoring
|
|||||||
Health.Value += hpMultiplier * hp_increase_perfect;
|
Health.Value += hpMultiplier * hp_increase_perfect;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (judgement.IsHit)
|
|
||||||
{
|
|
||||||
// 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.NumericResult * comboRelevance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int scoreForAccuracy = 0;
|
|
||||||
int maxScoreForAccuracy = 0;
|
|
||||||
|
|
||||||
foreach (var j in Judgements)
|
|
||||||
{
|
|
||||||
var maniaJudgement = (ManiaJudgement)j;
|
|
||||||
|
|
||||||
scoreForAccuracy += maniaJudgement.NumericAccuracyResult;
|
|
||||||
maxScoreForAccuracy += maniaJudgement.MaxNumericAccuracyResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
Accuracy.Value = (double)scoreForAccuracy / maxScoreForAccuracy;
|
|
||||||
TotalScore.Value = comboScore + accuracyScore + bonusScore;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Reset()
|
|
||||||
{
|
|
||||||
base.Reset();
|
|
||||||
|
|
||||||
Health.Value = 1;
|
|
||||||
Accuracy.Value = 1;
|
|
||||||
|
|
||||||
bonusScore = 0;
|
|
||||||
comboPortion = 0;
|
|
||||||
totalHits = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,12 +8,10 @@ using osu.Game.Rulesets.Objects.Drawables;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Judgements
|
namespace osu.Game.Rulesets.Osu.Judgements
|
||||||
{
|
{
|
||||||
public class SliderTickJudgement : OsuJudgement
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public class OsuJudgement : Judgement
|
public class OsuJudgement : Judgement
|
||||||
{
|
{
|
||||||
|
public override HitResult MaxResult => HitResult.Great;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The positional hit offset.
|
/// The positional hit offset.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
protected override void CheckForJudgements(bool userTriggered, double timeOffset)
|
protected override void CheckForJudgements(bool userTriggered, double timeOffset)
|
||||||
{
|
{
|
||||||
if (timeOffset >= 0)
|
if (timeOffset >= 0)
|
||||||
AddJudgement(new SliderTickJudgement { Result = Tracking ? HitResult.Perfect : HitResult.Miss });
|
AddJudgement(new OsuJudgement { Result = Tracking ? HitResult.Great : HitResult.Miss });
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdatePreemptState()
|
protected override void UpdatePreemptState()
|
||||||
|
@ -12,8 +12,6 @@ using osu.Game.Rulesets.UI;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Osu.Scoring;
|
|
||||||
using osu.Game.Rulesets.Scoring;
|
|
||||||
using osu.Game.Overlays.Settings;
|
using osu.Game.Overlays.Settings;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
|
|
||||||
@ -118,8 +116,6 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
|
|
||||||
public override string Description => "osu!";
|
public override string Description => "osu!";
|
||||||
|
|
||||||
public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor();
|
|
||||||
|
|
||||||
public override SettingsSubsection CreateSettings() => new OsuSettings();
|
public override SettingsSubsection CreateSettings() => new OsuSettings();
|
||||||
|
|
||||||
public override int LegacyID => 0;
|
public override int LegacyID => 0;
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Framework.Configuration;
|
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
@ -18,8 +16,6 @@ namespace osu.Game.Rulesets.Osu.Scoring
|
|||||||
{
|
{
|
||||||
internal class OsuScoreProcessor : ScoreProcessor<OsuHitObject>
|
internal class OsuScoreProcessor : ScoreProcessor<OsuHitObject>
|
||||||
{
|
{
|
||||||
public readonly Bindable<ScoringMode> Mode = new Bindable<ScoringMode>(ScoringMode.Exponential);
|
|
||||||
|
|
||||||
public OsuScoreProcessor()
|
public OsuScoreProcessor()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -31,31 +27,33 @@ namespace osu.Game.Rulesets.Osu.Scoring
|
|||||||
|
|
||||||
private float hpDrainRate;
|
private float hpDrainRate;
|
||||||
|
|
||||||
private int totalAccurateJudgements;
|
|
||||||
|
|
||||||
private readonly Dictionary<HitResult, int> scoreResultCounts = new Dictionary<HitResult, int>();
|
private readonly Dictionary<HitResult, int> scoreResultCounts = new Dictionary<HitResult, int>();
|
||||||
private readonly Dictionary<ComboResult, int> comboResultCounts = new Dictionary<ComboResult, int>();
|
private readonly Dictionary<ComboResult, int> comboResultCounts = new Dictionary<ComboResult, int>();
|
||||||
|
|
||||||
private double comboMaxScore;
|
protected override void SimulateAutoplay(Beatmap<OsuHitObject> beatmap)
|
||||||
|
|
||||||
protected override void ComputeTargets(Beatmap<OsuHitObject> beatmap)
|
|
||||||
{
|
{
|
||||||
hpDrainRate = beatmap.BeatmapInfo.Difficulty.DrainRate;
|
hpDrainRate = beatmap.BeatmapInfo.Difficulty.DrainRate;
|
||||||
totalAccurateJudgements = beatmap.HitObjects.Count;
|
|
||||||
|
|
||||||
foreach (var unused in beatmap.HitObjects)
|
foreach (var obj in beatmap.HitObjects)
|
||||||
{
|
{
|
||||||
// TODO: add support for other object types.
|
var slider = obj as Slider;
|
||||||
|
if (slider != null)
|
||||||
|
{
|
||||||
|
// Head
|
||||||
|
AddJudgement(new OsuJudgement { Result = HitResult.Great });
|
||||||
|
|
||||||
|
// Ticks
|
||||||
|
foreach (var unused in slider.Ticks)
|
||||||
|
AddJudgement(new OsuJudgement { Result = HitResult.Great });
|
||||||
|
}
|
||||||
|
|
||||||
AddJudgement(new OsuJudgement { Result = HitResult.Great });
|
AddJudgement(new OsuJudgement { Result = HitResult.Great });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Reset()
|
protected override void Reset(bool storeResults)
|
||||||
{
|
{
|
||||||
base.Reset();
|
base.Reset(storeResults);
|
||||||
|
|
||||||
Health.Value = 1;
|
|
||||||
Accuracy.Value = 1;
|
|
||||||
|
|
||||||
scoreResultCounts.Clear();
|
scoreResultCounts.Clear();
|
||||||
comboResultCounts.Clear();
|
comboResultCounts.Clear();
|
||||||
@ -73,6 +71,8 @@ namespace osu.Game.Rulesets.Osu.Scoring
|
|||||||
|
|
||||||
protected override void OnNewJudgement(Judgement judgement)
|
protected override void OnNewJudgement(Judgement judgement)
|
||||||
{
|
{
|
||||||
|
base.OnNewJudgement(judgement);
|
||||||
|
|
||||||
var osuJudgement = (OsuJudgement)judgement;
|
var osuJudgement = (OsuJudgement)judgement;
|
||||||
|
|
||||||
if (judgement.Result != HitResult.None)
|
if (judgement.Result != HitResult.None)
|
||||||
@ -103,52 +103,6 @@ namespace osu.Game.Rulesets.Osu.Scoring
|
|||||||
Health.Value -= hpDrainRate * 0.04;
|
Health.Value -= hpDrainRate * 0.04;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
calculateScore();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void calculateScore()
|
|
||||||
{
|
|
||||||
int baseScore = 0;
|
|
||||||
double comboScore = 0;
|
|
||||||
|
|
||||||
int baseMaxScore = 0;
|
|
||||||
|
|
||||||
foreach (var j in Judgements)
|
|
||||||
{
|
|
||||||
baseScore += j.NumericResult;
|
|
||||||
baseMaxScore += j.MaxNumericResult;
|
|
||||||
|
|
||||||
comboScore += j.NumericResult * (1 + Combo.Value / 10d);
|
|
||||||
}
|
|
||||||
|
|
||||||
Accuracy.Value = (double)baseScore / baseMaxScore;
|
|
||||||
|
|
||||||
if (comboScore > comboMaxScore)
|
|
||||||
comboMaxScore = comboScore;
|
|
||||||
|
|
||||||
if (baseScore == 0)
|
|
||||||
TotalScore.Value = 0;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// temporary to make scoring feel more like score v1 without being score v1.
|
|
||||||
float exponentialFactor = Mode.Value == ScoringMode.Exponential ? (float)Judgements.Count / 100 : 1;
|
|
||||||
|
|
||||||
TotalScore.Value =
|
|
||||||
(int)
|
|
||||||
(
|
|
||||||
exponentialFactor *
|
|
||||||
700000 * comboScore / comboMaxScore +
|
|
||||||
300000 * Math.Pow(Accuracy.Value, 10) * ((double)Judgements.Count / totalAccurateJudgements) +
|
|
||||||
0 /* bonusScore */
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum ScoringMode
|
|
||||||
{
|
|
||||||
Standardised,
|
|
||||||
Exponential
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,10 +19,5 @@ namespace osu.Game.Rulesets.Taiko.Judgements
|
|||||||
return 200;
|
return 200;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override int NumericResultForAccuracy(HitResult result)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -8,19 +8,10 @@ namespace osu.Game.Rulesets.Taiko.Judgements
|
|||||||
{
|
{
|
||||||
public class TaikoJudgement : Judgement
|
public class TaikoJudgement : Judgement
|
||||||
{
|
{
|
||||||
/// <summary>
|
public override HitResult MaxResult => HitResult.Great;
|
||||||
/// The result value for the accuracy portion of the score.
|
|
||||||
/// </summary>
|
|
||||||
public int ResultNumericForAccuracy => Result == HitResult.Miss ? 0 : NumericResultForAccuracy(Result);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The maximum result value for the accuracy portion of the score.
|
|
||||||
/// </summary>
|
|
||||||
public int MaxResultValueForAccuracy => NumericResultForAccuracy(HitResult.Great);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Computes the numeric result value for the combo portion of the score.
|
/// Computes the numeric result value for the combo portion of the score.
|
||||||
/// For the accuracy portion of the score (including accuracy percentage), see <see cref="NumericResultForAccuracy(HitResult)"/>.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="result">The result to compute the value for.</param>
|
/// <param name="result">The result to compute the value for.</param>
|
||||||
/// <returns>The numeric result value.</returns>
|
/// <returns>The numeric result value.</returns>
|
||||||
@ -36,24 +27,5 @@ namespace osu.Game.Rulesets.Taiko.Judgements
|
|||||||
return 300;
|
return 300;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Computes the numeric result value for the accuracy portion of the score.
|
|
||||||
/// For the combo portion of the score, see <see cref="NumericResultFor(HitResult)"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="result">The result to compute the value for.</param>
|
|
||||||
/// <returns>The numeric result value.</returns>
|
|
||||||
protected virtual int NumericResultForAccuracy(HitResult result)
|
|
||||||
{
|
|
||||||
switch (result)
|
|
||||||
{
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
case HitResult.Good:
|
|
||||||
return 150;
|
|
||||||
case HitResult.Great:
|
|
||||||
return 300;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,8 @@ namespace osu.Game.Rulesets.Taiko.Judgements
|
|||||||
{
|
{
|
||||||
public class TaikoStrongHitJudgement : TaikoJudgement
|
public class TaikoStrongHitJudgement : TaikoJudgement
|
||||||
{
|
{
|
||||||
|
public override bool AffectsCombo => false;
|
||||||
|
|
||||||
public TaikoStrongHitJudgement()
|
public TaikoStrongHitJudgement()
|
||||||
{
|
{
|
||||||
base.Result = HitResult.Perfect;
|
base.Result = HitResult.Perfect;
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
using System;
|
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
@ -9,33 +8,11 @@ using osu.Game.Rulesets.Scoring;
|
|||||||
using osu.Game.Rulesets.Taiko.Judgements;
|
using osu.Game.Rulesets.Taiko.Judgements;
|
||||||
using osu.Game.Rulesets.Taiko.Objects;
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using OpenTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Scoring
|
namespace osu.Game.Rulesets.Taiko.Scoring
|
||||||
{
|
{
|
||||||
internal class TaikoScoreProcessor : ScoreProcessor<TaikoHitObject>
|
internal class TaikoScoreProcessor : ScoreProcessor<TaikoHitObject>
|
||||||
{
|
{
|
||||||
/// <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>
|
/// <summary>
|
||||||
/// The HP awarded by a <see cref="HitResult.Great"/> hit.
|
/// The HP awarded by a <see cref="HitResult.Great"/> hit.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -76,40 +53,13 @@ namespace osu.Game.Rulesets.Taiko.Scoring
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Taiko fails at the end of the map if the player has not half-filled their HP bar.
|
/// Taiko fails at the end of the map if the player has not half-filled their HP bar.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public override bool HasFailed => totalHits == maxTotalHits && Health.Value <= 0.5;
|
public override bool HasFailed => Hits == MaxHits && Health.Value <= 0.5;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The cumulative combo portion of the score.
|
|
||||||
/// </summary>
|
|
||||||
private double comboScore => combo_portion_max * comboPortion / maxComboPortion;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The cumulative accuracy portion of the score.
|
|
||||||
/// </summary>
|
|
||||||
private double accuracyScore => accuracy_portion_max * Math.Pow(Accuracy, 3.6) * totalHits / maxTotalHits;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The cumulative bonus score.
|
|
||||||
/// 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 a strong hit object with both keys.
|
|
||||||
/// </summary>
|
|
||||||
private double strongHitScale;
|
|
||||||
|
|
||||||
private double hpIncreaseTick;
|
private double hpIncreaseTick;
|
||||||
private double hpIncreaseGreat;
|
private double hpIncreaseGreat;
|
||||||
private double hpIncreaseGood;
|
private double hpIncreaseGood;
|
||||||
private double hpIncreaseMiss;
|
private double hpIncreaseMiss;
|
||||||
|
|
||||||
private double maxComboPortion;
|
|
||||||
private double comboPortion;
|
|
||||||
private int maxTotalHits;
|
|
||||||
private int totalHits;
|
|
||||||
|
|
||||||
public TaikoScoreProcessor()
|
public TaikoScoreProcessor()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -119,7 +69,7 @@ namespace osu.Game.Rulesets.Taiko.Scoring
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void ComputeTargets(Beatmap<TaikoHitObject> beatmap)
|
protected override void SimulateAutoplay(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));
|
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));
|
||||||
|
|
||||||
@ -128,13 +78,6 @@ namespace osu.Game.Rulesets.Taiko.Scoring
|
|||||||
hpIncreaseGood = hpMultiplierNormal * hp_hit_good;
|
hpIncreaseGood = hpMultiplierNormal * hp_hit_good;
|
||||||
hpIncreaseMiss = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.Difficulty.DrainRate, hp_miss_min, hp_miss_mid, hp_miss_max);
|
hpIncreaseMiss = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.Difficulty.DrainRate, hp_miss_min, hp_miss_mid, hp_miss_max);
|
||||||
|
|
||||||
var strongHits = beatmap.HitObjects.FindAll(o => o is Hit && o.IsStrong);
|
|
||||||
|
|
||||||
// This is a linear function that awards:
|
|
||||||
// 10 times bonus points for hitting a strong hit object with both keys with 30 strong hit objects in the map
|
|
||||||
// 3 times bonus points for hitting a strong hit object with both keys with 120 strong hit objects in the map
|
|
||||||
strongHitScale = -7d / 90d * MathHelper.Clamp(strongHits.Count, 30, 120) + 111d / 9d;
|
|
||||||
|
|
||||||
foreach (var obj in beatmap.HitObjects)
|
foreach (var obj in beatmap.HitObjects)
|
||||||
{
|
{
|
||||||
if (obj is Hit)
|
if (obj is Hit)
|
||||||
@ -163,46 +106,14 @@ namespace osu.Game.Rulesets.Taiko.Scoring
|
|||||||
AddJudgement(new TaikoJudgement { Result = HitResult.Great });
|
AddJudgement(new TaikoJudgement { Result = HitResult.Great });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
maxTotalHits = totalHits;
|
|
||||||
maxComboPortion = comboPortion;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnNewJudgement(Judgement judgement)
|
protected override void OnNewJudgement(Judgement judgement)
|
||||||
{
|
{
|
||||||
bool isStrong = judgement is TaikoStrongHitJudgement;
|
base.OnNewJudgement(judgement);
|
||||||
|
|
||||||
bool isTick = judgement is TaikoDrumRollTickJudgement;
|
bool isTick = judgement is TaikoDrumRollTickJudgement;
|
||||||
|
|
||||||
// Don't consider ticks and strong hits as a type of hit that counts towards map completion
|
|
||||||
if (!isTick && !isStrong)
|
|
||||||
totalHits++;
|
|
||||||
|
|
||||||
// Apply score changes
|
|
||||||
if (judgement.IsHit)
|
|
||||||
{
|
|
||||||
double baseValue = judgement.NumericResult;
|
|
||||||
|
|
||||||
if (isStrong)
|
|
||||||
{
|
|
||||||
// Add increased score for the previous judgement by hitting a strong hit object with the second key
|
|
||||||
var prevJudgement = Judgements[Judgements.Count - 1];
|
|
||||||
baseValue = prevJudgement.NumericResult * strongHitScale;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add score to portions
|
|
||||||
if (judgement is TaikoDrumRollTickJudgement)
|
|
||||||
bonusScore += baseValue;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 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
|
// Apply HP changes
|
||||||
switch (judgement.Result)
|
switch (judgement.Result)
|
||||||
{
|
{
|
||||||
@ -221,32 +132,13 @@ namespace osu.Game.Rulesets.Taiko.Scoring
|
|||||||
Health.Value += hpIncreaseGreat;
|
Health.Value += hpIncreaseGreat;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
int scoreForAccuracy = 0;
|
|
||||||
int maxScoreForAccuracy = 0;
|
|
||||||
|
|
||||||
foreach (var j in Judgements)
|
|
||||||
{
|
|
||||||
var taikoJudgement = (TaikoJudgement)j;
|
|
||||||
|
|
||||||
scoreForAccuracy += taikoJudgement.ResultNumericForAccuracy;
|
|
||||||
maxScoreForAccuracy += taikoJudgement.MaxResultValueForAccuracy;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Accuracy.Value = (double)scoreForAccuracy / maxScoreForAccuracy;
|
protected override void Reset(bool storeResults)
|
||||||
TotalScore.Value = comboScore + accuracyScore + bonusScore;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Reset()
|
|
||||||
{
|
{
|
||||||
base.Reset();
|
base.Reset(storeResults);
|
||||||
|
|
||||||
Health.Value = 0;
|
Health.Value = 0;
|
||||||
Accuracy.Value = 1;
|
|
||||||
|
|
||||||
bonusScore = 0;
|
|
||||||
comboPortion = 0;
|
|
||||||
totalHits = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,8 +9,6 @@ using osu.Game.Rulesets.Taiko.UI;
|
|||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Scoring;
|
|
||||||
using osu.Game.Rulesets.Taiko.Scoring;
|
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko
|
namespace osu.Game.Rulesets.Taiko
|
||||||
@ -101,8 +99,6 @@ namespace osu.Game.Rulesets.Taiko
|
|||||||
|
|
||||||
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap) => new TaikoDifficultyCalculator(beatmap);
|
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap) => new TaikoDifficultyCalculator(beatmap);
|
||||||
|
|
||||||
public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor();
|
|
||||||
|
|
||||||
public override int LegacyID => 1;
|
public override int LegacyID => 1;
|
||||||
|
|
||||||
public TaikoRuleset(RulesetInfo rulesetInfo)
|
public TaikoRuleset(RulesetInfo rulesetInfo)
|
||||||
|
@ -7,7 +7,6 @@ using osu.Framework.Audio.Track;
|
|||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Scoring;
|
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
|
|
||||||
namespace osu.Game.Beatmaps
|
namespace osu.Game.Beatmaps
|
||||||
@ -62,11 +61,6 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap) => null;
|
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap) => null;
|
||||||
|
|
||||||
public override ScoreProcessor CreateScoreProcessor()
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string Description => "dummy";
|
public override string Description => "dummy";
|
||||||
|
|
||||||
public DummyRuleset(RulesetInfo rulesetInfo)
|
public DummyRuleset(RulesetInfo rulesetInfo)
|
||||||
|
@ -25,6 +25,10 @@ namespace osu.Game.Rulesets.Judgements
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public double TimeOffset { get; internal set; }
|
public double TimeOffset { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the <see cref="Result"/> should affect the combo portion of the score.
|
||||||
|
/// If false, the <see cref="Result"/> will be considered for the bonus portion of the score.
|
||||||
|
/// </summary>
|
||||||
public virtual bool AffectsCombo => true;
|
public virtual bool AffectsCombo => true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -31,6 +31,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
}
|
}
|
||||||
|
|
||||||
public override bool RemoveWhenNotAlive => false;
|
public override bool RemoveWhenNotAlive => false;
|
||||||
|
protected override bool RequiresChildrenUpdate => true;
|
||||||
|
|
||||||
protected DrawableScrollingHitObject(TObject hitObject)
|
protected DrawableScrollingHitObject(TObject hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
|
@ -10,7 +10,6 @@ using osu.Game.Beatmaps;
|
|||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Overlays.Settings;
|
using osu.Game.Overlays.Settings;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Scoring;
|
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets
|
namespace osu.Game.Rulesets
|
||||||
@ -50,8 +49,6 @@ namespace osu.Game.Rulesets
|
|||||||
|
|
||||||
public abstract DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap);
|
public abstract DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap);
|
||||||
|
|
||||||
public abstract ScoreProcessor CreateScoreProcessor();
|
|
||||||
|
|
||||||
public virtual Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_question_circle };
|
public virtual Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_question_circle };
|
||||||
|
|
||||||
public abstract string Description { get; }
|
public abstract string Description { get; }
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Diagnostics;
|
||||||
using osu.Framework.Configuration;
|
using osu.Framework.Configuration;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
@ -15,11 +15,17 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
public abstract class ScoreProcessor
|
public abstract class ScoreProcessor
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invoked when the ScoreProcessor is in a failed state.
|
/// Invoked when the <see cref="ScoreProcessor"/> is in a failed state.
|
||||||
|
/// This may occur regardless of whether an <see cref="AllJudged"/> event is invoked.
|
||||||
/// Return true if the fail was permitted.
|
/// Return true if the fail was permitted.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event Func<bool> Failed;
|
public event Func<bool> Failed;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked when all <see cref="HitObject"/>s have been judged.
|
||||||
|
/// </summary>
|
||||||
|
public event Action AllJudged;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invoked when a new judgement has occurred. This occurs after the judgement has been processed by the <see cref="ScoreProcessor"/>.
|
/// Invoked when a new judgement has occurred. This occurs after the judgement has been processed by the <see cref="ScoreProcessor"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -33,7 +39,7 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The current accuracy.
|
/// The current accuracy.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly BindableDouble Accuracy = new BindableDouble { MinValue = 0, MaxValue = 1 };
|
public readonly BindableDouble Accuracy = new BindableDouble(1) { MinValue = 0, MaxValue = 1 };
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The current health.
|
/// The current health.
|
||||||
@ -50,10 +56,15 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly BindableInt HighestCombo = new BindableInt();
|
public readonly BindableInt HighestCombo = new BindableInt();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether all <see cref="Judgement"/>s have been processed.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual bool HasCompleted => false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether the score is in a failed state.
|
/// Whether the score is in a failed state.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual bool HasFailed => false;
|
public virtual bool HasFailed => Health.Value == Health.MinValue;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether this ScoreProcessor has already triggered the failed state.
|
/// Whether this ScoreProcessor has already triggered the failed state.
|
||||||
@ -63,8 +74,6 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
protected ScoreProcessor()
|
protected ScoreProcessor()
|
||||||
{
|
{
|
||||||
Combo.ValueChanged += delegate { HighestCombo.Value = Math.Max(HighestCombo.Value, Combo.Value); };
|
Combo.ValueChanged += delegate { HighestCombo.Value = Math.Max(HighestCombo.Value, Combo.Value); };
|
||||||
|
|
||||||
Reset();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ScoreRank rankFrom(double acc)
|
private ScoreRank rankFrom(double acc)
|
||||||
@ -85,11 +94,12 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Resets this ScoreProcessor to a default state.
|
/// Resets this ScoreProcessor to a default state.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected virtual void Reset()
|
/// <param name="storeResults">Whether to store the current state of the <see cref="ScoreProcessor"/> for future use.</param>
|
||||||
|
protected virtual void Reset(bool storeResults)
|
||||||
{
|
{
|
||||||
TotalScore.Value = 0;
|
TotalScore.Value = 0;
|
||||||
Accuracy.Value = 0;
|
Accuracy.Value = 1;
|
||||||
Health.Value = 0;
|
Health.Value = 1;
|
||||||
Combo.Value = 0;
|
Combo.Value = 0;
|
||||||
HighestCombo.Value = 0;
|
HighestCombo.Value = 0;
|
||||||
|
|
||||||
@ -118,6 +128,9 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
protected void NotifyNewJudgement(Judgement judgement)
|
protected void NotifyNewJudgement(Judgement judgement)
|
||||||
{
|
{
|
||||||
NewJudgement?.Invoke(judgement);
|
NewJudgement?.Invoke(judgement);
|
||||||
|
|
||||||
|
if (HasCompleted)
|
||||||
|
AllJudged?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -135,36 +148,55 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract class ScoreProcessor<TObject> : ScoreProcessor
|
public class ScoreProcessor<TObject> : ScoreProcessor
|
||||||
where TObject : HitObject
|
where TObject : HitObject
|
||||||
{
|
{
|
||||||
/// <summary>
|
private const double base_portion = 0.3;
|
||||||
/// All judgements held by this ScoreProcessor.
|
private const double combo_portion = 0.7;
|
||||||
/// </summary>
|
private const double max_score = 1000000;
|
||||||
protected readonly List<Judgement> Judgements = new List<Judgement>();
|
|
||||||
|
|
||||||
public override bool HasFailed => Health.Value == Health.MinValue;
|
public readonly Bindable<ScoringMode> Mode = new Bindable<ScoringMode>();
|
||||||
|
|
||||||
|
protected sealed override bool HasCompleted => Hits == MaxHits;
|
||||||
|
|
||||||
|
protected int MaxHits { get; private set; }
|
||||||
|
protected int Hits { get; private set; }
|
||||||
|
|
||||||
|
private double maxHighestCombo;
|
||||||
|
|
||||||
|
private double maxBaseScore;
|
||||||
|
private double rollingMaxBaseScore;
|
||||||
|
private double baseScore;
|
||||||
|
|
||||||
protected ScoreProcessor()
|
protected ScoreProcessor()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ScoreProcessor(RulesetContainer<TObject> rulesetContainer)
|
public ScoreProcessor(RulesetContainer<TObject> rulesetContainer)
|
||||||
{
|
{
|
||||||
Judgements.Capacity = rulesetContainer.Beatmap.HitObjects.Count;
|
Debug.Assert(base_portion + combo_portion == 1.0);
|
||||||
|
|
||||||
rulesetContainer.OnJudgement += AddJudgement;
|
rulesetContainer.OnJudgement += AddJudgement;
|
||||||
|
|
||||||
ComputeTargets(rulesetContainer.Beatmap);
|
SimulateAutoplay(rulesetContainer.Beatmap);
|
||||||
|
Reset(true);
|
||||||
|
|
||||||
Reset();
|
if (maxBaseScore == 0 || maxHighestCombo == 0)
|
||||||
|
{
|
||||||
|
Mode.Value = ScoringMode.Exponential;
|
||||||
|
Mode.Disabled = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Computes target scoring values for this ScoreProcessor. This is equivalent to performing an auto-play of the score to find the values.
|
/// Simulates an autoplay of <see cref="HitObject"/>s that will be judged by this <see cref="ScoreProcessor{TObject}"/>
|
||||||
|
/// by adding <see cref="Judgement"/>s for each <see cref="HitObject"/> in the <see cref="Beatmap{TObject}"/>.
|
||||||
|
/// <para>
|
||||||
|
/// This is required for <see cref="ScoringMode.Standardised"/> to work, otherwise <see cref="ScoringMode.Exponential"/> will be used.
|
||||||
|
/// </para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="beatmap">The Beatmap containing the objects that will be judged by this ScoreProcessor.</param>
|
/// <param name="beatmap">The <see cref="Beatmap{TObject}"/> containing the <see cref="HitObject"/>s that will be judged by this <see cref="ScoreProcessor{TObject}"/>.</param>
|
||||||
protected virtual void ComputeTargets(Beatmap<TObject> beatmap) { }
|
protected virtual void SimulateAutoplay(Beatmap<TObject> beatmap) { }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a judgement to this ScoreProcessor.
|
/// Adds a judgement to this ScoreProcessor.
|
||||||
@ -172,10 +204,16 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
/// <param name="judgement">The judgement to add.</param>
|
/// <param name="judgement">The judgement to add.</param>
|
||||||
protected void AddJudgement(Judgement judgement)
|
protected void AddJudgement(Judgement judgement)
|
||||||
{
|
{
|
||||||
bool exists = Judgements.Contains(judgement);
|
OnNewJudgement(judgement);
|
||||||
|
NotifyNewJudgement(judgement);
|
||||||
|
|
||||||
if (!exists)
|
UpdateFailed();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnNewJudgement(Judgement judgement)
|
||||||
{
|
{
|
||||||
|
double bonusScore = 0;
|
||||||
|
|
||||||
if (judgement.AffectsCombo)
|
if (judgement.AffectsCombo)
|
||||||
{
|
{
|
||||||
switch (judgement.Result)
|
switch (judgement.Result)
|
||||||
@ -189,28 +227,49 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
Combo.Value++;
|
Combo.Value++;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
baseScore += judgement.NumericResult;
|
||||||
|
rollingMaxBaseScore += judgement.MaxNumericResult;
|
||||||
|
|
||||||
|
Hits++;
|
||||||
}
|
}
|
||||||
|
else if (judgement.IsHit)
|
||||||
|
bonusScore += judgement.NumericResult;
|
||||||
|
|
||||||
Judgements.Add(judgement);
|
if (rollingMaxBaseScore != 0)
|
||||||
OnNewJudgement(judgement);
|
Accuracy.Value = baseScore / rollingMaxBaseScore;
|
||||||
|
|
||||||
NotifyNewJudgement(judgement);
|
switch (Mode.Value)
|
||||||
}
|
|
||||||
|
|
||||||
UpdateFailed();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Reset()
|
|
||||||
{
|
{
|
||||||
base.Reset();
|
case ScoringMode.Standardised:
|
||||||
|
TotalScore.Value = max_score * (base_portion * baseScore / maxBaseScore + combo_portion * HighestCombo / maxHighestCombo) + bonusScore;
|
||||||
Judgements.Clear();
|
break;
|
||||||
|
case ScoringMode.Exponential:
|
||||||
|
TotalScore.Value = (baseScore + bonusScore) * Math.Log(HighestCombo + 1, 2);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
protected override void Reset(bool storeResults)
|
||||||
/// Updates any values that need post-processing. Invoked when a new judgement has occurred.
|
{
|
||||||
/// </summary>
|
if (storeResults)
|
||||||
/// <param name="judgement">The judgement that triggered this calculation.</param>
|
{
|
||||||
protected abstract void OnNewJudgement(Judgement judgement);
|
MaxHits = Hits;
|
||||||
|
maxHighestCombo = HighestCombo;
|
||||||
|
maxBaseScore = baseScore;
|
||||||
|
}
|
||||||
|
|
||||||
|
base.Reset(storeResults);
|
||||||
|
|
||||||
|
Hits = 0;
|
||||||
|
baseScore = 0;
|
||||||
|
rollingMaxBaseScore = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ScoringMode
|
||||||
|
{
|
||||||
|
Standardised,
|
||||||
|
Exponential
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ namespace osu.Game.Rulesets.Timing
|
|||||||
internal Axes ScrollingAxes;
|
internal Axes ScrollingAxes;
|
||||||
|
|
||||||
public override bool RemoveWhenNotAlive => false;
|
public override bool RemoveWhenNotAlive => false;
|
||||||
|
protected override bool RequiresChildrenUpdate => true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The control point that defines the speed adjustments for this container. This is set by the <see cref="SpeedAdjustmentContainer"/>.
|
/// The control point that defines the speed adjustments for this container. This is set by the <see cref="SpeedAdjustmentContainer"/>.
|
||||||
|
@ -38,6 +38,7 @@ namespace osu.Game.Rulesets.Timing
|
|||||||
}
|
}
|
||||||
|
|
||||||
public override bool RemoveWhenNotAlive => false;
|
public override bool RemoveWhenNotAlive => false;
|
||||||
|
protected override bool RequiresChildrenUpdate => true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The <see cref="MultiplierControlPoint"/> that defines the speed adjustments.
|
/// The <see cref="MultiplierControlPoint"/> that defines the speed adjustments.
|
||||||
|
@ -29,11 +29,6 @@ namespace osu.Game.Rulesets.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class RulesetContainer : Container
|
public abstract class RulesetContainer : Container
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Invoked when all the judgeable HitObjects have been judged.
|
|
||||||
/// </summary>
|
|
||||||
public event Action OnAllJudged;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether to apply adjustments to the child <see cref="Playfield"/> based on our own size.
|
/// Whether to apply adjustments to the child <see cref="Playfield"/> based on our own size.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -61,11 +56,6 @@ namespace osu.Game.Rulesets.UI
|
|||||||
|
|
||||||
public abstract IEnumerable<HitObject> Objects { get; }
|
public abstract IEnumerable<HitObject> Objects { get; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether all the HitObjects have been judged.
|
|
||||||
/// </summary>
|
|
||||||
protected abstract bool AllObjectsJudged { get; }
|
|
||||||
|
|
||||||
protected readonly Ruleset Ruleset;
|
protected readonly Ruleset Ruleset;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -77,15 +67,6 @@ namespace osu.Game.Rulesets.UI
|
|||||||
Ruleset = ruleset;
|
Ruleset = ruleset;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks whether all HitObjects have been judged, and invokes OnAllJudged.
|
|
||||||
/// </summary>
|
|
||||||
protected void CheckAllJudged()
|
|
||||||
{
|
|
||||||
if (AllObjectsJudged)
|
|
||||||
OnAllJudged?.Invoke();
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract ScoreProcessor CreateScoreProcessor();
|
public abstract ScoreProcessor CreateScoreProcessor();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -152,7 +133,7 @@ namespace osu.Game.Rulesets.UI
|
|||||||
|
|
||||||
public sealed override bool ProvidingUserCursor => !HasReplayLoaded && Playfield.ProvidingUserCursor;
|
public sealed override bool ProvidingUserCursor => !HasReplayLoaded && Playfield.ProvidingUserCursor;
|
||||||
|
|
||||||
protected override bool AllObjectsJudged => drawableObjects.All(h => h.AllJudged);
|
public override ScoreProcessor CreateScoreProcessor() => new ScoreProcessor<TObject>(this);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The playfield.
|
/// The playfield.
|
||||||
@ -162,8 +143,6 @@ namespace osu.Game.Rulesets.UI
|
|||||||
protected override Container<Drawable> Content => content;
|
protected override Container<Drawable> Content => content;
|
||||||
private Container content;
|
private Container content;
|
||||||
|
|
||||||
private readonly List<DrawableHitObject<TObject>> drawableObjects = new List<DrawableHitObject<TObject>>();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether to assume the beatmap passed into this <see cref="RulesetContainer{TObject}"/> is for the current ruleset.
|
/// Whether to assume the beatmap passed into this <see cref="RulesetContainer{TObject}"/> is for the current ruleset.
|
||||||
/// Creates a hit renderer for a beatmap.
|
/// Creates a hit renderer for a beatmap.
|
||||||
@ -250,8 +229,6 @@ namespace osu.Game.Rulesets.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void loadObjects()
|
private void loadObjects()
|
||||||
{
|
{
|
||||||
drawableObjects.Capacity = Beatmap.HitObjects.Count;
|
|
||||||
|
|
||||||
foreach (TObject h in Beatmap.HitObjects)
|
foreach (TObject h in Beatmap.HitObjects)
|
||||||
{
|
{
|
||||||
var drawableObject = GetVisualRepresentation(h);
|
var drawableObject = GetVisualRepresentation(h);
|
||||||
@ -263,10 +240,8 @@ namespace osu.Game.Rulesets.UI
|
|||||||
{
|
{
|
||||||
Playfield.OnJudgement(d, j);
|
Playfield.OnJudgement(d, j);
|
||||||
OnJudgement?.Invoke(j);
|
OnJudgement?.Invoke(j);
|
||||||
CheckAllJudged();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
drawableObjects.Add(drawableObject);
|
|
||||||
Playfield.Add(drawableObject);
|
Playfield.Add(drawableObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -206,10 +206,8 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
hudOverlay.ModDisplay.Current.BindTo(working.Mods);
|
hudOverlay.ModDisplay.Current.BindTo(working.Mods);
|
||||||
|
|
||||||
//bind RulesetContainer to ScoreProcessor and ourselves (for a pass situation)
|
// Bind ScoreProcessor to ourselves
|
||||||
RulesetContainer.OnAllJudged += onCompletion;
|
scoreProcessor.AllJudged += onCompletion;
|
||||||
|
|
||||||
//bind ScoreProcessor to ourselves (for a fail situation)
|
|
||||||
scoreProcessor.Failed += onFail;
|
scoreProcessor.Failed += onFail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user