// 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 System.Linq; using System.Reflection; using Newtonsoft.Json; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Framework.Testing; using osu.Game.Configuration; using osu.Game.IO.Serialization; using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Mods { /// /// The base class for gameplay modifiers. /// [ExcludeFromDynamicCompile] public abstract class Mod : IMod, IJsonSerializable { /// /// The name of this mod. /// [JsonIgnore] public abstract string Name { get; } /// /// The shortened name of this mod. /// public abstract string Acronym { get; } /// /// The icon of this mod. /// [JsonIgnore] public virtual IconUsage? Icon => null; /// /// The type of this mod. /// [JsonIgnore] public virtual ModType Type => ModType.Fun; /// /// The user readable description of this mod. /// [JsonIgnore] public virtual string Description => string.Empty; /// /// The tooltip to display for this mod when used in a . /// /// /// Differs from , as the value of attributes (AR, CS, etc) changeable via the mod /// are displayed in the tooltip. /// [JsonIgnore] public string IconTooltip { get { string description = SettingDescription; return string.IsNullOrEmpty(description) ? Name : $"{Name} ({description})"; } } /// /// The description of editable settings of a mod to use in the . /// /// /// Parentheses are added to the tooltip, surrounding the value of this property. If this property is string.Empty, /// the tooltip will not have parentheses. /// public virtual string SettingDescription { get { var tooltipTexts = new List(); foreach ((SettingSourceAttribute attr, PropertyInfo property) in this.GetOrderedSettingsSourceProperties()) { var bindable = (IBindable)property.GetValue(this); if (!bindable.IsDefault) tooltipTexts.Add($"{attr.Label} {bindable}"); } return string.Join(", ", tooltipTexts.Where(s => !string.IsNullOrEmpty(s))); } } /// /// The score multiplier of this mod. /// [JsonIgnore] public abstract double ScoreMultiplier { get; } /// /// Returns true if this mod is implemented (and playable). /// [JsonIgnore] public virtual bool HasImplementation => this is IApplicableMod; /// /// Returns if this mod is ranked. /// [JsonIgnore] public virtual bool Ranked => false; /// /// Whether this mod requires configuration to apply changes to the game. /// [JsonIgnore] public virtual bool RequiresConfiguration => false; /// /// The mods this mod cannot be enabled with. /// [JsonIgnore] public virtual Type[] IncompatibleMods => Array.Empty(); /// /// Creates a copy of this initialised to a default state. /// public virtual Mod CreateCopy() { var copy = (Mod)Activator.CreateInstance(GetType()); // Copy bindable values across foreach (var (_, prop) in this.GetSettingsSourceProperties()) { var origBindable = (IBindable)prop.GetValue(this); var copyBindable = (IBindable)prop.GetValue(copy); // we only care about changes that have been made away from defaults. if (!origBindable.IsDefault) copy.CopyAdjustedSetting(copyBindable, origBindable); } return copy; } /// /// When creating copies or clones of a Mod, this method will be called to copy explicitly adjusted user settings. /// The base implementation will transfer the value via and should be called unless replaced with custom logic. /// /// The target bindable to apply the adjustment. /// The adjustment to apply. internal virtual void CopyAdjustedSetting(IBindable bindable, object value) => bindable.Parse(value); public bool Equals(IMod other) => GetType() == other?.GetType(); } }