Merge branch 'master' into diffcalc/fix/clockrate-adjusted-decay

This commit is contained in:
smoogipoo
2021-03-26 11:47:38 +09:00
369 changed files with 4523 additions and 1782 deletions

View File

@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Difficulty
private DifficultyAttributes calculate(IBeatmap beatmap, Mod[] mods, double clockRate)
{
var skills = CreateSkills(beatmap);
var skills = CreateSkills(beatmap, mods);
if (!beatmap.HitObjects.Any())
return CreateDifficultyAttributes(beatmap, mods, skills, clockRate);
@ -202,7 +202,8 @@ namespace osu.Game.Rulesets.Difficulty
/// Creates the <see cref="Skill"/>s to calculate the difficulty of an <see cref="IBeatmap"/>.
/// </summary>
/// <param name="beatmap">The <see cref="IBeatmap"/> whose difficulty will be calculated.</param>
/// <param name="mods">Mods to calculate difficulty with.</param>
/// <returns>The <see cref="Skill"/>s.</returns>
protected abstract Skill[] CreateSkills(IBeatmap beatmap);
protected abstract Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods);
}
}

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Linq;
using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Utils;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Difficulty.Skills
{
@ -46,10 +47,22 @@ namespace osu.Game.Rulesets.Difficulty.Skills
/// </summary>
protected double CurrentStrain { get; private set; } = 1;
/// <summary>
/// Mods for use in skill calculations.
/// </summary>
protected IReadOnlyList<Mod> Mods => mods;
private double currentSectionPeak = 1; // We also keep track of the peak strain level in the current section.
private readonly List<double> strainPeaks = new List<double>();
private readonly Mod[] mods;
protected Skill(Mod[] mods)
{
this.mods = mods;
}
/// <summary>
/// Process a <see cref="DifficultyHitObject"/> and update current strain values accordingly.
/// </summary>

View File

@ -0,0 +1,55 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Beatmaps;
using osu.Game.Screens.Select;
using osu.Game.Screens.Select.Filter;
namespace osu.Game.Rulesets.Filter
{
/// <summary>
/// Allows for extending the beatmap filtering capabilities of song select (as implemented in <see cref="FilterCriteria"/>)
/// with ruleset-specific criteria.
/// </summary>
public interface IRulesetFilterCriteria
{
/// <summary>
/// Checks whether the supplied <paramref name="beatmap"/> satisfies ruleset-specific custom criteria,
/// in addition to the ones mandated by song select.
/// </summary>
/// <param name="beatmap">The beatmap to test the criteria against.</param>
/// <returns>
/// <c>true</c> if the beatmap matches the ruleset-specific custom filtering criteria,
/// <c>false</c> otherwise.
/// </returns>
bool Matches(BeatmapInfo beatmap);
/// <summary>
/// Attempts to parse a single custom keyword criterion, given by the user via the song select search box.
/// The format of the criterion is:
/// <code>
/// {key}{op}{value}
/// </code>
/// </summary>
/// <remarks>
/// <para>
/// For adding optional string criteria, <see cref="FilterCriteria.OptionalTextFilter"/> can be used for matching,
/// along with <see cref="FilterQueryParser.TryUpdateCriteriaText"/> for parsing.
/// </para>
/// <para>
/// For adding numerical-type range criteria, <see cref="FilterCriteria.OptionalRange{T}"/> can be used for matching,
/// along with <see cref="FilterQueryParser.TryUpdateCriteriaRange{T}(ref osu.Game.Screens.Select.FilterCriteria.OptionalRange{T},osu.Game.Screens.Select.Filter.Operator,string,FilterQueryParser.TryParseFunction{T})"/>
/// and <see cref="float"/>- and <see cref="double"/>-typed overloads for parsing.
/// </para>
/// </remarks>
/// <param name="key">The key (name) of the criterion.</param>
/// <param name="op">The operator in the criterion.</param>
/// <param name="value">The value of the criterion.</param>
/// <returns>
/// <c>true</c> if the keyword criterion is valid, <c>false</c> if it has been ignored.
/// Valid criteria are stripped from <see cref="FilterCriteria.SearchText"/>,
/// while ignored criteria are included in <see cref="FilterCriteria.SearchText"/>.
/// </returns>
bool TryParseCustomKeywordCriteria(string key, Operator op, string value);
}
}

View File

@ -150,17 +150,13 @@ namespace osu.Game.Rulesets.Judgements
}
if (JudgementBody.Drawable is IAnimatableJudgement animatable)
{
var drawableAnimation = (Drawable)animatable;
animatable.PlayAnimation();
// a derived version of DrawableJudgement may be proposing a lifetime.
// if not adjusted (or the skinned portion requires greater bounds than calculated) use the skinned source's lifetime.
double lastTransformTime = drawableAnimation.LatestTransformEndTime;
if (LifetimeEnd == double.MaxValue || lastTransformTime > LifetimeEnd)
LifetimeEnd = lastTransformTime;
}
// a derived version of DrawableJudgement may be proposing a lifetime.
// if not adjusted (or the skinned portion requires greater bounds than calculated) use the skinned source's lifetime.
double lastTransformTime = JudgementBody.Drawable.LatestTransformEndTime;
if (LifetimeEnd == double.MaxValue || lastTransformTime > LifetimeEnd)
LifetimeEnd = lastTransformTime;
}
}

