2019-01-24 08:43:03 +00:00
// 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.
2018-04-13 09:19:50 +00:00
using System ;
2021-09-09 07:34:49 +00:00
using System.Collections.Concurrent ;
2018-04-13 09:19:50 +00:00
using System.Collections.Generic ;
using System.Linq ;
using osu.Framework.Graphics ;
2019-03-27 10:29:27 +00:00
using osu.Framework.Graphics.Sprites ;
2018-04-13 09:19:50 +00:00
using osu.Framework.Input.Bindings ;
2019-09-04 11:28:21 +00:00
using osu.Framework.IO.Stores ;
2018-04-13 09:19:50 +00:00
using osu.Game.Beatmaps ;
using osu.Game.Overlays.Settings ;
using osu.Game.Rulesets.Edit ;
using osu.Game.Rulesets.Mods ;
using osu.Game.Rulesets.Replays.Types ;
using osu.Game.Rulesets.UI ;
2018-04-13 13:41:35 +00:00
using osu.Game.Beatmaps.Legacy ;
2018-06-11 04:17:08 +00:00
using osu.Game.Configuration ;
using osu.Game.Rulesets.Configuration ;
2018-05-15 08:38:04 +00:00
using osu.Game.Rulesets.Difficulty ;
2019-12-17 11:08:13 +00:00
using osu.Game.Rulesets.Scoring ;
2018-11-28 07:12:57 +00:00
using osu.Game.Scoring ;
2019-08-26 03:21:49 +00:00
using osu.Game.Skinning ;
2020-01-03 11:39:15 +00:00
using osu.Game.Users ;
2020-06-02 11:32:52 +00:00
using JetBrains.Annotations ;
2020-10-07 06:34:23 +00:00
using osu.Framework.Extensions ;
2021-01-27 21:01:56 +00:00
using osu.Framework.Extensions.EnumExtensions ;
2020-09-04 11:34:26 +00:00
using osu.Framework.Testing ;
2021-05-12 20:42:26 +00:00
using osu.Game.Extensions ;
2021-03-02 19:07:11 +00:00
using osu.Game.Rulesets.Filter ;
2021-08-22 14:40:17 +00:00
using osu.Game.Screens.Edit.Setup ;
2020-06-15 13:45:18 +00:00
using osu.Game.Screens.Ranking.Statistics ;
2018-04-13 09:19:50 +00:00
namespace osu.Game.Rulesets
{
2020-09-04 11:34:26 +00:00
[ExcludeFromDynamicCompile]
2018-04-13 09:19:50 +00:00
public abstract class Ruleset
{
2019-12-24 04:48:27 +00:00
public RulesetInfo RulesetInfo { get ; internal set ; }
2018-04-13 09:19:50 +00:00
2021-11-22 07:52:54 +00:00
private static readonly ConcurrentDictionary < string , IMod [ ] > mod_reference_cache = new ConcurrentDictionary < string , IMod [ ] > ( ) ;
2021-09-09 07:34:49 +00:00
/// <summary>
2021-09-10 02:09:13 +00:00
/// A queryable source containing all available mods.
/// Call <see cref="IMod.CreateInstance"/> for consumption purposes.
2021-09-09 07:34:49 +00:00
/// </summary>
2021-09-10 02:09:13 +00:00
public IEnumerable < IMod > AllMods
2021-09-09 07:34:49 +00:00
{
2021-09-10 02:09:13 +00:00
get
{
2021-11-22 12:41:09 +00:00
// Is the case for many test usages.
if ( string . IsNullOrEmpty ( ShortName ) )
return CreateAllMods ( ) ;
2021-11-22 07:52:54 +00:00
if ( ! mod_reference_cache . TryGetValue ( ShortName , out var mods ) )
mod_reference_cache [ ShortName ] = mods = CreateAllMods ( ) . Cast < IMod > ( ) . ToArray ( ) ;
2021-09-09 07:34:49 +00:00
2021-09-10 02:09:13 +00:00
return mods ;
}
2021-09-09 07:34:49 +00:00
}
2021-09-10 02:09:13 +00:00
/// <summary>
/// Returns fresh instances of all mods.
/// </summary>
/// <remarks>
/// This comes with considerable allocation overhead. If only accessing for reference purposes (ie. not changing bindables / settings)
/// use <see cref="AllMods"/> instead.
/// </remarks>
public IEnumerable < Mod > CreateAllMods ( ) = > Enum . GetValues ( typeof ( ModType ) ) . Cast < ModType > ( )
// Confine all mods of each mod type into a single IEnumerable<Mod>
. SelectMany ( GetModsFor )
// Filter out all null mods
. Where ( mod = > mod ! = null )
// Resolve MultiMods as their .Mods property
. SelectMany ( mod = > ( mod as MultiMod ) ? . Mods ? ? new [ ] { mod } ) ;
2021-09-09 07:34:49 +00:00
/// <summary>
/// Returns a fresh instance of the mod matching the specified acronym.
/// </summary>
/// <param name="acronym">The acronym to query for .</param>
2021-09-10 02:09:13 +00:00
public Mod CreateModFromAcronym ( string acronym )
2021-09-09 07:34:49 +00:00
{
2021-09-10 03:05:10 +00:00
return AllMods . FirstOrDefault ( m = > m . Acronym = = acronym ) ? . CreateInstance ( ) ;
2021-09-09 07:34:49 +00:00
}
2021-09-09 07:46:24 +00:00
/// <summary>
/// Returns a fresh instance of the mod matching the specified type.
/// </summary>
2021-09-10 02:09:13 +00:00
public T CreateMod < T > ( )
2021-09-09 07:46:24 +00:00
where T : Mod
{
2021-09-10 03:05:10 +00:00
return AllMods . FirstOrDefault ( m = > m is T ) ? . CreateInstance ( ) as T ;
2021-09-09 07:46:24 +00:00
}
2018-04-13 09:19:50 +00:00
public abstract IEnumerable < Mod > GetModsFor ( ModType type ) ;
2018-04-16 12:14:40 +00:00
/// <summary>
/// Converts mods from legacy enum values. Do not override if you're not a legacy ruleset.
/// </summary>
2020-03-24 03:06:24 +00:00
/// <param name="mods">The legacy enum which will be converted.</param>
/// <returns>An enumerable of constructed <see cref="Mod"/>s.</returns>
public virtual IEnumerable < Mod > ConvertFromLegacyMods ( LegacyMods mods ) = > Array . Empty < Mod > ( ) ;
/// <summary>
/// Converts mods to legacy enum values. Do not override if you're not a legacy ruleset.
/// </summary>
/// <param name="mods">The mods which will be converted.</param>
/// <returns>A single bitwise enumerable value representing (to the best of our ability) the mods.</returns>
public virtual LegacyMods ConvertToLegacyMods ( Mod [ ] mods )
{
var value = LegacyMods . None ;
foreach ( var mod in mods )
{
switch ( mod )
{
case ModNoFail _ :
value | = LegacyMods . NoFail ;
break ;
case ModEasy _ :
value | = LegacyMods . Easy ;
break ;
case ModHidden _ :
value | = LegacyMods . Hidden ;
break ;
case ModHardRock _ :
value | = LegacyMods . HardRock ;
break ;
2020-11-15 14:35:06 +00:00
case ModPerfect _ :
value | = LegacyMods . Perfect ;
break ;
2020-03-24 03:06:24 +00:00
case ModSuddenDeath _ :
value | = LegacyMods . SuddenDeath ;
break ;
2020-11-15 14:35:06 +00:00
case ModNightcore _ :
value | = LegacyMods . Nightcore ;
break ;
2020-03-24 03:06:24 +00:00
case ModDoubleTime _ :
value | = LegacyMods . DoubleTime ;
break ;
case ModRelax _ :
value | = LegacyMods . Relax ;
break ;
case ModHalfTime _ :
value | = LegacyMods . HalfTime ;
break ;
case ModFlashlight _ :
value | = LegacyMods . Flashlight ;
break ;
2020-11-15 14:35:06 +00:00
case ModCinema _ :
value | = LegacyMods . Cinema ;
break ;
case ModAutoplay _ :
value | = LegacyMods . Autoplay ;
break ;
2020-03-24 03:06:24 +00:00
}
}
return value ;
}
2018-04-13 13:41:35 +00:00
2020-06-02 11:32:52 +00:00
[CanBeNull]
2021-09-10 02:09:13 +00:00
public ModAutoplay GetAutoplayMod ( ) = > CreateMod < ModAutoplay > ( ) ;
2018-04-13 09:19:50 +00:00
2021-06-10 10:41:41 +00:00
public virtual ISkin CreateLegacySkinProvider ( [ NotNull ] ISkin skin , IBeatmap beatmap ) = > null ;
2019-08-26 03:21:49 +00:00
2019-12-18 05:49:09 +00:00
protected Ruleset ( )
2018-04-13 09:19:50 +00:00
{
2019-12-24 07:02:35 +00:00
RulesetInfo = new RulesetInfo
{
Name = Description ,
ShortName = ShortName ,
2021-11-22 05:26:24 +00:00
OnlineID = ( this as ILegacyRuleset ) ? . LegacyID ? ? - 1 ,
2021-05-12 20:42:26 +00:00
InstantiationInfo = GetType ( ) . GetInvariantInstantiationInfo ( ) ,
2020-01-03 11:39:15 +00:00
Available = true ,
2019-12-24 07:02:35 +00:00
} ;
2018-04-13 09:19:50 +00:00
}
/// <summary>
/// Attempt to create a hit renderer for a beatmap
/// </summary>
/// <param name="beatmap">The beatmap to create the hit renderer for.</param>
2019-04-25 08:36:17 +00:00
/// <param name="mods">The <see cref="Mod"/>s to apply.</param>
2018-04-13 09:19:50 +00:00
/// <exception cref="BeatmapInvalidForRulesetException">Unable to successfully load the beatmap to be usable with this ruleset.</exception>
2019-12-12 06:58:11 +00:00
public abstract DrawableRuleset CreateDrawableRulesetWith ( IBeatmap beatmap , IReadOnlyList < Mod > mods = null ) ;
2018-04-13 09:19:50 +00:00
2019-12-17 11:08:13 +00:00
/// <summary>
2019-12-24 08:01:17 +00:00
/// Creates a <see cref="ScoreProcessor"/> for this <see cref="Ruleset"/>.
2019-12-17 11:08:13 +00:00
/// </summary>
/// <returns>The score processor.</returns>
2019-12-24 08:01:17 +00:00
public virtual ScoreProcessor CreateScoreProcessor ( ) = > new ScoreProcessor ( ) ;
2019-12-17 11:08:13 +00:00
2019-12-19 11:03:14 +00:00
/// <summary>
2019-12-24 08:01:17 +00:00
/// Creates a <see cref="HealthProcessor"/> for this <see cref="Ruleset"/>.
2019-12-19 11:03:14 +00:00
/// </summary>
/// <returns>The health processor.</returns>
2019-12-27 07:14:49 +00:00
public virtual HealthProcessor CreateHealthProcessor ( double drainStartTime ) = > new DrainingHealthProcessor ( drainStartTime ) ;
2019-12-19 11:03:14 +00:00
2018-06-29 04:07:00 +00:00
/// <summary>
/// Creates a <see cref="IBeatmapConverter"/> to convert a <see cref="IBeatmap"/> to one that is applicable for this <see cref="Ruleset"/>.
/// </summary>
/// <param name="beatmap">The <see cref="IBeatmap"/> to be converted.</param>
/// <returns>The <see cref="IBeatmapConverter"/>.</returns>
2018-04-19 13:04:12 +00:00
public abstract IBeatmapConverter CreateBeatmapConverter ( IBeatmap beatmap ) ;
2018-06-29 04:07:00 +00:00
/// <summary>
/// Optionally creates a <see cref="IBeatmapProcessor"/> to alter a <see cref="IBeatmap"/> after it has been converted.
/// </summary>
/// <param name="beatmap">The <see cref="IBeatmap"/> to be processed.</param>
/// <returns>The <see cref="IBeatmapProcessor"/>.</returns>
2018-04-19 13:04:12 +00:00
public virtual IBeatmapProcessor CreateBeatmapProcessor ( IBeatmap beatmap ) = > null ;
2021-11-15 09:19:23 +00:00
public abstract DifficultyCalculator CreateDifficultyCalculator ( IWorkingBeatmap beatmap ) ;
2018-04-13 09:19:50 +00:00
2020-10-07 08:46:57 +00:00
/// <summary>
/// Optionally creates a <see cref="PerformanceCalculator"/> to generate performance data from the provided score.
/// </summary>
/// <param name="attributes">Difficulty attributes for the beatmap related to the provided score.</param>
/// <param name="score">The score to be processed.</param>
/// <returns>A performance calculator instance for the provided score.</returns>
[CanBeNull]
2020-10-03 14:51:22 +00:00
public virtual PerformanceCalculator CreatePerformanceCalculator ( DifficultyAttributes attributes , ScoreInfo score ) = > null ;
2020-10-07 08:46:57 +00:00
/// <summary>
/// Optionally creates a <see cref="PerformanceCalculator"/> to generate performance data from the provided score.
/// </summary>
/// <param name="beatmap">The beatmap to use as a source for generating <see cref="DifficultyAttributes"/>.</param>
/// <param name="score">The score to be processed.</param>
/// <returns>A performance calculator instance for the provided score.</returns>
[CanBeNull]
2021-11-15 09:19:23 +00:00
public PerformanceCalculator CreatePerformanceCalculator ( IWorkingBeatmap beatmap , ScoreInfo score )
2020-10-03 14:51:22 +00:00
{
var difficultyCalculator = CreateDifficultyCalculator ( beatmap ) ;
var difficultyAttributes = difficultyCalculator . Calculate ( score . Mods ) ;
return CreatePerformanceCalculator ( difficultyAttributes , score ) ;
}
2018-04-13 09:19:50 +00:00
public virtual HitObjectComposer CreateHitObjectComposer ( ) = > null ;
2021-04-13 08:40:56 +00:00
public virtual IBeatmapVerifier CreateBeatmapVerifier ( ) = > null ;
2021-04-07 12:36:43 +00:00
2019-04-02 10:55:24 +00:00
public virtual Drawable CreateIcon ( ) = > new SpriteIcon { Icon = FontAwesome . Solid . QuestionCircle } ;
2018-04-13 09:19:50 +00:00
2019-12-28 13:13:18 +00:00
public virtual IResourceStore < byte [ ] > CreateResourceStore ( ) = > new NamespacedResourceStore < byte [ ] > ( new DllResourceStore ( GetType ( ) . Assembly ) , @"Resources" ) ;
2019-09-04 11:28:21 +00:00
2018-04-13 09:19:50 +00:00
public abstract string Description { get ; }
2018-06-11 04:28:50 +00:00
public virtual RulesetSettingsSubsection CreateSettings ( ) = > null ;
2018-04-13 09:19:50 +00:00
2018-06-11 04:17:08 +00:00
/// <summary>
/// Creates the <see cref="IRulesetConfigManager"/> for this <see cref="Ruleset"/>.
/// </summary>
/// <param name="settings">The <see cref="SettingsStore"/> to store the settings.</param>
public virtual IRulesetConfigManager CreateConfig ( SettingsStore settings ) = > null ;
2018-04-13 09:19:50 +00:00
/// <summary>
/// A unique short name to reference this ruleset in online requests.
/// </summary>
public abstract string ShortName { get ; }
2020-01-03 11:39:15 +00:00
/// <summary>
2021-08-18 00:13:53 +00:00
/// The playing verb to be shown in the <see cref="UserActivity.InGame"/> activities.
2020-01-03 11:39:15 +00:00
/// </summary>
2021-08-14 14:39:12 +00:00
public virtual string PlayingVerb = > "Playing" ;
2020-01-03 11:39:15 +00:00
2018-04-13 09:19:50 +00:00
/// <summary>
/// A list of available variant ids.
/// </summary>
public virtual IEnumerable < int > AvailableVariants = > new [ ] { 0 } ;
/// <summary>
/// Get a list of default keys for the specified variant.
/// </summary>
/// <param name="variant">A variant.</param>
/// <returns>A list of valid <see cref="KeyBinding"/>s.</returns>
2019-11-28 13:41:29 +00:00
public virtual IEnumerable < KeyBinding > GetDefaultKeyBindings ( int variant = 0 ) = > Array . Empty < KeyBinding > ( ) ;
2018-04-13 09:19:50 +00:00
/// <summary>
/// Gets the name for a key binding variant. This is used for display in the settings overlay.
/// </summary>
/// <param name="variant">The variant.</param>
/// <returns>A descriptive name of the variant.</returns>
public virtual string GetVariantName ( int variant ) = > string . Empty ;
/// <summary>
/// For rulesets which support legacy (osu-stable) replay conversion, this method will create an empty replay frame
/// for conversion use.
/// </summary>
/// <returns>An empty frame for the current ruleset, or null if unsupported.</returns>
public virtual IConvertibleReplayFrame CreateConvertibleReplayFrame ( ) = > null ;
2020-06-15 13:45:18 +00:00
2020-06-19 11:53:43 +00:00
/// <summary>
/// Creates the statistics for a <see cref="ScoreInfo"/> to be displayed in the results screen.
/// </summary>
/// <param name="score">The <see cref="ScoreInfo"/> to create the statistics for. The score is guaranteed to have <see cref="ScoreInfo.HitEvents"/> populated.</param>
2020-06-22 09:38:41 +00:00
/// <param name="playableBeatmap">The <see cref="IBeatmap"/>, converted for this <see cref="Ruleset"/> with all relevant <see cref="Mod"/>s applied.</param>
2020-06-19 11:53:43 +00:00
/// <returns>The <see cref="StatisticRow"/>s to display. Each <see cref="StatisticRow"/> may contain 0 or more <see cref="StatisticItem"/>.</returns>
2020-06-19 11:31:52 +00:00
[NotNull]
2020-06-22 09:38:41 +00:00
public virtual StatisticRow [ ] CreateStatisticsForScore ( ScoreInfo score , IBeatmap playableBeatmap ) = > Array . Empty < StatisticRow > ( ) ;
2020-10-07 06:34:23 +00:00
/// <summary>
/// Get all valid <see cref="HitResult"/>s for this ruleset.
/// Generally used for results display purposes, where it can't be determined if zero-count means the user has not achieved any or the type is not used by this ruleset.
/// </summary>
/// <returns>
/// All valid <see cref="HitResult"/>s along with a display-friendly name.
/// </returns>
public IEnumerable < ( HitResult result , string displayName ) > GetHitResults ( )
{
var validResults = GetValidHitResults ( ) ;
// enumerate over ordered list to guarantee return order is stable.
2021-01-27 21:01:56 +00:00
foreach ( var result in EnumExtensions . GetValuesInOrder < HitResult > ( ) )
2020-10-07 06:34:23 +00:00
{
switch ( result )
{
// hard blocked types, should never be displayed even if the ruleset tells us to.
case HitResult . None :
case HitResult . IgnoreHit :
case HitResult . IgnoreMiss :
// display is handled as a completion count with corresponding "hit" type.
case HitResult . LargeTickMiss :
case HitResult . SmallTickMiss :
continue ;
}
if ( result = = HitResult . Miss | | validResults . Contains ( result ) )
yield return ( result , GetDisplayNameForHitResult ( result ) ) ;
}
}
/// <summary>
/// Get all valid <see cref="HitResult"/>s for this ruleset.
/// Generally used for results display purposes, where it can't be determined if zero-count means the user has not achieved any or the type is not used by this ruleset.
/// </summary>
/// <remarks>
/// <see cref="HitResult.Miss"/> is implicitly included. Special types like <see cref="HitResult.IgnoreHit"/> are ignored even when specified.
/// </remarks>
2021-01-27 21:01:56 +00:00
protected virtual IEnumerable < HitResult > GetValidHitResults ( ) = > EnumExtensions . GetValuesInOrder < HitResult > ( ) ;
2020-10-07 06:34:23 +00:00
/// <summary>
/// Get a display friendly name for the specified result type.
/// </summary>
/// <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 ( ) ;
2021-03-02 19:07:11 +00:00
/// <summary>
/// Creates ruleset-specific beatmap filter criteria to be used on the song select screen.
/// </summary>
[CanBeNull]
public virtual IRulesetFilterCriteria CreateRulesetFilterCriteria ( ) = > null ;
2021-08-22 14:40:17 +00:00
/// <summary>
/// Can be overridden to add a ruleset-specific section to the editor beatmap setup screen.
/// </summary>
[CanBeNull]
2021-09-05 04:34:23 +00:00
public virtual RulesetSetupSection CreateEditorSetupSection ( ) = > null ;
2018-04-13 09:19:50 +00:00
}
}