1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-22 05:27:23 +08:00

Merge pull request #571 from smoogipooo/partial-judgements

Implement partial judgements + make Result non-nullable.
This commit is contained in:
Dean Herbert 2017-03-31 17:07:32 +09:00 committed by GitHub
commit f42935eccf
20 changed files with 172 additions and 56 deletions

View File

@ -56,7 +56,6 @@ namespace osu.Desktop.VisualTests.Tests
Result = HitResult.Hit,
TaikoResult = hitResult,
TimeOffset = 0,
ComboAtHit = 1,
SecondHit = RNG.Next(10) == 0
}
});
@ -69,8 +68,7 @@ namespace osu.Desktop.VisualTests.Tests
Judgement = new TaikoJudgement
{
Result = HitResult.Miss,
TimeOffset = 0,
ComboAtHit = 0
TimeOffset = 0
}
});
}

View File

@ -19,7 +19,7 @@ namespace osu.Game.Modes.Catch.Scoring
{
}
protected override void OnNewJugement(CatchJudgement judgement)
protected override void OnNewJudgement(CatchJudgement judgement)
{
}
}

View File

@ -19,7 +19,7 @@ namespace osu.Game.Modes.Mania.Scoring
{
}
protected override void OnNewJugement(ManiaJudgement judgement)
protected override void OnNewJudgement(ManiaJudgement judgement)
{
}
}

View File

@ -38,7 +38,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
Colour = AccentColour,
Hit = () =>
{
if (Judgement.Result.HasValue) return false;
if (Judgement.Result != HitResult.None) return false;
Judgement.PositionOffset = Vector2.Zero; //todo: set to correct value
UpdateJudgement(true);

View File

@ -28,7 +28,7 @@ namespace osu.Game.Modes.Osu.Scoring
Accuracy.Value = 1;
}
protected override void OnNewJugement(OsuJudgement judgement)
protected override void OnNewJudgement(OsuJudgement judgement)
{
if (judgement != null)
{

View File

@ -22,7 +22,7 @@ namespace osu.Game.Modes.Taiko.Judgements
/// The result value for the combo portion of the score.
/// </summary>
public int ResultValueForScore => NumericResultForScore(TaikoResult);
/// <summary>
/// The result value for the accuracy portion of the score.
/// </summary>
@ -32,7 +32,7 @@ namespace osu.Game.Modes.Taiko.Judgements
/// The maximum result value for the combo portion of the score.
/// </summary>
public int MaxResultValueForScore => NumericResultForScore(MAX_HIT_RESULT);
/// <summary>
/// The maximum result value for the accuracy portion of the score.
/// </summary>
@ -45,7 +45,7 @@ namespace osu.Game.Modes.Taiko.Judgements
/// <summary>
/// Whether this Judgement has a secondary hit in the case of finishers.
/// </summary>
public bool SecondHit;
public virtual bool SecondHit { get; set; }
/// <summary>
/// Computes the numeric result value for the combo portion of the score.

View File

@ -0,0 +1,25 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Modes.Judgements;
namespace osu.Game.Modes.Taiko.Judgements
{
public class TaikoStrongHitJudgement : TaikoJudgement, IPartialJudgement
{
public bool Changed { get; set; }
public override bool SecondHit
{
get { return base.SecondHit; }
set
{
if (base.SecondHit == value)
return;
base.SecondHit = value;
Changed = true;
}
}
}
}

View File

@ -47,7 +47,7 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable
protected override bool HandleKeyPress(Key key)
{
return !Judgement.Result.HasValue && UpdateJudgement(true);
return Judgement.Result == HitResult.None && UpdateJudgement(true);
}
}
}

View File