View File

@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Mods
public bool RestartOnFail => false;
public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail) };
public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail) };
public override bool HasImplementation => GetType().GenericTypeArguments.Length == 0;

View File

@ -0,0 +1,25 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mods
{
public abstract class ModFailCondition : Mod, IApplicableToHealthProcessor, IApplicableFailOverride
{
public override Type[] IncompatibleMods => new[] { typeof(ModNoFail), typeof(ModRelax), typeof(ModAutoplay) };
public virtual bool PerformFail() => true;
public virtual bool RestartOnFail => true;
public void ApplyToHealthProcessor(HealthProcessor healthProcessor)
{
healthProcessor.FailConditions += FailCondition;
}
protected abstract bool FailCondition(HealthProcessor healthProcessor, JudgementResult result);
}
}

View File

@ -16,6 +16,6 @@ namespace osu.Game.Rulesets.Mods
public override string Description => "You can't fail, no matter what.";
public override double ScoreMultiplier => 0.5;
public override bool Ranked => true;
public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModAutoplay) };
public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModFailCondition), typeof(ModAutoplay) };
}
}

View File

@ -1,6 +1,8 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osu.Game.Rulesets.Judgements;
@ -8,13 +10,18 @@ using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mods
{
public abstract class ModPerfect : ModSuddenDeath
public abstract class ModPerfect : ModFailCondition
{
public override string Name => "Perfect";
public override string Acronym => "PF";
public override IconUsage? Icon => OsuIcon.ModPerfect;
public override ModType Type => ModType.DifficultyIncrease;
public override bool Ranked => true;
public override double ScoreMultiplier => 1;
public override string Description => "SS or quit.";
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModSuddenDeath)).ToArray();
protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result)
=> result.Type.AffectsAccuracy()
&& result.Type != result.Judgement.MaxResult;

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Bindables;
@ -24,6 +25,8 @@ namespace osu.Game.Rulesets.Mods
public double ApplyToRate(double time, double rate) => rate * SpeedChange.Value;
public override Type[] IncompatibleMods => new[] { typeof(ModTimeRamp) };
public override string SettingDescription => SpeedChange.IsDefault ? string.Empty : $"{SpeedChange.Value:N2}x";
}
}

View File

@ -14,6 +14,6 @@ namespace osu.Game.Rulesets.Mods
public override IconUsage? Icon => OsuIcon.ModRelax;
public override ModType Type => ModType.Automation;
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModNoFail), typeof(ModSuddenDeath) };
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModNoFail), typeof(ModFailCondition) };
}
}

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osu.Game.Rulesets.Judgements;
@ -9,7 +10,7 @@ using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mods
{
public abstract class ModSuddenDeath : Mod, IApplicableToHealthProcessor, IApplicableFailOverride
public abstract class ModSuddenDeath : ModFailCondition
{
public override string Name => "Sudden Death";
public override string Acronym => "SD";
@ -18,18 +19,10 @@ namespace osu.Game.Rulesets.Mods
public override string Description => "Miss and fail.";
public override double ScoreMultiplier => 1;
public override bool Ranked => true;
public override Type[] IncompatibleMods => new[] { typeof(ModNoFail), typeof(ModRelax), typeof(ModAutoplay) };
public bool PerformFail() => true;
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModPerfect)).ToArray();
public bool RestartOnFail => true;
public void ApplyToHealthProcessor(HealthProcessor healthProcessor)
{
healthProcessor.FailConditions += FailCondition;
}
protected virtual bool FailCondition(HealthProcessor healthProcessor, JudgementResult result)
protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result)
=> result.Type.AffectsCombo()
&& !result.IsHit;
}

