1
0
mirror of https://github.com/ppy/osu.git synced 2026-06-02 00:20:40 +08:00

Compare commits

..

4 Commits

101 changed files with 192 additions and 239 deletions
+1 -1
View File
@@ -10,7 +10,7 @@
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Framework.Android" Version="2026.521.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2026.527.0" />
</ItemGroup>
<PropertyGroup>
<!-- Fody does not handle Android build well, and warns when unchanged.
@@ -4,6 +4,7 @@
using System.Collections.Generic;
using BenchmarkDotNet.Attributes;
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
@@ -42,7 +43,7 @@ namespace osu.Game.Benchmarks
public override void SetUp()
{
base.SetUp();
calculator = new OsuRuleset().CreateScoreMultiplierCalculator(new ScoreMultiplierContext());
calculator = new OsuRuleset().CreateScoreMultiplierCalculator(new ScoreMultiplierContext(new BeatmapDifficulty()));
}
[Benchmark]
@@ -13,8 +13,6 @@ namespace osu.Game.Rulesets.Catch.Mods
{
public partial class CatchModFlashlight : ModFlashlight<CatchHitObject>
{
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
public override BindableFloat SizeMultiplier { get; } = new BindableFloat(1)
{
MinValue = 0.5f,
@@ -16,7 +16,6 @@ namespace osu.Game.Rulesets.Catch.Mods
public override string Name => "Floating Fruits";
public override string Acronym => "FF";
public override LocalisableString Description => "The fruits are... floating?";
public override double ScoreMultiplier => 1;
public override IconUsage? Icon => OsuIcon.ModFloatingFruits;
public void ApplyToDrawableRuleset(DrawableRuleset<CatchHitObject> drawableRuleset)
@@ -10,8 +10,6 @@ namespace osu.Game.Rulesets.Catch.Mods
{
public class CatchModHardRock : ModHardRock, IApplicableToBeatmapProcessor
{
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
public void ApplyToBeatmapProcessor(IBeatmapProcessor beatmapProcessor)
{
var catchProcessor = (CatchBeatmapProcessor)beatmapProcessor;
@@ -16,7 +16,6 @@ namespace osu.Game.Rulesets.Catch.Mods
public class CatchModHidden : ModHidden, IApplicableToDrawableRuleset<CatchHitObject>
{
public override LocalisableString Description => @"Play with fading fruits.";
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1;
private const double fade_out_offset_multiplier = 0.6;
private const double fade_out_duration_multiplier = 0.44;
@@ -23,7 +23,6 @@ namespace osu.Game.Rulesets.Catch.Mods
public override string Acronym => "MF";
public override LocalisableString Description => "Dashing by default, slow down!";
public override ModType Type => ModType.Fun;
public override double ScoreMultiplier => 1;
public override IconUsage? Icon => OsuIcon.ModMovingFast;
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModRelax) };
@@ -4,6 +4,7 @@
using System;
using NUnit.Framework;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Mods;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
@@ -212,7 +213,7 @@ namespace osu.Game.Rulesets.Mania.Tests
[TestCaseSource(nameof(key_mod_multiplier_test_cases))]
public void TestKeyModMultiplierCompatibility(DateTimeOffset endDate, string clientVersion, double expectedMultiplier)
{
var calculator = Ruleset.CreateScoreMultiplierCalculator(new ScoreMultiplierContext(new ScoreInfo
var calculator = Ruleset.CreateScoreMultiplierCalculator(new ScoreMultiplierContext(new BeatmapDifficulty(), new ScoreInfo
{
Date = endDate,
ClientVersion = clientVersion
@@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods
Mod = doubleTime,
PassCondition = () => Player.ScoreProcessor.JudgedHits > 0
&& Player.ScoreProcessor.Accuracy.Value == 1
&& Player.ScoreProcessor.TotalScore.Value == (long)(1_000_000 * new ManiaScoreMultiplierCalculator(new ScoreMultiplierContext()).CalculateFor([doubleTime])),
&& Player.ScoreProcessor.TotalScore.Value == (long)(1_000_000 * new ManiaScoreMultiplierCalculator(new ScoreMultiplierContext(new BeatmapDifficulty())).CalculateFor([doubleTime])),
Autoplay = false,
CreateBeatmap = () => new Beatmap
{
@@ -14,7 +14,6 @@ namespace osu.Game.Rulesets.Mania.Mods
public override string Acronym => Name;
public abstract int KeyCount { get; }
public override ModType Type => ModType.Conversion;
public override double ScoreMultiplier => 0.9;
public override bool Ranked => UsesDefaultConfiguration;
public void ApplyToBeatmapConverter(IBeatmapConverter beatmapConverter)
@@ -18,8 +18,6 @@ namespace osu.Game.Rulesets.Mania.Mods
public override string Acronym => "CS";
public override double ScoreMultiplier => 0.9;
public override LocalisableString Description => "No more tricky speed changes!";
public override IconUsage? Icon => OsuIcon.ModConstantSpeed;
@@ -20,8 +20,6 @@ namespace osu.Game.Rulesets.Mania.Mods
public override LocalisableString Description => @"Decrease the playfield's viewing area.";
public override double ScoreMultiplier => 1;
protected override CoverExpandDirection ExpandDirection => Direction.Value;
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[]
@@ -7,9 +7,5 @@ namespace osu.Game.Rulesets.Mania.Mods
{
public class ManiaModDoubleTime : ModDoubleTime, IManiaRateAdjustmentMod
{
// For now, all rate-increasing mods should be given a 1x multiplier in mania because it doesn't always
// make the map harder and is more of a personal preference.
// In the future, we can consider adjusting this by experimenting with not applying the hitwindow leniency.
public override double ScoreMultiplier => 1;
}
}
@@ -17,7 +17,6 @@ namespace osu.Game.Rulesets.Mania.Mods
public override LocalisableString Description => @"Double the stages, double the fun!";
public override IconUsage? Icon => OsuIcon.ModDualStages;
public override ModType Type => ModType.Conversion;
public override double ScoreMultiplier => 1;
private bool isForCurrentRuleset;
@@ -16,7 +16,6 @@ namespace osu.Game.Rulesets.Mania.Mods
public override string Acronym => "FI";
public override IconUsage? Icon => OsuIcon.ModFadeIn;
public override LocalisableString Description => @"Keys appear out of nowhere!";
public override double ScoreMultiplier => 1;
public override bool ValidForFreestyleAsRequiredMod => false;
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[]
@@ -13,7 +13,6 @@ namespace osu.Game.Rulesets.Mania.Mods
{
public partial class ManiaModFlashlight : ModFlashlight<ManiaHitObject>
{
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(ModHidden) };
public override BindableFloat SizeMultiplier { get; } = new BindableFloat(1)
@@ -10,7 +10,6 @@ namespace osu.Game.Rulesets.Mania.Mods
{
public class ManiaModHardRock : ModHardRock, IApplicableToHitObject
{
public override double ScoreMultiplier => 1;
public override bool Ranked => false;
public const double HIT_WINDOW_DIFFICULTY_MULTIPLIER = 1.4;
@@ -30,7 +30,6 @@ namespace osu.Game.Rulesets.Mania.Mods
private const float coverage_increase_per_combo = 0.5f;
public override LocalisableString Description => @"Keys fade out before you hit them!";
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[]
{
@@ -21,8 +21,6 @@ namespace osu.Game.Rulesets.Mania.Mods
public override string Acronym => "HO";
public override double ScoreMultiplier => 0.9;
public override LocalisableString Description => @"Replaces all hold notes with normal notes.";
public override IconUsage? Icon => OsuIcon.ModHoldOff;
@@ -20,7 +20,6 @@ namespace osu.Game.Rulesets.Mania.Mods
public override string Name => "Invert";
public override string Acronym => "IN";
public override double ScoreMultiplier => 1;
public override LocalisableString Description => "Hold the keys. To the beat.";
@@ -8,9 +8,5 @@ namespace osu.Game.Rulesets.Mania.Mods
{
public class ManiaModNightcore : ModNightcore<ManiaHitObject>, IManiaRateAdjustmentMod
{
// For now, all rate-increasing mods should be given a 1x multiplier in mania because it doesn't always
// make the map any harder and is more of a personal preference.
// In the future, we can consider adjusting this by experimenting with not applying the hitwindow leniency.
public override double ScoreMultiplier => 1;
}
}
@@ -26,8 +26,6 @@ namespace osu.Game.Rulesets.Mania.Mods
public override LocalisableString Description => "No more timing the end of hold notes.";
public override double ScoreMultiplier => 0.9;
public override IconUsage? Icon => OsuIcon.ModNoRelease;
public override ModType Type => ModType.DifficultyReduction;
@@ -21,7 +21,6 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public abstract partial class InputBlockingMod : Mod, IApplicableToDrawableRuleset<OsuHitObject>, IUpdatableByPlayfield
{
public override double ScoreMultiplier => 1.0;
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModRelax), typeof(OsuModCinema) };
public override ModType Type => ModType.Conversion;
@@ -19,7 +19,6 @@ namespace osu.Game.Rulesets.Osu.Mods
public override string Name => "Approach Different";
public override string Acronym => "AD";
public override LocalisableString Description => "Never trust the approach circles...";
public override double ScoreMultiplier => 1;
public override IconUsage? Icon => OsuIcon.ModApproachDifferent;
public override Type[] IncompatibleMods => new[] { typeof(IHidesApproachCircles), typeof(OsuModFreezeFrame) };
@@ -23,7 +23,6 @@ namespace osu.Game.Rulesets.Osu.Mods
public override IconUsage? Icon => OsuIcon.ModAutopilot;
public override ModType Type => ModType.Automation;
public override LocalisableString Description => @"Automatic cursor movement - just follow the rhythm.";
public override double ScoreMultiplier => 0.1;
public override Type[] IncompatibleMods => new[]
{
@@ -30,7 +30,6 @@ namespace osu.Game.Rulesets.Osu.Mods
public override IconUsage? Icon => OsuIcon.ModBlinds;
public override ModType Type => ModType.DifficultyIncrease;
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
public override Type[] IncompatibleMods => new[] { typeof(OsuModFlashlight) };
public override bool Ranked => true;
@@ -26,7 +26,6 @@ namespace osu.Game.Rulesets.Osu.Mods
public override IconUsage? Icon => OsuIcon.ModBloom;
public override ModType Type => ModType.Fun;
public override LocalisableString Description => "The cursor blooms into.. a larger cursor!";
public override double ScoreMultiplier => 1;
protected const float MIN_SIZE = 1;
protected const float TRANSITION_DURATION = 100;
public override Type[] IncompatibleMods => new[] { typeof(OsuModFlashlight), typeof(OsuModNoScope), typeof(ModTouchDevice) };
@@ -34,8 +34,6 @@ namespace osu.Game.Rulesets.Osu.Mods
public override LocalisableString Description => "Don't let their popping distract you!";
public override double ScoreMultiplier => 1;
public override IconUsage? Icon => OsuIcon.ModBubbles;
public override ModType Type => ModType.Fun;
@@ -25,7 +25,6 @@ namespace osu.Game.Rulesets.Osu.Mods
public override IconUsage? Icon => OsuIcon.ModDepth;
public override ModType Type => ModType.Fun;
public override LocalisableString Description => "3D. Almost.";
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModMagnetised), typeof(OsuModRepel), typeof(OsuModFreezeFrame), typeof(ModWithVisibilityAdjustment) }).ToArray();
private static readonly Vector3 camera_position = new Vector3(OsuPlayfield.BASE_SIZE.X * 0.5f, OsuPlayfield.BASE_SIZE.Y * 0.5f, -200);
@@ -19,7 +19,6 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public partial class OsuModFlashlight : ModFlashlight<OsuHitObject>, IApplicableToDrawableHitObject
{
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModBloom), typeof(OsuModBlinds) }).ToArray();
private const double default_follow_delay = 120;
@@ -23,8 +23,6 @@ namespace osu.Game.Rulesets.Osu.Mods
public override IconUsage? Icon => OsuIcon.ModFreezeFrame;
public override double ScoreMultiplier => 1;
public override LocalisableString Description => "Burn the notes into your memory.";
/// <remarks>
@@ -13,8 +13,6 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public class OsuModHardRock : ModHardRock, IApplicableToHitObject
{
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1;
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModMirror)).ToArray();
public void ApplyToHitObject(HitObject hitObject)
@@ -24,7 +24,6 @@ namespace osu.Game.Rulesets.Osu.Mods
public Bindable<bool> OnlyFadeApproachCircles { get; } = new BindableBool();
public override LocalisableString Description => @"Play with no approach circles and fading circles/sliders.";
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1;
public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn), typeof(OsuModDepth), typeof(OsuModFreezeFrame) };
@@ -27,7 +27,6 @@ namespace osu.Game.Rulesets.Osu.Mods
public override IconUsage? Icon => OsuIcon.ModMagnetised;
public override ModType Type => ModType.Fun;
public override LocalisableString Description => "No need to chase the circles your cursor is a magnet!";
public override double ScoreMultiplier => 0.5;
public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModRelax), typeof(OsuModRepel), typeof(OsuModBubbles), typeof(OsuModDepth) };
[SettingSource("Attraction strength", "How strong the pull is.", 0)]
@@ -19,8 +19,6 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public override ModType Type => ModType.Fun;
public override double ScoreMultiplier => 1;
[SettingSource("Starting Size", "The initial size multiplier applied to all objects.")]
public abstract BindableNumber<float> StartScale { get; }
@@ -28,7 +28,6 @@ namespace osu.Game.Rulesets.Osu.Mods
public override IconUsage? Icon => OsuIcon.ModRepel;
public override ModType Type => ModType.Fun;
public override LocalisableString Description => "Hit objects run away!";
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModMagnetised), typeof(OsuModBubbles), typeof(OsuModDepth) };
[SettingSource("Repulsion strength", "How strong the repulsion is.", 0)]
@@ -21,7 +21,6 @@ namespace osu.Game.Rulesets.Osu.Mods
public override IconUsage? Icon => OsuIcon.ModSpinIn;
public override ModType Type => ModType.Fun;
public override LocalisableString Description => "Circles spin in. No approach circles.";
public override double ScoreMultiplier => 1;
// todo: this mod needs to be incompatible with "hidden" due to forcing the circle to remain opaque,
// further implementation will be required for supporting that.
@@ -20,7 +20,6 @@ namespace osu.Game.Rulesets.Osu.Mods
public override IconUsage? Icon => OsuIcon.ModSpunOut;
public override ModType Type => ModType.Automation;
public override LocalisableString Description => @"Spinners will be automatically completed.";
public override double ScoreMultiplier => 0.9;
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(OsuModAutopilot), typeof(OsuModTargetPractice) };
public override bool Ranked => UsesDefaultConfiguration;
@@ -29,7 +29,6 @@ namespace osu.Game.Rulesets.Osu.Mods
public override IconUsage? Icon => OsuIcon.ModStrictTracking;
public override ModType Type => ModType.DifficultyIncrease;
public override LocalisableString Description => @"Once you start a slider, follow precisely or get a miss.";
public override double ScoreMultiplier => 1.0;
public override Type[] IncompatibleMods => new[] { typeof(ModClassic), typeof(OsuModTargetPractice) };
public void ApplyToDrawableHitObject(DrawableHitObject drawable)
@@ -39,7 +39,6 @@ namespace osu.Game.Rulesets.Osu.Mods
public override ModType Type => ModType.Conversion;
public override IconUsage? Icon => OsuIcon.ModTargetPractice;
public override LocalisableString Description => @"Practice keeping up with the beat of the song.";
public override double ScoreMultiplier => 0.1;
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[]
{
@@ -23,7 +23,6 @@ namespace osu.Game.Rulesets.Osu.Mods
public override IconUsage? Icon => OsuIcon.ModTraceable;
public override ModType Type => ModType.DifficultyIncrease;
public override LocalisableString Description => "Put your faith in the approach circles...";
public override double ScoreMultiplier => 1;
public override bool Ranked => true;
public override Type[] IncompatibleMods => new[] { typeof(IHidesApproachCircles), typeof(OsuModDepth) };
@@ -22,7 +22,6 @@ namespace osu.Game.Rulesets.Osu.Mods
public override IconUsage? Icon => OsuIcon.ModTransform;
public override ModType Type => ModType.Fun;
public override LocalisableString Description => "Everything rotates. EVERYTHING.";
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModWiggle), typeof(OsuModMagnetised), typeof(OsuModRepel), typeof(OsuModFreezeFrame), typeof(OsuModDepth) }).ToArray();
private float theta;
@@ -23,7 +23,6 @@ namespace osu.Game.Rulesets.Osu.Mods
public override IconUsage? Icon => OsuIcon.ModWiggle;
public override ModType Type => ModType.Fun;
public override LocalisableString Description => "They just won't stay still...";
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform), typeof(OsuModMagnetised), typeof(OsuModRepel), typeof(OsuModDepth) };
private const int wiggle_duration = 100; // (ms) Higher = fewer wiggles
@@ -16,7 +16,6 @@ namespace osu.Game.Rulesets.Taiko.Mods
{
public override string Name => "Constant Speed";
public override string Acronym => "CS";
public override double ScoreMultiplier => 0.9;
public override LocalisableString Description => "No more tricky speed changes!";
public override IconUsage? Icon => OsuIcon.ModConstantSpeed;
public override ModType Type => ModType.Conversion;
@@ -14,8 +14,6 @@ namespace osu.Game.Rulesets.Taiko.Mods
{
public partial class TaikoModFlashlight : ModFlashlight<TaikoHitObject>
{
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
public override BindableFloat SizeMultiplier { get; } = new BindableFloat(1)
{
MinValue = 0.5f,
@@ -9,8 +9,6 @@ namespace osu.Game.Rulesets.Taiko.Mods
{
public class TaikoModHardRock : ModHardRock
{
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1;
/// <summary>
/// Multiplier factor added to the scrolling speed.
/// </summary>
@@ -17,7 +17,6 @@ namespace osu.Game.Rulesets.Taiko.Mods
public class TaikoModHidden : ModHidden, IApplicableToDrawableRuleset<TaikoHitObject>
{
public override LocalisableString Description => @"Beats fade out before you hit them!";
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1;
/// <summary>
/// How far away from the hit target should hitobjects start to fade out.
@@ -21,7 +21,6 @@ namespace osu.Game.Rulesets.Taiko.Mods
{
public override string Name => "Simplified Rhythm";
public override string Acronym => "SR";
public override double ScoreMultiplier => 0.6;
public override LocalisableString Description => "Simplify tricky rhythms!";
public override IconUsage? Icon => OsuIcon.ModSimplifiedRhythm;
public override ModType Type => ModType.DifficultyReduction;
@@ -30,7 +30,6 @@ namespace osu.Game.Rulesets.Taiko.Mods
public override LocalisableString Description => @"One key for dons, one key for kats.";
public override bool Ranked => true;
public override double ScoreMultiplier => 1.0;
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModRelax), typeof(TaikoModCinema) };
public override ModType Type => ModType.Conversion;
@@ -20,7 +20,6 @@ namespace osu.Game.Rulesets.Taiko.Mods
public override LocalisableString Description => @"Dons become kats, kats become dons";
public override IconUsage? Icon => OsuIcon.ModSwap;
public override ModType Type => ModType.Conversion;
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModRandom)).ToArray();
public override bool Ranked => true;
-1
View File
@@ -91,7 +91,6 @@ namespace osu.Game.Tests.Mods
{
public override string Name => "Non-matching setting type mod";
public override LocalisableString Description => "Description";
public override double ScoreMultiplier => 1;
public override ModType Type => ModType.Conversion;
[SettingSource("Test setting")]
-3
View File
@@ -421,7 +421,6 @@ namespace osu.Game.Tests.Mods
public override string Name => string.Empty;
public override LocalisableString Description => string.Empty;
public override string Acronym => string.Empty;
public override double ScoreMultiplier => 1;
public override bool HasImplementation => true;
public override bool ValidForMultiplayer => false;
public override bool ValidForMultiplayerAsFreeMod => false;
@@ -432,7 +431,6 @@ namespace osu.Game.Tests.Mods
public override string Name => string.Empty;
public override LocalisableString Description => string.Empty;
public override string Acronym => string.Empty;
public override double ScoreMultiplier => 1;
public override bool HasImplementation => true;
public override bool ValidForMultiplayerAsFreeMod => false;
}
@@ -441,7 +439,6 @@ namespace osu.Game.Tests.Mods
{
public override string Name => string.Empty;
public override LocalisableString Description => string.Empty;
public override double ScoreMultiplier => 1;
public override string Acronym => string.Empty;
public override bool HasImplementation => true;
public override bool ValidForFreestyleAsRequiredMod => false;
@@ -59,8 +59,6 @@ namespace osu.Game.Tests.Mods
public abstract class TestModCustomisable : Mod, IApplicableMod
{
public override double ScoreMultiplier => 1.0;
public override LocalisableString Description => "This is a customisable test mod.";
public override ModType Type => ModType.Conversion;
@@ -161,7 +161,6 @@ namespace osu.Game.Tests.NonVisual
public override string Name => nameof(ModA);
public override string Acronym => nameof(ModA);
public override LocalisableString Description => string.Empty;
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(ModIncompatibleWithA), typeof(ModIncompatibleWithAAndB) };
}
@@ -171,7 +170,6 @@ namespace osu.Game.Tests.NonVisual
public override string Name => nameof(ModB);
public override LocalisableString Description => string.Empty;
public override string Acronym => nameof(ModB);
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(ModIncompatibleWithAAndB) };
}
@@ -181,7 +179,6 @@ namespace osu.Game.Tests.NonVisual
public override string Name => nameof(ModC);
public override string Acronym => nameof(ModC);
public override LocalisableString Description => string.Empty;
public override double ScoreMultiplier => 1;
}
private class ModIncompatibleWithA : Mod
@@ -189,7 +186,6 @@ namespace osu.Game.Tests.NonVisual
public override string Name => $"Incompatible With {nameof(ModA)}";
public override string Acronym => $"Incompatible With {nameof(ModA)}";
public override LocalisableString Description => string.Empty;
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(ModA) };
}
@@ -208,7 +204,6 @@ namespace osu.Game.Tests.NonVisual
public override string Name => $"Incompatible With {nameof(ModA)} and {nameof(ModB)}";
public override string Acronym => $"Incompatible With {nameof(ModA)} and {nameof(ModB)}";
public override LocalisableString Description => string.Empty;
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(ModA), typeof(ModB) };
}
@@ -185,7 +185,6 @@ namespace osu.Game.Tests.Online
public override string Name => "Test Mod";
public override string Acronym => "TM";
public override LocalisableString Description => "This is a test mod.";
public override double ScoreMultiplier => 1;
[SettingSource("Test")]
public BindableNumber<double> TestSetting { get; } = new BindableDouble
@@ -202,7 +201,6 @@ namespace osu.Game.Tests.Online
public override string Name => "Test Mod";
public override string Acronym => "TMTR";
public override LocalisableString Description => "This is a test mod.";
public override double ScoreMultiplier => 1;
[SettingSource("Initial rate", "The starting speed of the track")]
public override BindableNumber<double> InitialRate { get; } = new BindableDouble(1.5)
@@ -104,7 +104,6 @@ namespace osu.Game.Tests.Online
public override string Name => "Test Mod";
public override string Acronym => "TM";
public override LocalisableString Description => "This is a test mod.";
public override double ScoreMultiplier => 1;
[SettingSource("Test")]
public BindableNumber<double> TestSetting { get; } = new BindableDouble
@@ -121,7 +120,6 @@ namespace osu.Game.Tests.Online
public override string Name => "Test Mod";
public override string Acronym => "TMTR";
public override LocalisableString Description => "This is a test mod.";
public override double ScoreMultiplier => 1;
[SettingSource("Initial rate", "The starting speed of the track")]
public override BindableNumber<double> InitialRate { get; } = new BindableDouble(1.5)
@@ -148,7 +146,6 @@ namespace osu.Game.Tests.Online
public override string Name => "Test Mod";
public override string Acronym => "TM";
public override LocalisableString Description => "This is a test mod.";
public override double ScoreMultiplier => 1;
[SettingSource("Test")]
public Bindable<TestEnum> TestSetting { get; } = new Bindable<TestEnum>();
@@ -222,12 +222,10 @@ namespace osu.Game.Tests.Resources
private class TestModHardRock : ModHardRock
{
public override double ScoreMultiplier => 1;
}
private class TestModDoubleTime : ModDoubleTime
{
public override double ScoreMultiplier => 1;
}
}
}
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
@@ -13,7 +14,7 @@ namespace osu.Game.Tests.Rulesets.Scoring
[Test]
public void TestFlatMultiplier()
{
var calculator = new TestScoreMultiplierCalculator(new ScoreMultiplierContext());
var calculator = new TestScoreMultiplierCalculator(new ScoreMultiplierContext(new BeatmapDifficulty()));
double multiplier = calculator.CalculateFor([new OsuModEasy()]);
@@ -23,7 +24,7 @@ namespace osu.Game.Tests.Rulesets.Scoring
[Test]
public void TestSettingDependentMultiplier()
{
var calculator = new TestScoreMultiplierCalculator(new ScoreMultiplierContext());
var calculator = new TestScoreMultiplierCalculator(new ScoreMultiplierContext(new BeatmapDifficulty()));
double multiplier = calculator.CalculateFor([new OsuModDaycore { SpeedChange = { Value = 0.6 } }]);
@@ -31,7 +32,7 @@ namespace osu.Game.Tests.Rulesets.Scoring
}
[Test]
public void TestContextDependentMultiplier()
public void TestScoreDependentMultiplier()
{
TestScoreMultiplierCalculator calculator;
@@ -39,20 +40,39 @@ namespace osu.Game.Tests.Rulesets.Scoring
Assert.Multiple(() =>
{
calculator = new TestScoreMultiplierCalculator(new ScoreMultiplierContext());
calculator = new TestScoreMultiplierCalculator(new ScoreMultiplierContext(new BeatmapDifficulty()));
multiplier = calculator.CalculateFor([new OsuModHardRock()]);
Assert.That(multiplier, Is.EqualTo(1.4));
calculator = new TestScoreMultiplierCalculator(new ScoreMultiplierContext(new ScoreInfo { ClientVersion = "2024.123.0" }));
calculator = new TestScoreMultiplierCalculator(new ScoreMultiplierContext(new BeatmapDifficulty(), new ScoreInfo { ClientVersion = "2024.123.0" }));
multiplier = calculator.CalculateFor([new OsuModHardRock()]);
Assert.That(multiplier, Is.EqualTo(1.2));
});
}
[Test]
public void TestDifficultyDependentMultiplier()
{
TestScoreMultiplierCalculator calculator;
double multiplier;
Assert.Multiple(() =>
{
calculator = new TestScoreMultiplierCalculator(new ScoreMultiplierContext(new BeatmapDifficulty()));
multiplier = calculator.CalculateFor([new OsuModEasy()]);
Assert.That(multiplier, Is.EqualTo(0.15));
calculator = new TestScoreMultiplierCalculator(new ScoreMultiplierContext(new BeatmapDifficulty { ApproachRate = 0 }));
multiplier = calculator.CalculateFor([new OsuModEasy()]);
Assert.That(multiplier, Is.EqualTo(0.1));
});
}
[Test]
public void TestCombinationMultiplier()
{
var calculator = new TestScoreMultiplierCalculator(new ScoreMultiplierContext());
var calculator = new TestScoreMultiplierCalculator(new ScoreMultiplierContext(new BeatmapDifficulty()));
double multiplier = calculator.CalculateFor([new OsuModEasy(), new OsuModDaycore()]);
@@ -62,7 +82,7 @@ namespace osu.Game.Tests.Rulesets.Scoring
[Test]
public void TestCombinationAndFlatMultipliers()
{
var calculator = new TestScoreMultiplierCalculator(new ScoreMultiplierContext());
var calculator = new TestScoreMultiplierCalculator(new ScoreMultiplierContext(new BeatmapDifficulty()));
double multiplier = calculator.CalculateFor([new OsuModDaycore(), new OsuModHardRock(), new OsuModEasy()]);
@@ -74,7 +94,7 @@ namespace osu.Game.Tests.Rulesets.Scoring
public TestScoreMultiplierCalculator(ScoreMultiplierContext context)
: base(context)
{
Single<OsuModEasy>(hasMultiplier: 0.15);
Single<OsuModEasy>(hasMultiplier: context.BeatmapDifficultyWithoutMods.ApproachRate == 0 ? 0.1 : 0.15);
Single<OsuModDaycore>(hasMultiplier: daycore => (1 + daycore.SpeedChange.Value) / 4);
Single<OsuModHardRock>(hasMultiplier: _ => context.Score?.ClientVersion == "2024.123.0" ? 1.2 : 1.4);
Combination<OsuModEasy, OsuModDaycore>(hasMultiplier: (_, _) => 0.003);
@@ -481,6 +481,29 @@ namespace osu.Game.Tests.Rulesets.Scoring
Assert.That(scoreProcessor.HighestCombo.Value, Is.Zero);
}
[Test]
public void TestScoreMultiplier()
{
Mod[] mods = new Mod[] { new OsuModHardRock() };
scoreProcessor = new TestScoreProcessor();
scoreProcessor.Mods.Value = mods;
var workingBeatmap = new TestWorkingBeatmap(beatmap);
var playableBeatmap = workingBeatmap.GetPlayableBeatmap(new OsuRuleset().RulesetInfo, mods);
scoreProcessor.ApplyBeatmap(playableBeatmap);
var judgementResult = new JudgementResult(beatmap.HitObjects.Single(), new OsuJudgement())
{
Type = HitResult.Great,
};
scoreProcessor.ApplyResult(judgementResult);
Assert.That(scoreProcessor.MaximumTotalScore, Is.EqualTo(1_000_000 * 1.1).Within(0.5d));
Assert.That(scoreProcessor.GetDisplayScore(ScoringMode.Standardised), Is.EqualTo(1_000_000 * 1.1).Within(0.5d));
}
private class TestJudgement : Judgement
{
public override HitResult MaxResult { get; }
@@ -537,9 +560,20 @@ namespace osu.Game.Tests.Rulesets.Scoring
public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => throw new NotImplementedException();
public override ScoreMultiplierCalculator CreateScoreMultiplierCalculator(ScoreMultiplierContext context) => new TestScoreMultiplierCalculator(context);
public override string Description => string.Empty;
public override string ShortName => string.Empty;
}
private class TestScoreMultiplierCalculator : ScoreMultiplierCalculator
{
public TestScoreMultiplierCalculator(ScoreMultiplierContext context)
: base(context)
{
Single<OsuModHardRock>(hasMultiplier: context.BeatmapDifficultyWithoutMods.CircleSize == 4 ? 1.1 : 1.0);
}
}
}
}
}
@@ -8,6 +8,7 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Graphics.Sprites;
using osu.Game.Overlays;
using osu.Game.Overlays.Mods;
@@ -120,7 +121,7 @@ namespace osu.Game.Tests.Visual.SongSelect
private void assertModsMultiplier(Ruleset ruleset, IEnumerable<Mod> mods)
{
var scoreMultiplierCalculator = ruleset.CreateScoreMultiplierCalculator(new ScoreMultiplierContext());
var scoreMultiplierCalculator = ruleset.CreateScoreMultiplierCalculator(new ScoreMultiplierContext(new BeatmapDifficulty()));
double multiplier = scoreMultiplierCalculator.CalculateFor(mods);
string expectedValue = ModUtils.FormatScoreMultiplier(multiplier).ToString();
@@ -244,7 +244,6 @@ namespace osu.Game.Tests.Visual.UserInterface
public override string Name => string.Empty;
public override LocalisableString Description => string.Empty;
public override double ScoreMultiplier => 1.0;
public override string Acronym => "Test";
}
}
@@ -15,6 +15,7 @@ using osu.Framework.Localisation;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays;
@@ -124,7 +125,7 @@ namespace osu.Game.Tests.Visual.UserInterface
AddUntilStep("two panels active", () => modSelectOverlay.ChildrenOfType<ModPanel>().Count(panel => panel.Active.Value) == 2);
AddAssert("mod multiplier correct", () =>
{
double multiplier = new OsuScoreMultiplierCalculator(new ScoreMultiplierContext()).CalculateFor(SelectedMods.Value);
double multiplier = new OsuScoreMultiplierCalculator(new ScoreMultiplierContext(new BeatmapDifficulty())).CalculateFor(SelectedMods.Value);
return Precision.AlmostEquals(multiplier, this.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value);
});
assertCustomisationToggleState(disabled: false, active: false);
@@ -139,7 +140,7 @@ namespace osu.Game.Tests.Visual.UserInterface
AddUntilStep("two panels active", () => modSelectOverlay.ChildrenOfType<ModPanel>().Count(panel => panel.Active.Value) == 2);
AddAssert("mod multiplier correct", () =>
{
double multiplier = new OsuScoreMultiplierCalculator(new ScoreMultiplierContext()).CalculateFor(SelectedMods.Value);
double multiplier = new OsuScoreMultiplierCalculator(new ScoreMultiplierContext(new BeatmapDifficulty())).CalculateFor(SelectedMods.Value);
return Precision.AlmostEquals(multiplier, this.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value);
});
assertCustomisationToggleState(disabled: false, active: false);
@@ -1146,7 +1147,6 @@ namespace osu.Game.Tests.Visual.UserInterface
public override string Name => "Unimplemented mod";
public override string Acronym => "UM";
public override LocalisableString Description => "A mod that is not implemented.";
public override double ScoreMultiplier => 1;
public override ModType Type => ModType.Conversion;
}
@@ -188,7 +188,10 @@ namespace osu.Game.Database
}
var ruleset = score.Ruleset.CreateInstance();
var scoreMultiplierCalculator = ruleset.CreateScoreMultiplierCalculator(new ScoreMultiplierContext(score));
Debug.Assert(score.BeatmapInfo != null);
var scoreMultiplierCalculator = ruleset.CreateScoreMultiplierCalculator(new ScoreMultiplierContext(score.BeatmapInfo.Difficulty, score));
double modMultiplier = scoreMultiplierCalculator.CalculateFor(score.Mods);
return (long)Math.Round((1000000 * (accuracyPortion * accuracyScore + (1 - accuracyPortion) * comboScore) + bonusScore) * modMultiplier);
@@ -351,7 +354,7 @@ namespace osu.Game.Database
long maximumLegacyBaseScore = maximumLegacyAccuracyScore + maximumLegacyComboScore;
double bonusProportion = Math.Max(0, ((long)score.LegacyTotalScore - maximumLegacyBaseScore) * maximumLegacyBonusRatio);
var modMultiplierCalculator = ruleset.CreateScoreMultiplierCalculator(new ScoreMultiplierContext());
var modMultiplierCalculator = ruleset.CreateScoreMultiplierCalculator(new ScoreMultiplierContext(difficulty));
double modMultiplier = modMultiplierCalculator.CalculateFor(score.Mods);
long convertedTotalScoreWithoutMods;
@@ -102,6 +102,8 @@ namespace osu.Game.Overlays.Mods
{
if (beatmapAttributesDisplay != null)
beatmapAttributesDisplay.BeatmapInfo.Value = b.NewValue?.BeatmapInfo;
updateInformation();
}, true);
Ruleset.BindValueChanged(_ => updateInformation());
@@ -122,9 +124,11 @@ namespace osu.Game.Overlays.Mods
private void updateInformation()
{
if (rankingInformationDisplay != null)
WorkingBeatmap? workingBeatmap = Beatmap.Value;
if (rankingInformationDisplay != null && workingBeatmap != null)
{
var scoreMultiplierCalculator = Ruleset.Value?.CreateInstance().CreateScoreMultiplierCalculator(new ScoreMultiplierContext());
var scoreMultiplierCalculator = Ruleset.Value?.CreateInstance().CreateScoreMultiplierCalculator(new ScoreMultiplierContext(workingBeatmap.BeatmapInfo.Difficulty));
double multiplier = scoreMultiplierCalculator?.CalculateFor(ActiveMods.Value) ?? 1;
rankingInformationDisplay.ModMultiplier.Value = multiplier;
+2 -1
View File
@@ -84,7 +84,8 @@ namespace osu.Game.Rulesets.Mods
/// The score multiplier of this mod.
/// </summary>
[JsonIgnore]
public abstract double ScoreMultiplier { get; }
[Obsolete("This property is no longer used to calculate the score multiplier. Use `Ruleset.CreateScoreMultiplierCalculator()` instead.")]
public virtual double ScoreMultiplier => 1;
/// <summary>
/// Returns true if this mod is implemented (and playable).
@@ -30,8 +30,6 @@ namespace osu.Game.Rulesets.Mods
public override ModType Type => ModType.DifficultyIncrease;
public override double ScoreMultiplier => 1.0;
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(ModEasyWithExtraLives), typeof(ModPerfect) }).ToArray();
public override bool RequiresConfiguration => false;
@@ -33,8 +33,6 @@ namespace osu.Game.Rulesets.Mods
public override ModType Type => ModType.Fun;
public override double ScoreMultiplier => 0.5;
public sealed override bool ValidForMultiplayer => false;
public sealed override bool ValidForMultiplayerAsFreeMod => false;
-1
View File
@@ -18,7 +18,6 @@ namespace osu.Game.Rulesets.Mods
public override IconUsage? Icon => OsuIcon.ModAutoplay;
public override ModType Type => ModType.Automation;
public override LocalisableString Description => "Watch a perfect automated play through the song.";
public override double ScoreMultiplier => 1;
public sealed override bool UserPlayable => false;
public sealed override bool ValidForMultiplayer => false;
-1
View File
@@ -40,7 +40,6 @@ namespace osu.Game.Rulesets.Mods
public override string Acronym => "BR";
public override IconUsage? Icon => OsuIcon.ModBarrelRoll;
public override LocalisableString Description => "The whole playfield is on a wheel!";
public override double ScoreMultiplier => 1;
public override IEnumerable<(LocalisableString setting, LocalisableString value)> SettingDescription
{
-2
View File
@@ -13,8 +13,6 @@ namespace osu.Game.Rulesets.Mods
public override string Acronym => "CL";
public override double ScoreMultiplier => 0.96;
public override IconUsage? Icon => OsuIcon.ModClassic;
public override LocalisableString Description => "Feeling nostalgic?";
+1 -6
View File
@@ -30,13 +30,10 @@ namespace osu.Game.Rulesets.Mods
private readonly BindableNumber<double> tempoAdjust = new BindableDouble(1);
private readonly BindableNumber<double> freqAdjust = new BindableDouble(1);
private readonly RateAdjustModHelper rateAdjustHelper;
protected ModDaycore()
{
rateAdjustHelper = new RateAdjustModHelper(SpeedChange);
// intentionally not deferring the speed change handling to `RateAdjustModHelper`
// intentionally not using `RateAdjustModHelper`
// as the expected result of operation is not the same (daycore should preserve constant pitch).
SpeedChange.BindValueChanged(val =>
{
@@ -50,7 +47,5 @@ namespace osu.Game.Rulesets.Mods
track.AddAdjustment(AdjustableProperty.Frequency, freqAdjust);
track.AddAdjustment(AdjustableProperty.Tempo, tempoAdjust);
}
public override double ScoreMultiplier => rateAdjustHelper.ScoreMultiplier;
}
}
@@ -25,8 +25,6 @@ namespace osu.Game.Rulesets.Mods
public override IconUsage? Icon => OsuIcon.ModDifficultyAdjust;
public override double ScoreMultiplier => 0.5;
public override bool RequiresConfiguration => true;
public override bool ValidForFreestyleAsRequiredMod => true;
-2
View File
@@ -56,7 +56,5 @@ namespace osu.Game.Rulesets.Mods
{
rateAdjustHelper.ApplyToTrack(track);
}
public override double ScoreMultiplier => rateAdjustHelper.ScoreMultiplier;
}
}
-1
View File
@@ -14,7 +14,6 @@ namespace osu.Game.Rulesets.Mods
public override string Acronym => "EZ";
public override IconUsage? Icon => OsuIcon.ModEasy;
public override ModType Type => ModType.DifficultyReduction;
public override double ScoreMultiplier => 0.5;
public override Type[] IncompatibleMods => new[] { typeof(ModHardRock), typeof(ModDifficultyAdjust) };
public override bool Ranked => UsesDefaultConfiguration;
public override bool ValidForFreestyleAsRequiredMod => true;
-2
View File
@@ -56,7 +56,5 @@ namespace osu.Game.Rulesets.Mods
{
rateAdjustHelper.ApplyToTrack(track);
}
public override double ScoreMultiplier => rateAdjustHelper.ScoreMultiplier;
}
}
-1
View File
@@ -12,6 +12,5 @@ namespace osu.Game.Rulesets.Mods
public override string Acronym => "MR";
public override IconUsage? Icon => OsuIcon.ModMirror;
public override ModType Type => ModType.Conversion;
public override double ScoreMultiplier => 1;
}
}
-1
View File
@@ -25,7 +25,6 @@ namespace osu.Game.Rulesets.Mods
public override IconUsage? Icon => OsuIcon.ModMuted;
public override LocalisableString Description => "Can you still feel the rhythm without music?";
public override ModType Type => ModType.Fun;
public override double ScoreMultiplier => 1;
public override bool Ranked => true;
public override bool ValidForFreestyleAsRequiredMod => true;
}
+1 -7
View File
@@ -41,13 +41,9 @@ namespace osu.Game.Rulesets.Mods
private readonly BindableNumber<double> tempoAdjust = new BindableDouble(1);
private readonly BindableNumber<double> freqAdjust = new BindableDouble(1);
private readonly RateAdjustModHelper rateAdjustHelper;
protected ModNightcore()
{
rateAdjustHelper = new RateAdjustModHelper(SpeedChange);
// intentionally not deferring the speed change handling to `RateAdjustModHelper`
// intentionally not using `RateAdjustModHelper`
// as the expected result of operation is not the same (nightcore should preserve constant pitch).
SpeedChange.BindValueChanged(val =>
{
@@ -61,8 +57,6 @@ namespace osu.Game.Rulesets.Mods
track.AddAdjustment(AdjustableProperty.Frequency, freqAdjust);
track.AddAdjustment(AdjustableProperty.Tempo, tempoAdjust);
}
public override double ScoreMultiplier => rateAdjustHelper.ScoreMultiplier;
}
public abstract partial class ModNightcore<TObject> : ModNightcore, IApplicableToDrawableRuleset<TObject>
-1
View File
@@ -18,7 +18,6 @@ namespace osu.Game.Rulesets.Mods
public override IconUsage? Icon => OsuIcon.ModNoFail;
public override ModType Type => ModType.DifficultyReduction;
public override LocalisableString Description => "You can't fail, no matter what.";
public override double ScoreMultiplier => 0.5;
public override Type[] IncompatibleMods => new[] { typeof(ModFailCondition), typeof(ModCinema) };
public override bool Ranked => UsesDefaultConfiguration;
public override bool ValidForFreestyleAsRequiredMod => true;
-1
View File
@@ -15,7 +15,6 @@ namespace osu.Game.Rulesets.Mods
public override string Name => "No Mod";
public override string Acronym => "NM";
public override LocalisableString Description => "No mods applied.";
public override double ScoreMultiplier => 1;
public override IconUsage? Icon => OsuIcon.ModNoMod;
public override ModType Type => ModType.System;
}
-1
View File
@@ -22,7 +22,6 @@ namespace osu.Game.Rulesets.Mods
public override string Acronym => "NS";
public override ModType Type => ModType.Fun;
public override IconUsage? Icon => OsuIcon.ModNoScope;
public override double ScoreMultiplier => 1;
public override bool Ranked => true;
/// <summary>
-1
View File
@@ -17,7 +17,6 @@ namespace osu.Game.Rulesets.Mods
public override string Acronym => "PF";
public override IconUsage? Icon => OsuIcon.ModPerfect;
public override ModType Type => ModType.DifficultyIncrease;
public override double ScoreMultiplier => 1;
public override LocalisableString Description => "SS or quit.";
public override bool Ranked => true;
public override bool ValidForFreestyleAsRequiredMod => true;
-1
View File
@@ -15,7 +15,6 @@ namespace osu.Game.Rulesets.Mods
public override string Acronym => "RD";
public override ModType Type => ModType.Conversion;
public override IconUsage? Icon => OsuIcon.ModRandom;
public override double ScoreMultiplier => 1;
[SettingSource("Seed", "Use a custom seed instead of a random one", SettingControlType = typeof(SettingsNumberBox))]
public Bindable<int?> Seed { get; } = new Bindable<int?>();
-1
View File
@@ -13,7 +13,6 @@ namespace osu.Game.Rulesets.Mods
public override string Acronym => "RX";
public override IconUsage? Icon => OsuIcon.ModRelax;
public override ModType Type => ModType.Automation;
public override double ScoreMultiplier => 0.1;
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay) };
}
}
-1
View File
@@ -18,7 +18,6 @@ namespace osu.Game.Rulesets.Mods
public override IconUsage? Icon => OsuIcon.ModScoreV2;
public override ModType Type => ModType.System;
public override LocalisableString Description => "Score set on earlier osu! versions with the V2 scoring algorithm active.";
public override double ScoreMultiplier => 1;
public override bool UserPlayable => false;
public override bool ValidForMultiplayer => false;
public override bool ValidForMultiplayerAsFreeMod => false;
-1
View File
@@ -18,7 +18,6 @@ namespace osu.Game.Rulesets.Mods
public override IconUsage? Icon => OsuIcon.ModSuddenDeath;
public override ModType Type => ModType.DifficultyIncrease;
public override LocalisableString Description => "Miss and fail.";
public override double ScoreMultiplier => 1;
public override bool Ranked => true;
public override bool ValidForFreestyleAsRequiredMod => true;
-1
View File
@@ -15,7 +15,6 @@ namespace osu.Game.Rulesets.Mods
public override string Name => "Synesthesia";
public override string Acronym => "SY";
public override LocalisableString Description => "Colours hit objects based on the rhythm.";
public override double ScoreMultiplier => 0.8;
public override IconUsage? Icon => OsuIcon.ModSynesthesia;
public override ModType Type => ModType.Fun;
}
-2
View File
@@ -21,8 +21,6 @@ namespace osu.Game.Rulesets.Mods
/// </summary>
public const double FINAL_RATE_PROGRESS = 0.75f;
public override double ScoreMultiplier => 0.5;
[SettingSource("Initial rate", "The starting speed of the track", SettingControlType = typeof(MultiplierSettingsSlider))]
public abstract BindableNumber<double> InitialRate { get; }
-1
View File
@@ -14,7 +14,6 @@ namespace osu.Game.Rulesets.Mods
public sealed override string Acronym => "TD";
public sealed override IconUsage? Icon => OsuIcon.ModTouchDevice;
public sealed override LocalisableString Description => "Automatically applied to plays on devices with a touchscreen.";
public sealed override double ScoreMultiplier => 1;
public sealed override ModType Type => ModType.System;
public sealed override bool ValidForMultiplayer => false;
public sealed override bool ValidForMultiplayerAsFreeMod => false;
-1
View File
@@ -12,7 +12,6 @@ namespace osu.Game.Rulesets.Mods
public override string Name => string.Empty;
public override string Acronym => string.Empty;
public override LocalisableString Description => string.Empty;
public override double ScoreMultiplier => 0;
public Mod[] Mods { get; }
@@ -18,26 +18,6 @@ namespace osu.Game.Rulesets.Mods
private BindableBool? adjustPitch;
/// <summary>
/// The score multiplier for the current <see cref="SpeedChange"/>.
/// </summary>
public double ScoreMultiplier
{
get
{
// Round to the nearest multiple of 0.1.
double value = (int)(SpeedChange.Value * 10) / 10.0;
// Offset back to 0.
value -= 1;
if (SpeedChange.Value >= 1)
return 1 + value / 5;
else
return 0.6 + value;
}
}
/// <summary>
/// Construct a new <see cref="RateAdjustModHelper"/>.
/// </summary>
-1
View File
@@ -15,7 +15,6 @@ namespace osu.Game.Rulesets.Mods
public override string Name => $"Unknown mod ({OriginalAcronym})";
public override string Acronym => $"{OriginalAcronym}??";
public override LocalisableString Description => "This mod could not be resolved by the game.";
public override double ScoreMultiplier => 0;
public override bool UserPlayable => false;
public override bool ValidForMultiplayer => false;
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods;
using osu.Game.Scoring;
@@ -95,6 +96,12 @@ namespace osu.Game.Rulesets.Scoring
/// </summary>
public class ScoreMultiplierContext
{
/// <summary>
/// The difficulty info for the beatmap that the multipliers are calculated for.
/// This must be the difficulty info for the beatmap BEFORE any mod application.
/// </summary>
public IBeatmapDifficultyInfo BeatmapDifficultyWithoutMods { get; }
/// <summary>
/// The score that the multipliers are calculated for.
/// Mostly relevant and present in backwards compatibility scenarios.
@@ -104,24 +111,19 @@ namespace osu.Game.Rulesets.Scoring
/// <summary>
/// Constructs a new instance.
/// Use this in situations wherein the current valid score multipliers are needed.
/// </summary>
public ScoreMultiplierContext()
: this(null)
{
}
/// <summary>
/// Constructs a new instance.
/// Use this in backwards compatibility scenarios when dealing with a specific <paramref name="score"/>.
/// </summary>
/// <param name="beatmapDifficultyWithoutMods">
/// The difficulty info for the beatmap that the multipliers are calculated for.
/// This must be the difficulty info for the beatmap BEFORE any mod application.
/// </param>
/// <param name="score">
/// The score that the multipliers are calculated for.
/// Mostly relevant and present in backwards compatibility scenarios.
/// In usages where the current valid score multipliers are required, pass <see langword="null"/> or use a constructor that does not require this.
/// In usages where the current valid score multipliers are required, pass <see langword="null"/> or omit this parameter entirely.
/// </param>
public ScoreMultiplierContext(ScoreInfo? score)
public ScoreMultiplierContext(IBeatmapDifficultyInfo beatmapDifficultyWithoutMods, ScoreInfo? score = null)
{
BeatmapDifficultyWithoutMods = beatmapDifficultyWithoutMods;
Score = score;
}
}
+28 -3
View File
@@ -91,6 +91,11 @@ namespace osu.Game.Rulesets.Scoring
/// </summary>
public readonly Bindable<IReadOnlyList<Mod>> Mods = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
/// <summary>
/// The current beatmap.
/// </summary>
public readonly Bindable<IBeatmap?> Beatmap = new Bindable<IBeatmap?>();
/// <summary>
/// The current rank.
/// </summary>
@@ -206,16 +211,27 @@ namespace osu.Game.Rulesets.Scoring
Mods.ValueChanged += mods =>
{
var calculator = ruleset.CreateScoreMultiplierCalculator(new ScoreMultiplierContext());
scoreMultiplier = calculator.CalculateFor(mods.NewValue);
updateScoreMultiplier();
updateScore();
updateRank();
};
Beatmap.ValueChanged += beatmap =>
{
updateScoreMultiplier();
};
}
public override void ApplyBeatmap(IBeatmap beatmap)
{
// NOTE: The ordering of operations here is significant.
// `Beatmap.Value` must be set before `base.ApplyBeatmap()` because changes to `Beatmap.Value`
// trigger recalculation of `scoreMultiplier`,
// and `base.ApplyBeatmap()` calls `SimulateAutoplay()` then `Reset(storeResults: true)`.
// failing to calculate the correct score multiplier *before* autoplay simulation would result in
// storing the incorrect value of `MaximumTotalScore`.
Beatmap.Value = beatmap;
base.ApplyBeatmap(beatmap);
beatmapApplied = true;
}
@@ -399,6 +415,15 @@ namespace osu.Game.Rulesets.Scoring
rank.Value = newRank;
}
private void updateScoreMultiplier()
{
if (Beatmap.Value == null)
return;
var calculator = Ruleset.CreateScoreMultiplierCalculator(new ScoreMultiplierContext(Beatmap.Value.BeatmapInfo.Difficulty));
scoreMultiplier = calculator.CalculateFor(Mods.Value);
}
protected virtual double ComputeTotalScore(double comboProgress, double accuracyProgress, double bonusPortion)
{
return 500000 * Accuracy.Value * comboProgress +
@@ -258,8 +258,10 @@ namespace osu.Game.Scoring.Legacy
public static void PopulateTotalScoreWithoutMods(ScoreInfo score)
{
Debug.Assert(score.BeatmapInfo != null);
var ruleset = score.Ruleset.CreateInstance();
var scoreMultiplierCalculator = ruleset.CreateScoreMultiplierCalculator(new ScoreMultiplierContext(score));
var scoreMultiplierCalculator = ruleset.CreateScoreMultiplierCalculator(new ScoreMultiplierContext(score.BeatmapInfo.Difficulty, score));
double modMultiplier = scoreMultiplierCalculator.CalculateFor(score.Mods);
score.TotalScoreWithoutMods = (long)Math.Round(score.TotalScore / modMultiplier);
@@ -60,8 +60,8 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Queue
client.MatchmakingQueueJoined += onMatchmakingQueueJoined;
client.MatchmakingQueueLeft += onMatchmakingQueueLeft;
client.MatchmakingRoomInvited += onMatchmakingRoomInvited;
client.MatchmakingDuelIssued += onMatchmakingDuelIssued;
client.MatchmakingRoomReady += onMatchmakingRoomReady;
client.MatchmakingDuelIssued += onMatchmakingDuelIssued;
}
/// <summary>
@@ -133,9 +133,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Queue
return;
isBackgrounded = true;
if (CurrentState.Value == ScreenQueue.MatchmakingScreenState.Queueing)
postNotification();
postNotification();
}
/// <summary>
@@ -147,7 +145,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Queue
return;
isBackgrounded = false;
closeNotifications();
closeNotification();
}
private void onRoomUpdated() => Scheduler.Add(() =>
@@ -160,37 +158,34 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Queue
{
CurrentState.Value = ScreenQueue.MatchmakingScreenState.Queueing;
if (isBackgrounded)
{
closeNotifications();
postNotification();
}
postNotification();
});
private void onMatchmakingQueueLeft() => Scheduler.Add(() =>
{
if (CurrentState.Value != ScreenQueue.MatchmakingScreenState.InRoom)
CurrentState.Value = ScreenQueue.MatchmakingScreenState.Idle;
CurrentState.Value = ScreenQueue.MatchmakingScreenState.Idle;
closeNotifications();
closeNotification();
});
private void onMatchmakingRoomInvited(MatchmakingRoomInvitationParams invitation) => Scheduler.Add(() =>
{
if (isBackgrounded)
postNotification();
CurrentState.Value = ScreenQueue.MatchmakingScreenState.PendingAccept;
postNotification();
backgroundNotification?.Complete(invitation);
backgroundNotification = null;
});
private void onMatchmakingRoomReady(long roomId, string password) => Scheduler.Add(() =>
{
CurrentState.Value = ScreenQueue.MatchmakingScreenState.InRoom;
client.JoinRoom(new Room { RoomID = roomId }, password).FireAndForget();
});
private void onMatchmakingDuelIssued(MatchmakingDuelIssuedParams duel)
{
handleDuelRequestAsync().FireAndForget();
async Task handleDuelRequestAsync()
Task.Run(async () =>
{
APIUser? user = await users.GetUserAsync(duel.UserId).ConfigureAwait(false);
@@ -198,34 +193,35 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Queue
return;
Scheduler.Add(() => notifications?.Post(new DuelNotification(this, user, duel)));
}
}).FireAndForget();
}
private void onMatchmakingRoomReady(long roomId, string password) => Scheduler.Add(() =>
{
client.JoinRoom(new Room { RoomID = roomId }, password)
.FireAndForget(() => Scheduler.Add(() =>
{
CurrentState.Value = ScreenQueue.MatchmakingScreenState.InRoom;
}));
});
private void postNotification()
{
if (backgroundNotification != null)
// Check if we can re-use an existing notification.
if (backgroundNotification?.State == ProgressNotificationState.Active || backgroundNotification?.State == ProgressNotificationState.Queued)
return;
// Existing notification could be in a post-completion state.
closeNotification();
if (!isBackgrounded)
return;
if (CurrentState.Value != ScreenQueue.MatchmakingScreenState.Queueing)
return;
notifications?.Post(backgroundNotification = new BackgroundQueueNotification(this));
}
private void closeNotifications()
private void closeNotification()
{
if (backgroundNotification != null)
{
backgroundNotification.State = ProgressNotificationState.Cancelled;
backgroundNotification.CloseAll();
backgroundNotification = null;
}
if (backgroundNotification == null)
return;
backgroundNotification.State = ProgressNotificationState.Cancelled;
backgroundNotification.CloseAll();
backgroundNotification = null;
}
protected override void Dispose(bool isDisposing)
@@ -239,6 +235,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Queue
client.MatchmakingQueueLeft -= onMatchmakingQueueLeft;
client.MatchmakingRoomInvited -= onMatchmakingRoomInvited;
client.MatchmakingRoomReady -= onMatchmakingRoomReady;
client.MatchmakingDuelIssued -= onMatchmakingDuelIssued;
}
}
@@ -290,12 +287,21 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Queue
public void Complete(MatchmakingRoomInvitationParams invitation)
{
if (State != ProgressNotificationState.Active && State != ProgressNotificationState.Queued)
return;
CompletionClickAction = () =>
{
client.MatchmakingAcceptInvitation().FireAndForget();
controller.CurrentState.Value = ScreenQueue.MatchmakingScreenState.AcceptedWaitingForRoom;
performer?.PerformFromScreen(s =>
{
client.MatchmakingAcceptInvitation().FireAndForget();
controller.CurrentState.Value = ScreenQueue.MatchmakingScreenState.AcceptedWaitingForRoom;
performer?.PerformFromScreen(s => s.Push(new ScreenIntro(invitation.Type)));
if (s is ScreenIntro || s is ScreenQueue)
return;
s.Push(new ScreenIntro(invitation.Type));
}, [typeof(ScreenIntro), typeof(ScreenQueue)]);
Close(false);
return true;
@@ -122,7 +122,7 @@ namespace osu.Game.Screens.Select
new StatisticRow(s.DisplayName.ToUpper(), s.Count.ToLocalisableString("N0"), colours.ForHitResult(s.Result)));
var ruleset = value.Ruleset.CreateInstance();
var scoreMultiplierCalculator = ruleset.CreateScoreMultiplierCalculator(new ScoreMultiplierContext());
var scoreMultiplierCalculator = ruleset.CreateScoreMultiplierCalculator(new ScoreMultiplierContext(score.BeatmapInfo!.Difficulty));
double multiplier = scoreMultiplierCalculator.CalculateFor(value.Mods);
var generalStatistics = new[]
+6 -1
View File
@@ -15,6 +15,7 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
@@ -78,6 +79,9 @@ namespace osu.Game.Screens.Select
[Resolved]
private OsuGameBase game { get; set; } = null!;
[Resolved]
private IBindable<WorkingBeatmap> beatmap { get; set; } = null!;
private IBindable<Language> currentLanguage = null!;
public FooterButtonMods(ModSelectOverlay overlay)
@@ -175,6 +179,7 @@ namespace osu.Game.Screens.Select
currentLanguage.BindValueChanged(_ => ScheduleAfterChildren(updateDisplay));
Ruleset.BindValueChanged(_ => updateDisplay());
beatmap.BindValueChanged(_ => updateDisplay());
Mods.BindValueChanged(m =>
{
modSettingChangeTracker?.Dispose();
@@ -244,7 +249,7 @@ namespace osu.Game.Screens.Select
modDisplay.FadeIn(duration, easing);
}
var scoreMultiplierCalculator = Ruleset.Value?.CreateInstance().CreateScoreMultiplierCalculator(new ScoreMultiplierContext());
var scoreMultiplierCalculator = Ruleset.Value?.CreateInstance().CreateScoreMultiplierCalculator(new ScoreMultiplierContext(beatmap.Value.BeatmapInfo.Difficulty));
double multiplier = scoreMultiplierCalculator?.CalculateFor(Mods.Value) ?? 1;
multiplierText.Text = ModUtils.FormatScoreMultiplier(multiplier);
@@ -4,6 +4,7 @@
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
@@ -26,16 +27,8 @@ namespace osu.Game.Tests.Rulesets
protected void TestModCombination(IEnumerable<Mod> mods, double expectedMultiplier)
{
Assert.Multiple(() =>
{
double multiplierViaOldAPI = 1;
foreach (var mod in mods)
multiplierViaOldAPI *= mod.ScoreMultiplier;
Assert.That(multiplierViaOldAPI, Is.EqualTo(expectedMultiplier).Within(Precision.DOUBLE_EPSILON));
var calculator = Ruleset.CreateScoreMultiplierCalculator(new ScoreMultiplierContext());
Assert.That(calculator.CalculateFor(mods), Is.EqualTo(expectedMultiplier).Within(Precision.DOUBLE_EPSILON));
});
var calculator = Ruleset.CreateScoreMultiplierCalculator(new ScoreMultiplierContext(new BeatmapDifficulty()));
Assert.That(calculator.CalculateFor(mods), Is.EqualTo(expectedMultiplier).Within(Precision.DOUBLE_EPSILON));
}
}
}
+1 -1
View File
@@ -39,7 +39,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Realm" Version="20.1.0" />
<PackageReference Include="ppy.osu.Framework" Version="2026.521.0" />
<PackageReference Include="ppy.osu.Framework" Version="2026.527.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2026.523.0" />
<PackageReference Include="Sentry" Version="6.2.0" />
<!-- Held back due to 0.34.0 failing AOT compilation on ZstdSharp.dll dependency. -->

Some files were not shown because too many files have changed in this diff Show More