Skip to content

Commit

Permalink
Merge pull request #571 from smoogipooo/partial-judgements
Browse files Browse the repository at this point in the history
Implement partial judgements + make Result non-nullable.
  • Loading branch information
peppy authored Mar 31, 2017
2 parents b078dfd + 14fcc19 commit f42935e
Show file tree
Hide file tree
Showing 20 changed files with 172 additions and 56 deletions.
4 changes: 1 addition & 3 deletions osu.Desktop.VisualTests/Tests/TestCaseTaikoPlayfield.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ private void addHitJudgement()
Result = HitResult.Hit,
TaikoResult = hitResult,
TimeOffset = 0,
ComboAtHit = 1,
SecondHit = RNG.Next(10) == 0
}
});
Expand All @@ -69,8 +68,7 @@ private void addMissJudgement()
Judgement = new TaikoJudgement
{
Result = HitResult.Miss,
TimeOffset = 0,
ComboAtHit = 0
TimeOffset = 0
}
});
}
Expand Down
2 changes: 1 addition & 1 deletion osu.Game.Modes.Catch/Scoring/CatchScoreProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public CatchScoreProcessor(HitRenderer<CatchBaseHit, CatchJudgement> hitRenderer
{
}

protected override void OnNewJugement(CatchJudgement judgement)
protected override void OnNewJudgement(CatchJudgement judgement)
{
}
}
Expand Down
2 changes: 1 addition & 1 deletion osu.Game.Modes.Mania/Scoring/ManiaScoreProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public ManiaScoreProcessor(HitRenderer<ManiaBaseHit, ManiaJudgement> hitRenderer
{
}

protected override void OnNewJugement(ManiaJudgement judgement)
protected override void OnNewJudgement(ManiaJudgement judgement)
{
}
}
Expand Down
2 changes: 1 addition & 1 deletion osu.Game.Modes.Osu/Objects/Drawables/DrawableHitCircle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public DrawableHitCircle(OsuHitObject h) : base(h)
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);
Expand Down
2 changes: 1 addition & 1 deletion osu.Game.Modes.Osu/Scoring/OsuScoreProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ protected override void Reset()
Accuracy.Value = 1;
}

protected override void OnNewJugement(OsuJudgement judgement)
protected override void OnNewJudgement(OsuJudgement judgement)
{
if (judgement != null)
{
Expand Down
6 changes: 3 additions & 3 deletions osu.Game.Modes.Taiko/Judgements/TaikoJudgement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public class TaikoJudgement : Judgement
/// 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>
Expand All @@ -32,7 +32,7 @@ public class TaikoJudgement : Judgement
/// 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>
Expand All @@ -45,7 +45,7 @@ public class TaikoJudgement : Judgement
/// <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.
Expand Down
25 changes: 25 additions & 0 deletions osu.Game.Modes.Taiko/Judgements/TaikoStrongHitJudgement.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ protected override void UpdateScrollPosition(double time)

protected override bool HandleKeyPress(Key key)
{
return !Judgement.Result.HasValue && UpdateJudgement(true);
return Judgement.Result == HitResult.None && UpdateJudgement(true);
}
}
}
2 changes: 1 addition & 1 deletion osu.Game.Modes.Taiko/Objects/Drawable/DrawableHit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ protected override void CheckJudgement(bool userTriggered)

protected override bool HandleKeyPress(Key key)
{
if (Judgement.Result.HasValue)
if (Judgement.Result != HitResult.None)
return false;

validKeyPressed = HitKeys.Contains(key);
Expand Down
8 changes: 6 additions & 2 deletions osu.Game.Modes.Taiko/Objects/Drawable/DrawableStrongHit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
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
{
Expand All @@ -25,9 +27,11 @@ protected DrawableStrongHit(Hit hit)
{
}

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;
Expand All @@ -45,7 +49,7 @@ protected override void CheckJudgement(bool userTriggered)
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);
Expand Down
2 changes: 1 addition & 1 deletion osu.Game.Modes.Taiko/Objects/Drawable/DrawableSwell.cs
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ protected override void UpdateScrollPosition(double time)

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
Expand Down
79 changes: 49 additions & 30 deletions osu.Game.Modes.Taiko/Scoring/TaikoScoreProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,9 @@ internal class TaikoScoreProcessor : ScoreProcessor<TaikoHitObject, TaikoJudgeme

/// <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;
Expand Down Expand Up @@ -128,12 +128,12 @@ protected override void ComputeTargets(Beatmap<TaikoHitObject> beatmap)
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)
{
Expand Down Expand Up @@ -179,37 +179,20 @@ protected override void ComputeTargets(Beatmap<TaikoHitObject> beatmap)
maxComboPortion = comboPortion;
}

protected override void OnNewJugement(TaikoJudgement judgement)
protected override void OnNewJudgement(TaikoJudgement judgement)
{
bool isTick = judgement is TaikoDrumRollTickJudgement;

// Don't consider ticks as a type of hit that counts towards map completion
if (!isTick)
totalHits++;

// Apply score changes
if (judgement.Result == HitResult.Hit)
{
double baseValue = judgement.ResultValueForScore;
// Apply combo changes, must be done before the hit score is added
if (!isTick && judgement.Result == HitResult.Hit)
Combo.Value++;

// 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;
}
}
// Apply score changes
addHitScore(judgement);

// Apply HP changes
switch (judgement.Result)
Expand All @@ -235,7 +218,43 @@ protected override void OnNewJugement(TaikoJudgement judgement)
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;

Expand Down
1 change: 1 addition & 0 deletions osu.Game.Modes.Taiko/osu.Game.Modes.Taiko.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -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" />
Expand Down
26 changes: 26 additions & 0 deletions osu.Game/Modes/Judgements/IPartialJudgement.cs
Original file line number Diff line number Diff line change
@@ -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; }
}
}
4 changes: 2 additions & 2 deletions osu.Game/Modes/Judgements/Judgement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public abstract class Judgement
/// <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.
Expand All @@ -20,7 +20,7 @@ public abstract class Judgement
/// <summary>
/// The combo after this judgement was processed.
/// </summary>
public ulong? ComboAtHit;
public int ComboAtHit;

/// <summary>
/// The string representation for the result achieved.
Expand Down
19 changes: 16 additions & 3 deletions osu.Game/Modes/Objects/Drawables/DrawableHitObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,16 +93,26 @@ protected DrawableHitObject(TObject hitObject)
/// <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)
Expand All @@ -117,6 +127,9 @@ protected bool UpdateJudgement(bool userTriggered)

OnJudgement?.Invoke(this);

if (partial != null)
partial.Changed = false;

return true;
}

Expand Down
11 changes: 11 additions & 0 deletions osu.Game/Modes/Objects/Drawables/HitResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand Down
Loading

0 comments on commit f42935e

Please sign in to comment.