View File

@ -30,6 +30,8 @@ namespace osu.Game.Rulesets.Mods
[SettingSource("Adjust pitch", "Should pitch be adjusted with speed")]
public abstract BindableBool AdjustPitch { get; }
public override Type[] IncompatibleMods => new[] { typeof(ModRateAdjust) };
public override string SettingDescription => $"{InitialRate.Value:N2}x to {FinalRate.Value:N2}x";
private double finalRateTime;

View File

@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Mods
[SettingSource("Initial rate", "The starting speed of the track")]
public override BindableNumber<double> InitialRate { get; } = new BindableDouble
{
MinValue = 1,
MinValue = 0.51,
MaxValue = 2,
Default = 1,
Value = 1,
@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Mods
public override BindableNumber<double> FinalRate { get; } = new BindableDouble
{
MinValue = 0.5,
MaxValue = 0.99,
MaxValue = 1.99,
Default = 0.75,
Value = 0.75,
Precision = 0.01,
@ -45,5 +45,20 @@ namespace osu.Game.Rulesets.Mods
};
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModWindUp)).ToArray();
public ModWindDown()
{
InitialRate.BindValueChanged(val =>
{
if (val.NewValue <= FinalRate.Value)
FinalRate.Value = val.NewValue - FinalRate.Precision;
});
FinalRate.BindValueChanged(val =>
{
if (val.NewValue >= InitialRate.Value)
InitialRate.Value = val.NewValue + InitialRate.Precision;
});
}
}
}

View File

@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mods
public override BindableNumber<double> InitialRate { get; } = new BindableDouble
{
MinValue = 0.5,
MaxValue = 1,
MaxValue = 1.99,
Default = 1,
Value = 1,
Precision = 0.01,
@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mods
[SettingSource("Final rate", "The speed increase to ramp towards")]
public override BindableNumber<double> FinalRate { get; } = new BindableDouble
{
MinValue = 1.01,
MinValue = 0.51,
MaxValue = 2,
Default = 1.5,
Value = 1.5,
@ -45,5 +45,20 @@ namespace osu.Game.Rulesets.Mods
};
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModWindDown)).ToArray();
public ModWindUp()
{
InitialRate.BindValueChanged(val =>
{
if (val.NewValue >= FinalRate.Value)
FinalRate.Value = val.NewValue + FinalRate.Precision;
});
FinalRate.BindValueChanged(val =>
{
if (val.NewValue <= InitialRate.Value)
InitialRate.Value = val.NewValue - InitialRate.Precision;
});
}
}
}

View File