@ -68,7 +68,7 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable
protected override bool HandleKeyPress(Key key)
{
if (Judgement.Result.HasValue)
if (Judgement.Result != HitResult.None)
return false;
validKeyPressed = HitKeys.Contains(key);

View File

@ -5,6 +5,8 @@ using OpenTK.Input;
using System;
using System.Linq;
using osu.Framework.Input;
using osu.Game.Modes.Objects.Drawables;
using osu.Game.Modes.Taiko.Judgements;
namespace osu.Game.Modes.Taiko.Objects.Drawable
{
@ -25,9 +27,11 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable
{
}
protected override TaikoJudgement CreateJudgement() => new TaikoStrongHitJudgement();
protected override void CheckJudgement(bool userTriggered)
{
if (!Judgement.Result.HasValue)
if (Judgement.Result == HitResult.None)
{
base.CheckJudgement(userTriggered);
return;
@ -45,7 +49,7 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable
protected override bool HandleKeyPress(Key key)
{
// Check if we've handled the first key
if (!Judgement.Result.HasValue)
if (Judgement.Result == HitResult.None)
{
// First key hasn't been handled yet, attempt to handle it
bool handled = base.HandleKeyPress(key);

View File

@ -219,7 +219,7 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable
protected override bool HandleKeyPress(Key key)
{
if (Judgement.Result.HasValue)
if (Judgement.Result != HitResult.None)
return false;
// Don't handle keys before the swell starts

View File

@ -96,9 +96,9 @@ namespace osu.Game.Modes.Taiko.Scoring
/// <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.
/// for correctly hitting a strong hit object with both keys.
/// </summary>
private double accentedHitScale;
private double strongHitScale;
private double hpIncreaseTick;
private double hpIncreaseGreat;
@ -128,12 +128,12 @@ namespace osu.Game.Modes.Taiko.Scoring
hpIncreaseGood = hpMultiplierNormal * hp_hit_good;
hpIncreaseMiss = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.Difficulty.DrainRate, hp_miss_min, hp_miss_mid, hp_miss_max);
var accentedHits = beatmap.HitObjects.FindAll(o => o is Hit && o.IsStrong);
var strongHits = beatmap.HitObjects.FindAll(o => o is Hit && o.IsStrong);
// 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;
// 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)
{
@ -179,7 +179,7 @@ namespace osu.Game.Modes.Taiko.Scoring
maxComboPortion = comboPortion;
}
protected override void OnNewJugement(TaikoJudgement judgement)
protected override void OnNewJudgement(TaikoJudgement judgement)
{
bool isTick = judgement is TaikoDrumRollTickJudgement;
@ -187,29 +187,12 @@ namespace osu.Game.Modes.Taiko.Scoring
if (!isTick)
totalHits++;
// Apply combo changes, must be done before the hit score is added
if (!isTick && judgement.Result == HitResult.Hit)
Combo.Value++;
// Apply score changes
if (judgement.Result == HitResult.Hit)
{
double baseValue = judgement.ResultValueForScore;
// Add bonus points for hitting an accented hit object with the second key
if (judgement.SecondHit)
baseValue += baseValue * accentedHitScale;
// Add score to portions
if (isTick)
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;
}
}
addHitScore(judgement);
// Apply HP changes
switch (judgement.Result)
@ -235,7 +218,43 @@ namespace osu.Game.Modes.Taiko.Scoring
break;
}
// Compute the new score + accuracy
calculateScore();
}
protected override void OnJudgementChanged(TaikoJudgement judgement)
{
// Apply score changes
addHitScore(judgement);
calculateScore();
}
private void addHitScore(TaikoJudgement judgement)
{
if (judgement.Result != HitResult.Hit)
return;
double baseValue = judgement.ResultValueForScore;
// Add increased score for hitting a strong hit object with the second key
if (judgement.SecondHit)
baseValue *= 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;
}
}
private void calculateScore()
{
int scoreForAccuracy = 0;
int maxScoreForAccuracy = 0;

View File

@ -50,6 +50,7 @@
<Compile Include="Beatmaps\TaikoBeatmapConverter.cs" />
<Compile Include="Beatmaps\TaikoBeatmapProcessor.cs" />
<Compile Include="Judgements\TaikoDrumRollTickJudgement.cs" />
<Compile Include="Judgements\TaikoStrongHitJudgement.cs" />
<Compile Include="Judgements\TaikoJudgement.cs" />
<Compile Include="Judgements\TaikoHitResult.cs" />
<Compile Include="Objects\Drawable\DrawableRimHit.cs" />

View File

@ -0,0 +1,26 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Modes.Objects.Drawables;
using osu.Game.Modes.Scoring;
namespace osu.Game.Modes.Judgements
{
/// <summary>
/// Inidicates that the judgement this is attached to is a partial judgement and the scoring value may change.
/// <para>
/// This judgement will be continually processed by <see cref="DrawableHitObject{TObject, TJudgement}.CheckJudgement(bool)"/>
/// unless the result is a miss and will trigger a full re-process of the <see cref="ScoreProcessor"/> when changed.
/// </para>
/// </summary>
public interface IPartialJudgement
{
/// <summary>
/// Indicates that this partial judgement has changed and requires a full re-process of the <see cref="ScoreProcessor"/>.
/// <para>
/// This is set to false once the judgement has been re-processed.
/// </para>
/// </summary>
bool Changed { get; set; }
}
}

View File

@ -10,7 +10,7 @@ namespace osu.Game.Modes.Judgements
/// <summary>
/// Whether this judgement is the result of a hit or a miss.
/// </summary>
public HitResult? Result;
public HitResult Result;
/// <summary>
/// The offset at which this judgement occurred.
@ -20,7 +20,7 @@ namespace osu.Game.Modes.Judgements
/// <summary>
/// The combo after this judgement was processed.
/// </summary>
public ulong? ComboAtHit;
public int ComboAtHit;
/// <summary>
/// The string representation for the result achieved.

View File

@ -93,16 +93,26 @@ namespace osu.Game.Modes.Objects.Drawables
/// <returns>Whether a hit was processed.</returns>
protected bool UpdateJudgement(bool userTriggered)
{
if (Judgement.Result != null)
IPartialJudgement partial = Judgement as IPartialJudgement;
// Never re-process non-partial hits
if (Judgement.Result != HitResult.None && partial == null)
return false;
// Update the judgement state
double endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime;
Judgement.TimeOffset = Time.Current - endTime;
// Update the judgement state
bool hadResult = Judgement.Result != HitResult.None;
CheckJudgement(userTriggered);
if (Judgement.Result == null)
// Don't process judgements with no result
if (Judgement.Result == HitResult.None)
return false;
// Don't process judgements that previously had results but the results were unchanged
if (hadResult && partial?.Changed != true)
return false;
switch (Judgement.Result)
@ -117,6 +127,9 @@ namespace osu.Game.Modes.Objects.Drawables
OnJudgement?.Invoke(this);
if (partial != null)
partial.Changed = false;
return true;
}

View File

@ -7,8 +7,19 @@ namespace osu.Game.Modes.Objects.Drawables
{
public enum HitResult
{
/// <summary>
/// Indicates that the object has not been judged yet.
/// </summary>
[Description("")]
None,
/// <summary>
/// Indicates that the object has been judged as a miss.
/// </summary>
[Description(@"Miss")]
Miss,
/// <summary>
/// Indicates that the object has been judged as a hit.
/// </summary>
[Description(@"Hit")]
Hit,
}

View File

@ -141,11 +141,17 @@ namespace osu.Game.Modes.Scoring
/// <param name="judgement">The judgement to add.</param>
protected void AddJudgement(TJudgement judgement)
{
Judgements.Add(judgement);
bool exists = Judgements.Contains(judgement);
OnNewJugement(judgement);
if (!exists)
{
Judgements.Add(judgement);
OnNewJudgement(judgement);
judgement.ComboAtHit = (ulong)Combo.Value;
judgement.ComboAtHit = Combo.Value;
}
else
OnJudgementChanged(judgement);
UpdateFailed();
}
@ -158,9 +164,21 @@ namespace osu.Game.Modes.Scoring
}
/// <summary>
/// Update any values that potentially need post-processing on a judgement change.
/// Updates any values that need post-processing. Invoked when a new judgement has occurred.
/// <para>
/// This is not triggered when existing judgements are changed - for that see <see cref="OnJudgementChanged(TJudgement)"/>.
/// </para>
/// </summary>
/// <param name="judgement">The judgement that triggered this calculation.</param>
protected abstract void OnNewJugement(TJudgement judgement);
protected abstract void OnNewJudgement(TJudgement judgement);
/// <summary>
/// Updates any values that need post-processing. Invoked when an existing judgement has changed.
/// <para>
/// This is not triggered when a new judgement has occurred - for that see <see cref="OnNewJudgement(TJudgement)"/>.
/// </para>
/// </summary>
/// <param name="judgement">The judgement that triggered this calculation.</param>
protected virtual void OnJudgementChanged(TJudgement judgement) { }
}
}

View File

@ -158,7 +158,7 @@ namespace osu.Game.Modes.UI
public event Action<TJudgement> OnJudgement;
protected override Container<Drawable> Content => content;
protected override bool AllObjectsJudged => Playfield.HitObjects.Children.All(h => h.Judgement.Result.HasValue);
protected override bool AllObjectsJudged => Playfield.HitObjects.Children.All(h => h.Judgement.Result != HitResult.None);
/// <summary>
/// The playfield.

View File

@ -100,6 +100,7 @@
<Compile Include="IPC\ScoreIPCChannel.cs" />
<Compile Include="Modes\Replays\Replay.cs" />
<Compile Include="Modes\Judgements\DrawableJudgement.cs" />
<Compile Include="Modes\Judgements\IPartialJudgement.cs" />
<Compile Include="Modes\Replays\FramedReplayInputHandler.cs" />
<Compile Include="Modes\Mods\IApplicableMod.cs" />
<Compile Include="Modes\Mods\ModType.cs" />