From eba6371526095c2e39f850f734c33671a496866c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 25 Dec 2019 21:16:40 +0900 Subject: [PATCH] Re-implement taiko's accumulating health processor --- .../Scoring/TaikoHealthProcessor.cs | 47 ++++++++ osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 + osu.Game/Rulesets/Ruleset.cs | 2 +- .../Scoring/AccumulatingHealthProcessor.cs | 20 ++++ .../Scoring/DrainingHealthProcessor.cs | 108 ++++++++++++++++++ osu.Game/Rulesets/Scoring/HealthProcessor.cs | 99 ++-------------- 6 files changed, 188 insertions(+), 90 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs create mode 100644 osu.Game/Rulesets/Scoring/AccumulatingHealthProcessor.cs create mode 100644 osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs new file mode 100644 index 0000000000..679addc32d --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs @@ -0,0 +1,47 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Taiko.Objects; + +namespace osu.Game.Rulesets.Taiko.Scoring +{ + public class TaikoHealthProcessor : AccumulatingHealthProcessor + { + /// + /// A value used for calculating . + /// + private const double object_count_factor = 3; + + /// + /// HP multiplier for a successful . + /// + private double hpMultiplier; + + /// + /// HP multiplier for a . + /// + private double hpMissMultiplier; + + public TaikoHealthProcessor(double gameplayStartTime) + : base(gameplayStartTime) + { + } + + public override void ApplyBeatmap(IBeatmap beatmap) + { + base.ApplyBeatmap(beatmap); + + hpMultiplier = 1 / (object_count_factor * beatmap.HitObjects.OfType().Count() * BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.5, 0.75, 0.98)); + hpMissMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.0018, 0.0075, 0.0120); + } + + protected override double GetHealthIncreaseFor(JudgementResult result) + => base.GetHealthIncreaseFor(result) * (result.Type == HitResult.Miss ? hpMissMultiplier : hpMultiplier); + + protected override bool DefaultFailCondition => JudgedHits == MaxHits && Health.Value <= 0.5; + } +} diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 79de2d0334..de589f81b6 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -30,6 +30,8 @@ namespace osu.Game.Rulesets.Taiko public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(); + public override HealthProcessor CreateHealthProcessor(double gameplayStartTime) => new TaikoHealthProcessor(gameplayStartTime); + public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(beatmap, this); diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 6310b19cd3..ff58d3c8dc 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -80,7 +80,7 @@ namespace osu.Game.Rulesets /// Creates a for this . /// /// The health processor. - public virtual HealthProcessor CreateHealthProcessor(double gameplayStartTime) => new HealthProcessor(gameplayStartTime); + public virtual HealthProcessor CreateHealthProcessor(double gameplayStartTime) => new DrainingHealthProcessor(gameplayStartTime); /// /// Creates a to convert a to one that is applicable for this . diff --git a/osu.Game/Rulesets/Scoring/AccumulatingHealthProcessor.cs b/osu.Game/Rulesets/Scoring/AccumulatingHealthProcessor.cs new file mode 100644 index 0000000000..edc8d18804 --- /dev/null +++ b/osu.Game/Rulesets/Scoring/AccumulatingHealthProcessor.cs @@ -0,0 +1,20 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Rulesets.Scoring +{ + public class AccumulatingHealthProcessor : HealthProcessor + { + public AccumulatingHealthProcessor(double gameplayStartTime) + : base(gameplayStartTime) + { + } + + protected override void Reset(bool storeResults) + { + base.Reset(storeResults); + + Health.Value = 0; + } + } +} diff --git a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs new file mode 100644 index 0000000000..8b30728caf --- /dev/null +++ b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs @@ -0,0 +1,108 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Rulesets.Scoring +{ + public class DrainingHealthProcessor : HealthProcessor + { + private IBeatmap beatmap; + + private List<(double time, double health)> healthIncreases; + private double targetMinimumHealth; + private double drainRate = 1; + + public DrainingHealthProcessor(double gameplayStartTime) + : base(gameplayStartTime) + { + } + + protected override void Update() + { + base.Update(); + + if (!IsBreakTime.Value) + { + // When jumping from before the gameplay start to after it or vice-versa, we only want to consider any drain since the gameplay start time + double lastTime = Math.Max(GameplayStartTime, Time.Current - Time.Elapsed); + Health.Value -= drainRate * (Time.Current - lastTime); + } + } + + public override void ApplyBeatmap(IBeatmap beatmap) + { + this.beatmap = beatmap; + + healthIncreases = new List<(double time, double health)>(); + targetMinimumHealth = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.95, 0.70, 0.30); + + base.ApplyBeatmap(beatmap); + + // Only required during the simulation stage + healthIncreases = null; + } + + protected override void ApplyResultInternal(JudgementResult result) + { + base.ApplyResultInternal(result); + healthIncreases?.Add((result.HitObject.GetEndTime() + result.TimeOffset, GetHealthIncreaseFor(result))); + } + + protected override void Reset(bool storeResults) + { + base.Reset(storeResults); + + drainRate = 1; + + if (storeResults) + { + int count = 1; + + while (true) + { + double currentHealth = 1; + double lowestHealth = 1; + int currentBreak = -1; + + for (int i = 0; i < healthIncreases.Count; i++) + { + double currentTime = healthIncreases[i].time; + double lastTime = i > 0 ? healthIncreases[i - 1].time : GameplayStartTime; + + // Subtract any break time from the duration since the last object + if (beatmap.Breaks.Count > 0) + { + while (currentBreak + 1 < beatmap.Breaks.Count && beatmap.Breaks[currentBreak + 1].EndTime < currentTime) + currentBreak++; + + if (currentBreak >= 0) + lastTime = Math.Max(lastTime, beatmap.Breaks[currentBreak].EndTime); + } + + // Apply health adjustments + currentHealth -= (healthIncreases[i].time - lastTime) * drainRate; + lowestHealth = Math.Min(lowestHealth, currentHealth); + currentHealth = Math.Min(1, currentHealth + healthIncreases[i].health); + + // Common scenario for when the drain rate is definitely too harsh + if (lowestHealth < 0) + break; + } + + if (Math.Abs(lowestHealth - targetMinimumHealth) <= 0.01) + break; + + count *= 2; + drainRate += 1.0 / count * Math.Sign(lowestHealth - targetMinimumHealth); + } + } + + healthIncreases.Clear(); + } + } +} diff --git a/osu.Game/Rulesets/Scoring/HealthProcessor.cs b/osu.Game/Rulesets/Scoring/HealthProcessor.cs index 8dd6cc1ed9..597989701d 100644 --- a/osu.Game/Rulesets/Scoring/HealthProcessor.cs +++ b/osu.Game/Rulesets/Scoring/HealthProcessor.cs @@ -2,16 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using osu.Framework.Bindables; using osu.Framework.MathUtils; -using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Scoring { - public class HealthProcessor : JudgementProcessor + public abstract class HealthProcessor : JudgementProcessor { /// /// Invoked when the is in a failed state. @@ -39,42 +36,14 @@ namespace osu.Game.Rulesets.Scoring /// public bool HasFailed { get; private set; } - private readonly double gameplayStartTime; + /// + /// The gameplay start time. + /// + protected readonly double GameplayStartTime; - private IBeatmap beatmap; - - private List<(double time, double health)> healthIncreases; - private double targetMinimumHealth; - private double drainRate = 1; - - public HealthProcessor(double gameplayStartTime) + protected HealthProcessor(double gameplayStartTime) { - this.gameplayStartTime = gameplayStartTime; - } - - public override void ApplyBeatmap(IBeatmap beatmap) - { - this.beatmap = beatmap; - - healthIncreases = new List<(double time, double health)>(); - targetMinimumHealth = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.95, 0.70, 0.30); - - base.ApplyBeatmap(beatmap); - - // Only required during the simulation stage - healthIncreases = null; - } - - protected override void Update() - { - base.Update(); - - if (!IsBreakTime.Value) - { - // When jumping from before the gameplay start to after it or vice-versa, we only want to consider any drain since the gameplay start time - double lastTime = Math.Max(gameplayStartTime, Time.Current - Time.Elapsed); - Health.Value -= drainRate * (Time.Current - lastTime); - } + GameplayStartTime = gameplayStartTime; } protected override void ApplyResultInternal(JudgementResult result) @@ -82,13 +51,10 @@ namespace osu.Game.Rulesets.Scoring result.HealthAtJudgement = Health.Value; result.FailedAtJudgement = HasFailed; - double healthIncrease = result.Judgement.HealthIncreaseFor(result); - healthIncreases?.Add((result.HitObject.GetEndTime() + result.TimeOffset, healthIncrease)); - if (HasFailed) return; - Health.Value += healthIncrease; + Health.Value += GetHealthIncreaseFor(result); if (!DefaultFailCondition && FailConditions?.Invoke(this, result) != true) return; @@ -104,6 +70,8 @@ namespace osu.Game.Rulesets.Scoring // Todo: Revert HasFailed state with proper player support } + protected virtual double GetHealthIncreaseFor(JudgementResult result) => result.Judgement.HealthIncreaseFor(result); + /// /// The default conditions for failing. /// @@ -113,53 +81,6 @@ namespace osu.Game.Rulesets.Scoring { base.Reset(storeResults); - drainRate = 1; - - if (storeResults) - { - int count = 1; - - while (true) - { - double currentHealth = 1; - double lowestHealth = 1; - int currentBreak = -1; - - for (int i = 0; i < healthIncreases.Count; i++) - { - double currentTime = healthIncreases[i].time; - double lastTime = i > 0 ? healthIncreases[i - 1].time : gameplayStartTime; - - // Subtract any break time from the duration since the last object - if (beatmap.Breaks.Count > 0) - { - while (currentBreak + 1 < beatmap.Breaks.Count && beatmap.Breaks[currentBreak + 1].EndTime < currentTime) - currentBreak++; - - if (currentBreak >= 0) - lastTime = Math.Max(lastTime, beatmap.Breaks[currentBreak].EndTime); - } - - // Apply health adjustments - currentHealth -= (healthIncreases[i].time - lastTime) * drainRate; - lowestHealth = Math.Min(lowestHealth, currentHealth); - currentHealth = Math.Min(1, currentHealth + healthIncreases[i].health); - - // Common scenario for when the drain rate is definitely too harsh - if (lowestHealth < 0) - break; - } - - if (Math.Abs(lowestHealth - targetMinimumHealth) <= 0.01) - break; - - count *= 2; - drainRate += 1.0 / count * Math.Sign(lowestHealth - targetMinimumHealth); - } - } - - healthIncreases.Clear(); - Health.Value = 1; HasFailed = false; }