@ -10,6 +10,7 @@ using osu.Game.Beatmaps.Formats;
using osu.Game.Audio;
using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Utils;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Skinning;
@ -54,7 +55,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
int comboOffset = (int)(type & LegacyHitObjectType.ComboOffset) >> 4;
type &= ~LegacyHitObjectType.ComboOffset;
bool combo = type.HasFlag(LegacyHitObjectType.NewCombo);
bool combo = type.HasFlagFast(LegacyHitObjectType.NewCombo);
type &= ~LegacyHitObjectType.NewCombo;
var soundType = (LegacyHitSoundType)Parsing.ParseInt(split[4]);
@ -62,14 +63,14 @@ namespace osu.Game.Rulesets.Objects.Legacy
HitObject result = null;
if (type.HasFlag(LegacyHitObjectType.Circle))
if (type.HasFlagFast(LegacyHitObjectType.Circle))
{
result = CreateHit(pos, combo, comboOffset);
if (split.Length > 5)
readCustomSampleBanks(split[5], bankInfo);
}
else if (type.HasFlag(LegacyHitObjectType.Slider))
else if (type.HasFlagFast(LegacyHitObjectType.Slider))
{
double? length = null;
@ -141,7 +142,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
result = CreateSlider(pos, combo, comboOffset, convertPathString(split[5], pos), length, repeatCount, nodeSamples);
}
else if (type.HasFlag(LegacyHitObjectType.Spinner))
else if (type.HasFlagFast(LegacyHitObjectType.Spinner))
{
double duration = Math.Max(0, Parsing.ParseDouble(split[5]) + Offset - startTime);
@ -150,7 +151,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
if (split.Length > 6)
readCustomSampleBanks(split[6], bankInfo);
}
else if (type.HasFlag(LegacyHitObjectType.Hold))
else if (type.HasFlagFast(LegacyHitObjectType.Hold))
{
// Note: Hold is generated by BMS converts
@ -436,16 +437,16 @@ namespace osu.Game.Rulesets.Objects.Legacy
new LegacyHitSampleInfo(HitSampleInfo.HIT_NORMAL, bankInfo.Normal, bankInfo.Volume, bankInfo.CustomSampleBank,
// if the sound type doesn't have the Normal flag set, attach it anyway as a layered sample.
// None also counts as a normal non-layered sample: https://osu.ppy.sh/help/wiki/osu!_File_Formats/Osu_(file_format)#hitsounds
type != LegacyHitSoundType.None && !type.HasFlag(LegacyHitSoundType.Normal))
type != LegacyHitSoundType.None && !type.HasFlagFast(LegacyHitSoundType.Normal))
};
if (type.HasFlag(LegacyHitSoundType.Finish))
if (type.HasFlagFast(LegacyHitSoundType.Finish))
soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_FINISH, bankInfo.Add, bankInfo.Volume, bankInfo.CustomSampleBank));
if (type.HasFlag(LegacyHitSoundType.Whistle))
if (type.HasFlagFast(LegacyHitSoundType.Whistle))
soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_WHISTLE, bankInfo.Add, bankInfo.Volume, bankInfo.CustomSampleBank));
if (type.HasFlag(LegacyHitSoundType.Clap))
if (type.HasFlagFast(LegacyHitSoundType.Clap))
soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_CLAP, bankInfo.Add, bankInfo.Volume, bankInfo.CustomSampleBank));
return soundTypes;

View File

@ -26,6 +26,7 @@ using JetBrains.Annotations;
using osu.Framework.Extensions;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Testing;
using osu.Game.Rulesets.Filter;
using osu.Game.Screens.Ranking.Statistics;
namespace osu.Game.Rulesets
@ -306,5 +307,11 @@ namespace osu.Game.Rulesets
/// <param name="result">The result type to get the name for.</param>
/// <returns>The display name.</returns>
public virtual string GetDisplayNameForHitResult(HitResult result) => result.GetDescription();
/// <summary>
/// Creates ruleset-specific beatmap filter criteria to be used on the song select screen.
/// </summary>
[CanBeNull]
public virtual IRulesetFilterCriteria CreateRulesetFilterCriteria() => null;
}
}

View File

@ -63,6 +63,7 @@ namespace osu.Game.Rulesets.UI
~DrawableRulesetDependencies()
{
// required to potentially clean up sample store from audio hierarchy.
Dispose(false);
}
@ -116,6 +117,10 @@ namespace osu.Game.Rulesets.UI
public void RemoveAllAdjustments(AdjustableProperty type) => throw new NotSupportedException();
public void BindAdjustments(IAggregateAudioAdjustment component) => throw new NotImplementedException();
public void UnbindAdjustments(IAggregateAudioAdjustment component) => throw new NotImplementedException();
public BindableNumber<double> Volume => throw new NotSupportedException();
public BindableNumber<double> Balance => throw new NotSupportedException();
@ -124,8 +129,6 @@ namespace osu.Game.Rulesets.UI
public BindableNumber<double> Tempo => throw new NotSupportedException();
public IBindable<double> GetAggregate(AdjustableProperty type) => throw new NotSupportedException();
public IBindable<double> AggregateVolume => throw new NotSupportedException();
public IBindable<double> AggregateBalance => throw new NotSupportedException();

View File

@ -16,7 +16,6 @@ using osu.Game.Configuration;
using osu.Game.Input.Bindings;
using osu.Game.Input.Handlers;
using osu.Game.Screens.Play;
using osuTK.Input;
using static osu.Game.Input.Handlers.ReplayInputHandler;
namespace osu.Game.Rulesets.UI
@ -109,9 +108,9 @@ namespace osu.Game.Rulesets.UI
{
switch (e)
{
case MouseDownEvent mouseDown when mouseDown.Button == MouseButton.Left || mouseDown.Button == MouseButton.Right:
case MouseDownEvent _:
if (mouseDisabled.Value)
return false;
return true; // importantly, block upwards propagation so global bindings also don't fire.
break;