mirror of
https://github.com/ppy/osu.git
synced 2026-06-07 06:23:39 +08:00
Compare commits
35 Commits
pp-dev
...
2026.605.0
+1
-1
@@ -10,7 +10,7 @@
|
|||||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2026.513.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2026.527.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
// 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.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;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
|
||||||
|
namespace osu.Game.Benchmarks
|
||||||
|
{
|
||||||
|
public class BenchmarkScoreMultiplierCalculator : BenchmarkTest
|
||||||
|
{
|
||||||
|
private ScoreMultiplierCalculator calculator = null!;
|
||||||
|
|
||||||
|
[Params(1, 10, 100)]
|
||||||
|
public int Times { get; set; }
|
||||||
|
|
||||||
|
public record ModTestCase(string Description, IEnumerable<Mod> Mods)
|
||||||
|
{
|
||||||
|
public override string ToString() => Description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<ModTestCase> ValuesForMods =>
|
||||||
|
[
|
||||||
|
new ModTestCase("no mods", []),
|
||||||
|
new ModTestCase("single mod", [new OsuModHardRock()]),
|
||||||
|
new ModTestCase("single mod 2", [new OsuModEasy()]),
|
||||||
|
new ModTestCase("multiple mods", [new OsuModHidden(), new OsuModHardRock(), new OsuModDoubleTime()]),
|
||||||
|
new ModTestCase("mods with adjusted settings", [
|
||||||
|
new OsuModDoubleTime { SpeedChange = { Value = 2 } },
|
||||||
|
new OsuModHidden { OnlyFadeApproachCircles = { Value = true } },
|
||||||
|
new OsuModHardRock()
|
||||||
|
]),
|
||||||
|
];
|
||||||
|
|
||||||
|
[ParamsSource(nameof(ValuesForMods))]
|
||||||
|
public ModTestCase Mods { get; set; } = null!;
|
||||||
|
|
||||||
|
public override void SetUp()
|
||||||
|
{
|
||||||
|
base.SetUp();
|
||||||
|
calculator = new OsuRuleset().CreateScoreMultiplierCalculator(new ScoreMultiplierContext(new BeatmapDifficulty()));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public double ViaCalculator()
|
||||||
|
=> viaCalculator(Times, Mods);
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ViaCalculator([Values(100)] int times, [ValueSource(nameof(ValuesForMods))] ModTestCase mods)
|
||||||
|
=> viaCalculator(times, mods);
|
||||||
|
|
||||||
|
private double viaCalculator(int times, ModTestCase mods)
|
||||||
|
{
|
||||||
|
double scoreMultiplier = 1;
|
||||||
|
|
||||||
|
for (int i = 0; i < times; ++i)
|
||||||
|
scoreMultiplier = calculator.CalculateFor(mods.Mods);
|
||||||
|
|
||||||
|
return scoreMultiplier;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,171 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Catch.Mods;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Tests.Rulesets;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Tests
|
||||||
|
{
|
||||||
|
public class CatchScoreMultiplierTest : RulesetScoreMultiplierTest
|
||||||
|
{
|
||||||
|
public CatchScoreMultiplierTest()
|
||||||
|
: base(new CatchRuleset())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly object[][] test_cases =
|
||||||
|
[
|
||||||
|
#region Difficulty Reduction
|
||||||
|
|
||||||
|
[new Mod[] { new CatchModEasy() }, 0.5],
|
||||||
|
[new Mod[] { new CatchModNoFail() }, 0.5],
|
||||||
|
|
||||||
|
[new Mod[] { new CatchModHalfTime { SpeedChange = { Value = 0.50 } } }, 0.1],
|
||||||
|
[new Mod[] { new CatchModHalfTime { SpeedChange = { Value = 0.55 } } }, 0.1],
|
||||||
|
[new Mod[] { new CatchModHalfTime { SpeedChange = { Value = 0.60 } } }, 0.2],
|
||||||
|
[new Mod[] { new CatchModHalfTime { SpeedChange = { Value = 0.65 } } }, 0.2],
|
||||||
|
[new Mod[] { new CatchModHalfTime { SpeedChange = { Value = 0.70 } } }, 0.3],
|
||||||
|
[new Mod[] { new CatchModHalfTime { SpeedChange = { Value = 0.75 } } }, 0.3],
|
||||||
|
[new Mod[] { new CatchModHalfTime { SpeedChange = { Value = 0.80 } } }, 0.4],
|
||||||
|
[new Mod[] { new CatchModHalfTime { SpeedChange = { Value = 0.85 } } }, 0.4],
|
||||||
|
[new Mod[] { new CatchModHalfTime { SpeedChange = { Value = 0.90 } } }, 0.5],
|
||||||
|
[new Mod[] { new CatchModHalfTime { SpeedChange = { Value = 0.95 } } }, 0.5],
|
||||||
|
[new Mod[] { new CatchModHalfTime { SpeedChange = { Value = 0.99 } } }, 0.5],
|
||||||
|
|
||||||
|
[new Mod[] { new CatchModDaycore { SpeedChange = { Value = 0.50 } } }, 0.1],
|
||||||
|
[new Mod[] { new CatchModDaycore { SpeedChange = { Value = 0.55 } } }, 0.1],
|
||||||
|
[new Mod[] { new CatchModDaycore { SpeedChange = { Value = 0.60 } } }, 0.2],
|
||||||
|
[new Mod[] { new CatchModDaycore { SpeedChange = { Value = 0.65 } } }, 0.2],
|
||||||
|
[new Mod[] { new CatchModDaycore { SpeedChange = { Value = 0.70 } } }, 0.3],
|
||||||
|
[new Mod[] { new CatchModDaycore { SpeedChange = { Value = 0.75 } } }, 0.3],
|
||||||
|
[new Mod[] { new CatchModDaycore { SpeedChange = { Value = 0.80 } } }, 0.4],
|
||||||
|
[new Mod[] { new CatchModDaycore { SpeedChange = { Value = 0.85 } } }, 0.4],
|
||||||
|
[new Mod[] { new CatchModDaycore { SpeedChange = { Value = 0.90 } } }, 0.5],
|
||||||
|
[new Mod[] { new CatchModDaycore { SpeedChange = { Value = 0.95 } } }, 0.5],
|
||||||
|
[new Mod[] { new CatchModDaycore { SpeedChange = { Value = 0.99 } } }, 0.5],
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Difficulty Increase
|
||||||
|
|
||||||
|
[new Mod[] { new CatchModHardRock() }, 1.12],
|
||||||
|
[new Mod[] { new CatchModSuddenDeath() }, 1],
|
||||||
|
[new Mod[] { new CatchModPerfect() }, 1],
|
||||||
|
|
||||||
|
[new Mod[] { new CatchModDoubleTime { SpeedChange = { Value = 1.01 } } }, 1.00],
|
||||||
|
[new Mod[] { new CatchModDoubleTime { SpeedChange = { Value = 1.05 } } }, 1.00],
|
||||||
|
[new Mod[] { new CatchModDoubleTime { SpeedChange = { Value = 1.10 } } }, 1.02],
|
||||||
|
[new Mod[] { new CatchModDoubleTime { SpeedChange = { Value = 1.15 } } }, 1.02],
|
||||||
|
[new Mod[] { new CatchModDoubleTime { SpeedChange = { Value = 1.20 } } }, 1.04],
|
||||||
|
[new Mod[] { new CatchModDoubleTime { SpeedChange = { Value = 1.25 } } }, 1.04],
|
||||||
|
[new Mod[] { new CatchModDoubleTime { SpeedChange = { Value = 1.30 } } }, 1.06],
|
||||||
|
[new Mod[] { new CatchModDoubleTime { SpeedChange = { Value = 1.35 } } }, 1.06],
|
||||||
|
[new Mod[] { new CatchModDoubleTime { SpeedChange = { Value = 1.40 } } }, 1.08],
|
||||||
|
[new Mod[] { new CatchModDoubleTime { SpeedChange = { Value = 1.45 } } }, 1.08],
|
||||||
|
[new Mod[] { new CatchModDoubleTime { SpeedChange = { Value = 1.50 } } }, 1.10],
|
||||||
|
[new Mod[] { new CatchModDoubleTime { SpeedChange = { Value = 1.55 } } }, 1.10],
|
||||||
|
[new Mod[] { new CatchModDoubleTime { SpeedChange = { Value = 1.60 } } }, 1.12],
|
||||||
|
[new Mod[] { new CatchModDoubleTime { SpeedChange = { Value = 1.65 } } }, 1.12],
|
||||||
|
[new Mod[] { new CatchModDoubleTime { SpeedChange = { Value = 1.70 } } }, 1.14],
|
||||||
|
[new Mod[] { new CatchModDoubleTime { SpeedChange = { Value = 1.75 } } }, 1.14],
|
||||||
|
[new Mod[] { new CatchModDoubleTime { SpeedChange = { Value = 1.80 } } }, 1.16],
|
||||||
|
[new Mod[] { new CatchModDoubleTime { SpeedChange = { Value = 1.85 } } }, 1.16],
|
||||||
|
[new Mod[] { new CatchModDoubleTime { SpeedChange = { Value = 1.90 } } }, 1.18],
|
||||||
|
[new Mod[] { new CatchModDoubleTime { SpeedChange = { Value = 1.95 } } }, 1.18],
|
||||||
|
[new Mod[] { new CatchModDoubleTime { SpeedChange = { Value = 2.00 } } }, 1.20],
|
||||||
|
|
||||||
|
[new Mod[] { new CatchModNightcore { SpeedChange = { Value = 1.01 } } }, 1.00],
|
||||||
|
[new Mod[] { new CatchModNightcore { SpeedChange = { Value = 1.05 } } }, 1.00],
|
||||||
|
[new Mod[] { new CatchModNightcore { SpeedChange = { Value = 1.10 } } }, 1.02],
|
||||||
|
[new Mod[] { new CatchModNightcore { SpeedChange = { Value = 1.15 } } }, 1.02],
|
||||||
|
[new Mod[] { new CatchModNightcore { SpeedChange = { Value = 1.20 } } }, 1.04],
|
||||||
|
[new Mod[] { new CatchModNightcore { SpeedChange = { Value = 1.25 } } }, 1.04],
|
||||||
|
[new Mod[] { new CatchModNightcore { SpeedChange = { Value = 1.30 } } }, 1.06],
|
||||||
|
[new Mod[] { new CatchModNightcore { SpeedChange = { Value = 1.35 } } }, 1.06],
|
||||||
|
[new Mod[] { new CatchModNightcore { SpeedChange = { Value = 1.40 } } }, 1.08],
|
||||||
|
[new Mod[] { new CatchModNightcore { SpeedChange = { Value = 1.45 } } }, 1.08],
|
||||||
|
[new Mod[] { new CatchModNightcore { SpeedChange = { Value = 1.50 } } }, 1.10],
|
||||||
|
[new Mod[] { new CatchModNightcore { SpeedChange = { Value = 1.55 } } }, 1.10],
|
||||||
|
[new Mod[] { new CatchModNightcore { SpeedChange = { Value = 1.60 } } }, 1.12],
|
||||||
|
[new Mod[] { new CatchModNightcore { SpeedChange = { Value = 1.65 } } }, 1.12],
|
||||||
|
[new Mod[] { new CatchModNightcore { SpeedChange = { Value = 1.70 } } }, 1.14],
|
||||||
|
[new Mod[] { new CatchModNightcore { SpeedChange = { Value = 1.75 } } }, 1.14],
|
||||||
|
[new Mod[] { new CatchModNightcore { SpeedChange = { Value = 1.80 } } }, 1.16],
|
||||||
|
[new Mod[] { new CatchModNightcore { SpeedChange = { Value = 1.85 } } }, 1.16],
|
||||||
|
[new Mod[] { new CatchModNightcore { SpeedChange = { Value = 1.90 } } }, 1.18],
|
||||||
|
[new Mod[] { new CatchModNightcore { SpeedChange = { Value = 1.95 } } }, 1.18],
|
||||||
|
[new Mod[] { new CatchModNightcore { SpeedChange = { Value = 2.00 } } }, 1.20],
|
||||||
|
|
||||||
|
[new Mod[] { new CatchModHidden() }, 1.06],
|
||||||
|
|
||||||
|
[new Mod[] { new CatchModFlashlight() }, 1.12],
|
||||||
|
[new Mod[] { new CatchModFlashlight { ComboBasedSize = { Value = false } } }, 1],
|
||||||
|
|
||||||
|
[new Mod[] { new ModAccuracyChallenge() }, 1],
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Conversion
|
||||||
|
|
||||||
|
[new Mod[] { new CatchModDifficultyAdjust() }, 0.5],
|
||||||
|
[new Mod[] { new CatchModClassic() }, 1],
|
||||||
|
[new Mod[] { new CatchModMirror() }, 1],
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Automation
|
||||||
|
|
||||||
|
[new Mod[] { new CatchModAutoplay() }, 1],
|
||||||
|
[new Mod[] { new CatchModCinema() }, 1],
|
||||||
|
[new Mod[] { new CatchModRelax() }, 0.1],
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Fun
|
||||||
|
|
||||||
|
[new Mod[] { new ModWindUp() }, 0.5],
|
||||||
|
[new Mod[] { new ModWindDown() }, 0.5],
|
||||||
|
[new Mod[] { new CatchModFloatingFruits() }, 1],
|
||||||
|
[new Mod[] { new CatchModMuted() }, 1],
|
||||||
|
[new Mod[] { new CatchModNoScope() }, 1],
|
||||||
|
[new Mod[] { new CatchModMovingFast() }, 1],
|
||||||
|
[new Mod[] { new CatchModSynesthesia() }, 0.8],
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region System
|
||||||
|
|
||||||
|
[new Mod[] { new ModScoreV2() }, 1],
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Combinations
|
||||||
|
|
||||||
|
[new Mod[] { new CatchModHidden(), new CatchModHardRock() }, 1.06 * 1.12]
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
];
|
||||||
|
|
||||||
|
[TestCaseSource(nameof(test_cases))]
|
||||||
|
public void TestMultipliers(Mod[] mods, double expectedMultiplier)
|
||||||
|
=> TestModCombination(mods, expectedMultiplier);
|
||||||
|
|
||||||
|
[TestCase(30000001, 0.96)]
|
||||||
|
[TestCase(30000009, 0.96)]
|
||||||
|
[TestCase(30000016, 0.96)]
|
||||||
|
[TestCase(30000017, 1)]
|
||||||
|
[TestCase(null, 1)]
|
||||||
|
public void TestClassicMultiplierVersioning(int? totalScoreVersion, double expectedMultiplier)
|
||||||
|
{
|
||||||
|
var scoreInfo = totalScoreVersion != null ? new ScoreInfo { TotalScoreVersion = totalScoreVersion.Value } : null;
|
||||||
|
var calculator = Ruleset.CreateScoreMultiplierCalculator(new ScoreMultiplierContext(new BeatmapDifficulty(), scoreInfo));
|
||||||
|
Assert.That(calculator.CalculateFor([new CatchModClassic()]), Is.EqualTo(expectedMultiplier).Within(Precision.DOUBLE_EPSILON));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -169,6 +169,8 @@ namespace osu.Game.Rulesets.Catch
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override ScoreMultiplierCalculator CreateScoreMultiplierCalculator(ScoreMultiplierContext context) => new CatchScoreMultiplierCalculator(context);
|
||||||
|
|
||||||
public override string Description => "osu!catch";
|
public override string Description => "osu!catch";
|
||||||
|
|
||||||
public override string ShortName => SHORT_NAME;
|
public override string ShortName => SHORT_NAME;
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Catch.Edit.Blueprints;
|
using osu.Game.Rulesets.Catch.Edit.Blueprints;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Edit.Tools;
|
using osu.Game.Rulesets.Edit.Tools;
|
||||||
|
|
||||||
@@ -13,11 +13,11 @@ namespace osu.Game.Rulesets.Catch.Edit
|
|||||||
public class BananaShowerCompositionTool : CompositionTool
|
public class BananaShowerCompositionTool : CompositionTool
|
||||||
{
|
{
|
||||||
public BananaShowerCompositionTool()
|
public BananaShowerCompositionTool()
|
||||||
: base(nameof(BananaShower))
|
: base("Banana shower")
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners);
|
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.EditorBananaShower };
|
||||||
|
|
||||||
public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new BananaShowerPlacementBlueprint();
|
public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new BananaShowerPlacementBlueprint();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,16 @@
|
|||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Catch.Edit.Blueprints;
|
using osu.Game.Rulesets.Catch.Edit.Blueprints;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Screens.Edit.Components.TernaryButtons;
|
||||||
using osu.Game.Screens.Edit.Compose.Components;
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@@ -22,6 +27,29 @@ namespace osu.Game.Rulesets.Catch.Edit
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override Drawable CreateNewComboButton() => new NewComboTernaryButton
|
||||||
|
{
|
||||||
|
Current = NewCombo,
|
||||||
|
CreateIcon = () => new Container
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new SpriteIcon
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomLeft,
|
||||||
|
Origin = Anchor.BottomLeft,
|
||||||
|
Icon = OsuIcon.EditorFruit,
|
||||||
|
Size = new Vector2(15),
|
||||||
|
},
|
||||||
|
new SpriteIcon
|
||||||
|
{
|
||||||
|
Icon = OsuIcon.EditorNewComboSparkles,
|
||||||
|
Size = new Vector2(20),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
protected override SelectionHandler<HitObject> CreateSelectionHandler() => new CatchSelectionHandler();
|
protected override SelectionHandler<HitObject> CreateSelectionHandler() => new CatchSelectionHandler();
|
||||||
|
|
||||||
public override HitObjectSelectionBlueprint? CreateHitObjectBlueprintFor(HitObject hitObject)
|
public override HitObjectSelectionBlueprint? CreateHitObjectBlueprintFor(HitObject hitObject)
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Catch.Edit.Blueprints;
|
using osu.Game.Rulesets.Catch.Edit.Blueprints;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
@@ -17,7 +18,7 @@ namespace osu.Game.Rulesets.Catch.Edit
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles);
|
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.EditorFruit };
|
||||||
|
|
||||||
public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new FruitPlacementBlueprint();
|
public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new FruitPlacementBlueprint();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Catch.Edit.Blueprints;
|
using osu.Game.Rulesets.Catch.Edit.Blueprints;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Edit.Tools;
|
using osu.Game.Rulesets.Edit.Tools;
|
||||||
|
|
||||||
@@ -13,11 +13,11 @@ namespace osu.Game.Rulesets.Catch.Edit
|
|||||||
public class JuiceStreamCompositionTool : CompositionTool
|
public class JuiceStreamCompositionTool : CompositionTool
|
||||||
{
|
{
|
||||||
public JuiceStreamCompositionTool()
|
public JuiceStreamCompositionTool()
|
||||||
: base(nameof(JuiceStream))
|
: base("Juice stream")
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders);
|
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.EditorJuiceStream };
|
||||||
|
|
||||||
public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new JuiceStreamPlacementBlueprint();
|
public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new JuiceStreamPlacementBlueprint();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ namespace osu.Game.Rulesets.Catch.Mods
|
|||||||
{
|
{
|
||||||
public partial class CatchModFlashlight : ModFlashlight<CatchHitObject>
|
public partial class CatchModFlashlight : ModFlashlight<CatchHitObject>
|
||||||
{
|
{
|
||||||
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
|
|
||||||
|
|
||||||
public override BindableFloat SizeMultiplier { get; } = new BindableFloat(1)
|
public override BindableFloat SizeMultiplier { get; } = new BindableFloat(1)
|
||||||
{
|
{
|
||||||
MinValue = 0.5f,
|
MinValue = 0.5f,
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ namespace osu.Game.Rulesets.Catch.Mods
|
|||||||
public override string Name => "Floating Fruits";
|
public override string Name => "Floating Fruits";
|
||||||
public override string Acronym => "FF";
|
public override string Acronym => "FF";
|
||||||
public override LocalisableString Description => "The fruits are... floating?";
|
public override LocalisableString Description => "The fruits are... floating?";
|
||||||
public override double ScoreMultiplier => 1;
|
|
||||||
public override IconUsage? Icon => OsuIcon.ModFloatingFruits;
|
public override IconUsage? Icon => OsuIcon.ModFloatingFruits;
|
||||||
|
|
||||||
public void ApplyToDrawableRuleset(DrawableRuleset<CatchHitObject> drawableRuleset)
|
public void ApplyToDrawableRuleset(DrawableRuleset<CatchHitObject> drawableRuleset)
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ namespace osu.Game.Rulesets.Catch.Mods
|
|||||||
{
|
{
|
||||||
public class CatchModHardRock : ModHardRock, IApplicableToBeatmapProcessor
|
public class CatchModHardRock : ModHardRock, IApplicableToBeatmapProcessor
|
||||||
{
|
{
|
||||||
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
|
|
||||||
|
|
||||||
public void ApplyToBeatmapProcessor(IBeatmapProcessor beatmapProcessor)
|
public void ApplyToBeatmapProcessor(IBeatmapProcessor beatmapProcessor)
|
||||||
{
|
{
|
||||||
var catchProcessor = (CatchBeatmapProcessor)beatmapProcessor;
|
var catchProcessor = (CatchBeatmapProcessor)beatmapProcessor;
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ namespace osu.Game.Rulesets.Catch.Mods
|
|||||||
public class CatchModHidden : ModHidden, IApplicableToDrawableRuleset<CatchHitObject>
|
public class CatchModHidden : ModHidden, IApplicableToDrawableRuleset<CatchHitObject>
|
||||||
{
|
{
|
||||||
public override LocalisableString Description => @"Play with fading fruits.";
|
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_offset_multiplier = 0.6;
|
||||||
private const double fade_out_duration_multiplier = 0.44;
|
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 string Acronym => "MF";
|
||||||
public override LocalisableString Description => "Dashing by default, slow down!";
|
public override LocalisableString Description => "Dashing by default, slow down!";
|
||||||
public override ModType Type => ModType.Fun;
|
public override ModType Type => ModType.Fun;
|
||||||
public override double ScoreMultiplier => 1;
|
|
||||||
public override IconUsage? Icon => OsuIcon.ModMovingFast;
|
public override IconUsage? Icon => OsuIcon.ModMovingFast;
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModRelax) };
|
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModRelax) };
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
// 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.Rulesets.Catch.Mods;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Scoring
|
||||||
|
{
|
||||||
|
public class CatchScoreMultiplierCalculator : ScoreMultiplierCalculator
|
||||||
|
{
|
||||||
|
public CatchScoreMultiplierCalculator(ScoreMultiplierContext context)
|
||||||
|
: base(context)
|
||||||
|
{
|
||||||
|
#region Difficulty Reduction
|
||||||
|
|
||||||
|
Single<CatchModEasy>(hasMultiplier: 0.5);
|
||||||
|
Single<CatchModNoFail>(hasMultiplier: 0.5);
|
||||||
|
Single<CatchModHalfTime>(hasMultiplier: halfTime => rateAdjustMultiplier(halfTime.SpeedChange.Value));
|
||||||
|
Single<CatchModDaycore>(hasMultiplier: daycore => rateAdjustMultiplier(daycore.SpeedChange.Value));
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Difficulty Increase
|
||||||
|
|
||||||
|
Single<CatchModHardRock>(hasMultiplier: hardRock => hardRock.UsesDefaultConfiguration ? 1.12 : 1);
|
||||||
|
// Sudden Death
|
||||||
|
// Perfect
|
||||||
|
Single<CatchModDoubleTime>(hasMultiplier: doubleTime => rateAdjustMultiplier(doubleTime.SpeedChange.Value));
|
||||||
|
Single<CatchModNightcore>(hasMultiplier: nightcore => rateAdjustMultiplier(nightcore.SpeedChange.Value));
|
||||||
|
Single<CatchModHidden>(hasMultiplier: hidden => hidden.UsesDefaultConfiguration ? 1.06 : 1);
|
||||||
|
Single<CatchModFlashlight>(hasMultiplier: flashlight => flashlight.UsesDefaultConfiguration ? 1.12 : 1);
|
||||||
|
// Accuracy Challenge
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Conversion
|
||||||
|
|
||||||
|
Single<CatchModDifficultyAdjust>(hasMultiplier: 0.5);
|
||||||
|
Single<CatchModClassic>(hasMultiplier: _ => classicMultiplier(context.Score));
|
||||||
|
// Mirror
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Automation
|
||||||
|
|
||||||
|
// Autoplay
|
||||||
|
// Cinema
|
||||||
|
Single<CatchModRelax>(hasMultiplier: 0.1);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Fun
|
||||||
|
|
||||||
|
Single<ModWindUp>(hasMultiplier: 0.5);
|
||||||
|
Single<ModWindDown>(hasMultiplier: 0.5);
|
||||||
|
// Floating Fruits
|
||||||
|
// Muted
|
||||||
|
// No Scope
|
||||||
|
// Moving Fast
|
||||||
|
Single<CatchModSynesthesia>(hasMultiplier: 0.8);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region System
|
||||||
|
|
||||||
|
// Score V2
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double rateAdjustMultiplier(double speedChange)
|
||||||
|
{
|
||||||
|
// Round to the nearest multiple of 0.1.
|
||||||
|
double value = (int)(speedChange * 10) / 10.0;
|
||||||
|
|
||||||
|
// Offset back to 0.
|
||||||
|
value -= 1;
|
||||||
|
|
||||||
|
if (speedChange >= 1)
|
||||||
|
return 1 + value / 5;
|
||||||
|
else
|
||||||
|
return 0.6 + value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double classicMultiplier(ScoreInfo? score)
|
||||||
|
{
|
||||||
|
if (score != null && score.TotalScoreVersion < 30000017)
|
||||||
|
return 0.96;
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,8 +36,8 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
var decoded = DecodeFromLegacy(beatmaps_resource_store.GetStream($"Resources/Testing/Beatmaps/{name}.osu"), beatmaps_resource_store, name);
|
var decoded = DecodeFromLegacy(beatmaps_resource_store.GetStream($"Resources/Testing/Beatmaps/{name}.osu"), beatmaps_resource_store, name);
|
||||||
var decodedAfterEncode = DecodeFromLegacy(EncodeToLegacy(decoded), beatmaps_resource_store, name);
|
var decodedAfterEncode = DecodeFromLegacy(EncodeToLegacy(decoded), beatmaps_resource_store, name);
|
||||||
|
|
||||||
Sort(decoded.beatmap);
|
Sort(decoded.Beatmap);
|
||||||
Sort(decodedAfterEncode.beatmap);
|
Sort(decodedAfterEncode.Beatmap);
|
||||||
|
|
||||||
CompareBeatmaps(decoded, decodedAfterEncode);
|
CompareBeatmaps(decoded, decodedAfterEncode);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,236 @@
|
|||||||
|
// 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 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;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Tests.Rulesets;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Tests
|
||||||
|
{
|
||||||
|
public class ManiaScoreMultiplierTest : RulesetScoreMultiplierTest
|
||||||
|
{
|
||||||
|
public ManiaScoreMultiplierTest()
|
||||||
|
: base(new ManiaRuleset())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly object[][] test_cases =
|
||||||
|
[
|
||||||
|
#region Difficulty Reduction
|
||||||
|
|
||||||
|
[new Mod[] { new ManiaModEasy() }, 0.5],
|
||||||
|
[new Mod[] { new ManiaModNoFail() }, 0.5],
|
||||||
|
|
||||||
|
[new Mod[] { new ManiaModHalfTime { SpeedChange = { Value = 0.50 } } }, 0.1],
|
||||||
|
[new Mod[] { new ManiaModHalfTime { SpeedChange = { Value = 0.55 } } }, 0.1],
|
||||||
|
[new Mod[] { new ManiaModHalfTime { SpeedChange = { Value = 0.60 } } }, 0.2],
|
||||||
|
[new Mod[] { new ManiaModHalfTime { SpeedChange = { Value = 0.65 } } }, 0.2],
|
||||||
|
[new Mod[] { new ManiaModHalfTime { SpeedChange = { Value = 0.70 } } }, 0.3],
|
||||||
|
[new Mod[] { new ManiaModHalfTime { SpeedChange = { Value = 0.75 } } }, 0.3],
|
||||||
|
[new Mod[] { new ManiaModHalfTime { SpeedChange = { Value = 0.80 } } }, 0.4],
|
||||||
|
[new Mod[] { new ManiaModHalfTime { SpeedChange = { Value = 0.85 } } }, 0.4],
|
||||||
|
[new Mod[] { new ManiaModHalfTime { SpeedChange = { Value = 0.90 } } }, 0.5],
|
||||||
|
[new Mod[] { new ManiaModHalfTime { SpeedChange = { Value = 0.95 } } }, 0.5],
|
||||||
|
[new Mod[] { new ManiaModHalfTime { SpeedChange = { Value = 0.99 } } }, 0.5],
|
||||||
|
|
||||||
|
[new Mod[] { new ManiaModDaycore { SpeedChange = { Value = 0.50 } } }, 0.1],
|
||||||
|
[new Mod[] { new ManiaModDaycore { SpeedChange = { Value = 0.55 } } }, 0.1],
|
||||||
|
[new Mod[] { new ManiaModDaycore { SpeedChange = { Value = 0.60 } } }, 0.2],
|
||||||
|
[new Mod[] { new ManiaModDaycore { SpeedChange = { Value = 0.65 } } }, 0.2],
|
||||||
|
[new Mod[] { new ManiaModDaycore { SpeedChange = { Value = 0.70 } } }, 0.3],
|
||||||
|
[new Mod[] { new ManiaModDaycore { SpeedChange = { Value = 0.75 } } }, 0.3],
|
||||||
|
[new Mod[] { new ManiaModDaycore { SpeedChange = { Value = 0.80 } } }, 0.4],
|
||||||
|
[new Mod[] { new ManiaModDaycore { SpeedChange = { Value = 0.85 } } }, 0.4],
|
||||||
|
[new Mod[] { new ManiaModDaycore { SpeedChange = { Value = 0.90 } } }, 0.5],
|
||||||
|
[new Mod[] { new ManiaModDaycore { SpeedChange = { Value = 0.95 } } }, 0.5],
|
||||||
|
[new Mod[] { new ManiaModDaycore { SpeedChange = { Value = 0.99 } } }, 0.5],
|
||||||
|
|
||||||
|
[new Mod[] { new ManiaModNoRelease() }, 0.9],
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Difficulty Increase
|
||||||
|
|
||||||
|
[new Mod[] { new ManiaModHardRock() }, 1],
|
||||||
|
[new Mod[] { new ManiaModSuddenDeath() }, 1],
|
||||||
|
[new Mod[] { new ManiaModPerfect() }, 1],
|
||||||
|
|
||||||
|
[new Mod[] { new ManiaModDoubleTime { SpeedChange = { Value = 1.01 } } }, 1],
|
||||||
|
[new Mod[] { new ManiaModDoubleTime { SpeedChange = { Value = 1.05 } } }, 1],
|
||||||
|
[new Mod[] { new ManiaModDoubleTime { SpeedChange = { Value = 1.10 } } }, 1],
|
||||||
|
[new Mod[] { new ManiaModDoubleTime { SpeedChange = { Value = 1.15 } } }, 1],
|
||||||
|
[new Mod[] { new ManiaModDoubleTime { SpeedChange = { Value = 1.20 } } }, 1],
|
||||||
|
[new Mod[] { new ManiaModDoubleTime { SpeedChange = { Value = 1.25 } } }, 1],
|
||||||
|
[new Mod[] { new ManiaModDoubleTime { SpeedChange = { Value = 1.30 } } }, 1],
|
||||||
|
[new Mod[] { new ManiaModDoubleTime { SpeedChange = { Value = 1.35 } } }, 1],
|
||||||
|
[new Mod[] { new ManiaModDoubleTime { SpeedChange = { Value = 1.40 } } }, 1],
|
||||||
|
[new Mod[] { new ManiaModDoubleTime { SpeedChange = { Value = 1.45 } } }, 1],
|
||||||
|
[new Mod[] { new ManiaModDoubleTime { SpeedChange = { Value = 1.50 } } }, 1],
|
||||||
|
[new Mod[] { new ManiaModDoubleTime { SpeedChange = { Value = 1.55 } } }, 1],
|
||||||
|
[new Mod[] { new ManiaModDoubleTime { SpeedChange = { Value = 1.60 } } }, 1],
|
||||||
|
[new Mod[] { new ManiaModDoubleTime { SpeedChange = { Value = 1.65 } } }, 1],
|
||||||
|
[new Mod[] { new ManiaModDoubleTime { SpeedChange = { Value = 1.70 } } }, 1],
|
||||||
|
[new Mod[] { new ManiaModDoubleTime { SpeedChange = { Value = 1.75 } } }, 1],
|
||||||
|
[new Mod[] { new ManiaModDoubleTime { SpeedChange = { Value = 1.80 } } }, 1],
|
||||||
|
[new Mod[] { new ManiaModDoubleTime { SpeedChange = { Value = 1.85 } } }, 1],
|
||||||
|
[new Mod[] { new ManiaModDoubleTime { SpeedChange = { Value = 1.90 } } }, 1],
|
||||||
|
[new Mod[] { new ManiaModDoubleTime { SpeedChange = { Value = 1.95 } } }, 1],
|
||||||
|
[new Mod[] { new ManiaModDoubleTime { SpeedChange = { Value = 2.00 } } }, 1],
|
||||||
|
|
||||||
|
[new Mod[] { new ManiaModNightcore { SpeedChange = { Value = 1.01 } } }, 1],
|
||||||
|
[new Mod[] { new ManiaModNightcore { SpeedChange = { Value = 1.05 } } }, 1],
|
||||||
|
[new Mod[] { new ManiaModNightcore { SpeedChange = { Value = 1.10 } } }, 1],
|
||||||
|
[new Mod[] { new ManiaModNightcore { SpeedChange = { Value = 1.15 } } }, 1],
|
||||||
|
[new Mod[] { new ManiaModNightcore { SpeedChange = { Value = 1.20 } } }, 1],
|
||||||
|
[new Mod[] { new ManiaModNightcore { SpeedChange = { Value = 1.25 } } }, 1],
|
||||||
|
[new Mod[] { new ManiaModNightcore { SpeedChange = { Value = 1.30 } } }, 1],
|
||||||
|
[new Mod[] { new ManiaModNightcore { SpeedChange = { Value = 1.35 } } }, 1],
|
||||||
|
[new Mod[] { new ManiaModNightcore { SpeedChange = { Value = 1.40 } } }, 1],
|
||||||
|
[new Mod[] { new ManiaModNightcore { SpeedChange = { Value = 1.45 } } }, 1],
|
||||||
|
[new Mod[] { new ManiaModNightcore { SpeedChange = { Value = 1.50 } } }, 1],
|
||||||
|
[new Mod[] { new ManiaModNightcore { SpeedChange = { Value = 1.55 } } }, 1],
|
||||||
|
[new Mod[] { new ManiaModNightcore { SpeedChange = { Value = 1.60 } } }, 1],
|
||||||
|
[new Mod[] { new ManiaModNightcore { SpeedChange = { Value = 1.65 } } }, 1],
|
||||||
|
[new Mod[] { new ManiaModNightcore { SpeedChange = { Value = 1.70 } } }, 1],
|
||||||
|
[new Mod[] { new ManiaModNightcore { SpeedChange = { Value = 1.75 } } }, 1],
|
||||||
|
[new Mod[] { new ManiaModNightcore { SpeedChange = { Value = 1.80 } } }, 1],
|
||||||
|
[new Mod[] { new ManiaModNightcore { SpeedChange = { Value = 1.85 } } }, 1],
|
||||||
|
[new Mod[] { new ManiaModNightcore { SpeedChange = { Value = 1.90 } } }, 1],
|
||||||
|
[new Mod[] { new ManiaModNightcore { SpeedChange = { Value = 1.95 } } }, 1],
|
||||||
|
[new Mod[] { new ManiaModNightcore { SpeedChange = { Value = 2.00 } } }, 1],
|
||||||
|
|
||||||
|
[new Mod[] { new ManiaModFadeIn() }, 1],
|
||||||
|
[new Mod[] { new ManiaModHidden() }, 1],
|
||||||
|
[new Mod[] { new ManiaModCover() }, 1],
|
||||||
|
|
||||||
|
[new Mod[] { new ManiaModFlashlight() }, 1],
|
||||||
|
[new Mod[] { new ModAccuracyChallenge() }, 1],
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Conversion
|
||||||
|
|
||||||
|
[new Mod[] { new ManiaModRandom() }, 1],
|
||||||
|
[new Mod[] { new ManiaModDualStages() }, 1],
|
||||||
|
[new Mod[] { new ManiaModMirror() }, 1],
|
||||||
|
[new Mod[] { new ManiaModDifficultyAdjust() }, 0.5],
|
||||||
|
[new Mod[] { new ManiaModClassic() }, 1],
|
||||||
|
[new Mod[] { new ManiaModInvert() }, 1],
|
||||||
|
[new Mod[] { new ManiaModConstantSpeed() }, 0.9],
|
||||||
|
[new Mod[] { new ManiaModHoldOff() }, 0.9],
|
||||||
|
[new Mod[] { new ManiaModKey1() }, 0.9],
|
||||||
|
[new Mod[] { new ManiaModKey2() }, 0.9],
|
||||||
|
[new Mod[] { new ManiaModKey3() }, 0.9],
|
||||||
|
[new Mod[] { new ManiaModKey4() }, 0.9],
|
||||||
|
[new Mod[] { new ManiaModKey5() }, 0.9],
|
||||||
|
[new Mod[] { new ManiaModKey6() }, 0.9],
|
||||||
|
[new Mod[] { new ManiaModKey7() }, 0.9],
|
||||||
|
[new Mod[] { new ManiaModKey8() }, 0.9],
|
||||||
|
[new Mod[] { new ManiaModKey9() }, 0.9],
|
||||||
|
[new Mod[] { new ManiaModKey10() }, 0.9],
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Automation
|
||||||
|
|
||||||
|
[new Mod[] { new ManiaModAutoplay() }, 1],
|
||||||
|
[new Mod[] { new ManiaModCinema() }, 1],
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Fun
|
||||||
|
|
||||||
|
[new Mod[] { new ModWindUp() }, 0.5],
|
||||||
|
[new Mod[] { new ModWindDown() }, 0.5],
|
||||||
|
[new Mod[] { new ManiaModMuted() }, 1],
|
||||||
|
[new Mod[] { new ModAdaptiveSpeed() }, 0.5],
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region System
|
||||||
|
|
||||||
|
[new Mod[] { new ManiaModScoreV2() }, 1],
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Combinations
|
||||||
|
|
||||||
|
[new Mod[] { new ManiaModEasy(), new ManiaModKey4() }, 0.5 * 0.9]
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
];
|
||||||
|
|
||||||
|
[TestCaseSource(nameof(test_cases))]
|
||||||
|
public void TestMultipliers(Mod[] mods, double expectedMultiplier)
|
||||||
|
=> TestModCombination(mods, expectedMultiplier);
|
||||||
|
|
||||||
|
private static readonly object[][] key_mod_multiplier_test_cases =
|
||||||
|
[
|
||||||
|
// score end date, client version, expected multiplier
|
||||||
|
|
||||||
|
// scores verifiably from old clients.
|
||||||
|
[new DateTimeOffset(2024, 1, 31, 11, 0, 0, TimeSpan.Zero), "2024.130.2", 1],
|
||||||
|
[new DateTimeOffset(2024, 12, 9, 11, 0, 0, TimeSpan.Zero), "2024.1208.0", 1],
|
||||||
|
[new DateTimeOffset(2025, 6, 12, 11, 0, 0, TimeSpan.Zero), "2025.605.3", 1],
|
||||||
|
[new DateTimeOffset(2025, 6, 28, 11, 0, 0, TimeSpan.Zero), "2025.625.0-tachyon", 1],
|
||||||
|
[new DateTimeOffset(2025, 7, 11, 11, 0, 0, TimeSpan.Zero), "2025.710.0-lazer", 1],
|
||||||
|
[new DateTimeOffset(2025, 7, 15, 11, 0, 0, TimeSpan.Zero), "2025.711.0-tachyon", 1],
|
||||||
|
|
||||||
|
// scores without explicit client versions, predating the change of multiplier.
|
||||||
|
// those MUST have used the old multiplier.
|
||||||
|
[new DateTimeOffset(2024, 1, 31, 11, 0, 0, TimeSpan.Zero), "", 1],
|
||||||
|
[new DateTimeOffset(2024, 12, 9, 11, 0, 0, TimeSpan.Zero), "", 1],
|
||||||
|
[new DateTimeOffset(2025, 6, 12, 11, 0, 0, TimeSpan.Zero), "", 1],
|
||||||
|
[new DateTimeOffset(2025, 6, 28, 11, 0, 0, TimeSpan.Zero), "", 1],
|
||||||
|
[new DateTimeOffset(2025, 7, 11, 11, 0, 0, TimeSpan.Zero), "", 1],
|
||||||
|
[new DateTimeOffset(2025, 7, 15, 11, 0, 0, TimeSpan.Zero), "", 1],
|
||||||
|
|
||||||
|
// scores without explicit client versions, AFTER the change of multiplier.
|
||||||
|
// there is NO way of verifying whether these scores use the new or old multiplier, therefore GUESS that it's the new one.
|
||||||
|
// "thankfully" the window of opportunity for this occurring *should* be slim
|
||||||
|
// (from client release with new key mod multipliers on July 18, 2025
|
||||||
|
// until spectator server release which added client version writing to server-side replays on August 1, 2025).
|
||||||
|
[new DateTimeOffset(2025, 7, 19, 0, 20, 15, 0, TimeSpan.Zero), "", 0.9],
|
||||||
|
[new DateTimeOffset(2025, 7, 23, 0, 20, 15, 0, TimeSpan.Zero), "", 0.9],
|
||||||
|
[new DateTimeOffset(2025, 8, 19, 0, 20, 15, 0, TimeSpan.Zero), "", 0.9],
|
||||||
|
[new DateTimeOffset(2026, 6, 18, 0, 20, 15, 0, TimeSpan.Zero), "", 0.9],
|
||||||
|
[new DateTimeOffset(2026, 7, 18, 0, 20, 15, 0, TimeSpan.Zero), "", 0.9],
|
||||||
|
|
||||||
|
// scores verifiably from new clients.
|
||||||
|
[new DateTimeOffset(2025, 7, 19, 0, 20, 15, 0, TimeSpan.Zero), "2025.718.0-tachyon", 0.9],
|
||||||
|
[new DateTimeOffset(2025, 7, 23, 0, 20, 15, 0, TimeSpan.Zero), "2025.721.0-tachyon", 0.9],
|
||||||
|
[new DateTimeOffset(2025, 8, 19, 0, 20, 15, 0, TimeSpan.Zero), "2025.816.0-lazer", 0.9],
|
||||||
|
[new DateTimeOffset(2026, 6, 18, 0, 20, 15, 0, TimeSpan.Zero), "2026.518.0-lazer", 0.9],
|
||||||
|
[new DateTimeOffset(2026, 7, 18, 0, 20, 15, 0, TimeSpan.Zero), "2026.522.1-tachyon", 0.9],
|
||||||
|
];
|
||||||
|
|
||||||
|
[TestCaseSource(nameof(key_mod_multiplier_test_cases))]
|
||||||
|
public void TestKeyModMultiplierCompatibility(DateTimeOffset endDate, string clientVersion, double expectedMultiplier)
|
||||||
|
{
|
||||||
|
var calculator = Ruleset.CreateScoreMultiplierCalculator(new ScoreMultiplierContext(new BeatmapDifficulty(), new ScoreInfo
|
||||||
|
{
|
||||||
|
Date = endDate,
|
||||||
|
ClientVersion = clientVersion
|
||||||
|
}));
|
||||||
|
Assert.That(calculator.CalculateFor([new ManiaModKey4()]), Is.EqualTo(expectedMultiplier).Within(Precision.DOUBLE_EPSILON));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase(30000001, 0.96)]
|
||||||
|
[TestCase(30000009, 0.96)]
|
||||||
|
[TestCase(30000016, 0.96)]
|
||||||
|
[TestCase(30000017, 1)]
|
||||||
|
[TestCase(null, 1)]
|
||||||
|
public void TestClassicMultiplierVersioning(int? totalScoreVersion, double expectedMultiplier)
|
||||||
|
{
|
||||||
|
var scoreInfo = totalScoreVersion != null ? new ScoreInfo { TotalScoreVersion = totalScoreVersion.Value } : null;
|
||||||
|
var calculator = Ruleset.CreateScoreMultiplierCalculator(new ScoreMultiplierContext(new BeatmapDifficulty(), scoreInfo));
|
||||||
|
Assert.That(calculator.CalculateFor([new ManiaModClassic()]), Is.EqualTo(expectedMultiplier).Within(Precision.DOUBLE_EPSILON));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,8 +8,10 @@ using osu.Game.Beatmaps;
|
|||||||
using osu.Game.Rulesets.Mania.Mods;
|
using osu.Game.Rulesets.Mania.Mods;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Mania.Replays;
|
using osu.Game.Rulesets.Mania.Replays;
|
||||||
|
using osu.Game.Rulesets.Mania.Scoring;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Replays;
|
using osu.Game.Rulesets.Replays;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Tests.Mods
|
namespace osu.Game.Rulesets.Mania.Tests.Mods
|
||||||
@@ -54,7 +56,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods
|
|||||||
Mod = doubleTime,
|
Mod = doubleTime,
|
||||||
PassCondition = () => Player.ScoreProcessor.JudgedHits > 0
|
PassCondition = () => Player.ScoreProcessor.JudgedHits > 0
|
||||||
&& Player.ScoreProcessor.Accuracy.Value == 1
|
&& Player.ScoreProcessor.Accuracy.Value == 1
|
||||||
&& Player.ScoreProcessor.TotalScore.Value == (long)(1_000_000 * doubleTime.ScoreMultiplier),
|
&& Player.ScoreProcessor.TotalScore.Value == (long)(1_000_000 * new ManiaScoreMultiplierCalculator(new ScoreMultiplierContext(new BeatmapDifficulty())).CalculateFor([doubleTime])),
|
||||||
Autoplay = false,
|
Autoplay = false,
|
||||||
CreateBeatmap = () => new Beatmap
|
CreateBeatmap = () => new Beatmap
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ using osu.Game.Rulesets.Edit;
|
|||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Mania.Skinning.Default;
|
using osu.Game.Rulesets.Mania.Skinning.Default;
|
||||||
using osu.Game.Rulesets.Mania.UI;
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
@@ -90,5 +92,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
private float getNoteHeight(Column resultPlayfield) =>
|
private float getNoteHeight(Column resultPlayfield) =>
|
||||||
resultPlayfield.ToScreenSpace(new Vector2(DefaultNotePiece.NOTE_HEIGHT)).Y -
|
resultPlayfield.ToScreenSpace(new Vector2(DefaultNotePiece.NOTE_HEIGHT)).Y -
|
||||||
resultPlayfield.ToScreenSpace(Vector2.Zero).Y;
|
resultPlayfield.ToScreenSpace(Vector2.Zero).Y;
|
||||||
|
|
||||||
|
public override bool ReplacesExistingObject(HitObject existing)
|
||||||
|
=> base.ReplacesExistingObject(existing) && HitObject.Column == ((IHasColumn)existing).Column;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Edit.Tools;
|
using osu.Game.Rulesets.Edit.Tools;
|
||||||
using osu.Game.Rulesets.Mania.Edit.Blueprints;
|
using osu.Game.Rulesets.Mania.Edit.Blueprints;
|
||||||
@@ -16,7 +17,7 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders);
|
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.EditorHoldNote };
|
||||||
|
|
||||||
public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new HoldNotePlacementBlueprint();
|
public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new HoldNotePlacementBlueprint();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,16 @@
|
|||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Mania.Edit.Blueprints;
|
using osu.Game.Rulesets.Mania.Edit.Blueprints;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Screens.Edit.Components.TernaryButtons;
|
||||||
using osu.Game.Screens.Edit.Compose.Components;
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@@ -22,6 +27,29 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override Drawable CreateNewComboButton() => new NewComboTernaryButton
|
||||||
|
{
|
||||||
|
Current = NewCombo,
|
||||||
|
CreateIcon = () => new Container
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new SpriteIcon
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomLeft,
|
||||||
|
Origin = Anchor.BottomLeft,
|
||||||
|
Icon = OsuIcon.EditorNote,
|
||||||
|
Size = new Vector2(15),
|
||||||
|
},
|
||||||
|
new SpriteIcon
|
||||||
|
{
|
||||||
|
Icon = OsuIcon.EditorNewComboSparkles,
|
||||||
|
Size = new Vector2(20),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
public override HitObjectSelectionBlueprint? CreateHitObjectBlueprintFor(HitObject hitObject)
|
public override HitObjectSelectionBlueprint? CreateHitObjectBlueprintFor(HitObject hitObject)
|
||||||
{
|
{
|
||||||
switch (hitObject)
|
switch (hitObject)
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Edit.Tools;
|
using osu.Game.Rulesets.Edit.Tools;
|
||||||
using osu.Game.Rulesets.Mania.Edit.Blueprints;
|
using osu.Game.Rulesets.Mania.Edit.Blueprints;
|
||||||
@@ -17,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles);
|
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.EditorNote };
|
||||||
|
|
||||||
public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new NotePlacementBlueprint();
|
public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new NotePlacementBlueprint();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -307,6 +307,8 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override ScoreMultiplierCalculator CreateScoreMultiplierCalculator(ScoreMultiplierContext context) => new ManiaScoreMultiplierCalculator(context);
|
||||||
|
|
||||||
public override string Description => "osu!mania";
|
public override string Description => "osu!mania";
|
||||||
|
|
||||||
public override string ShortName => SHORT_NAME;
|
public override string ShortName => SHORT_NAME;
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
public override string Acronym => Name;
|
public override string Acronym => Name;
|
||||||
public abstract int KeyCount { get; }
|
public abstract int KeyCount { get; }
|
||||||
public override ModType Type => ModType.Conversion;
|
public override ModType Type => ModType.Conversion;
|
||||||
public override double ScoreMultiplier => 0.9;
|
|
||||||
public override bool Ranked => UsesDefaultConfiguration;
|
public override bool Ranked => UsesDefaultConfiguration;
|
||||||
|
|
||||||
public void ApplyToBeatmapConverter(IBeatmapConverter beatmapConverter)
|
public void ApplyToBeatmapConverter(IBeatmapConverter beatmapConverter)
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
|
|
||||||
public override string Acronym => "CS";
|
public override string Acronym => "CS";
|
||||||
|
|
||||||
public override double ScoreMultiplier => 0.9;
|
|
||||||
|
|
||||||
public override LocalisableString Description => "No more tricky speed changes!";
|
public override LocalisableString Description => "No more tricky speed changes!";
|
||||||
|
|
||||||
public override IconUsage? Icon => OsuIcon.ModConstantSpeed;
|
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 LocalisableString Description => @"Decrease the playfield's viewing area.";
|
||||||
|
|
||||||
public override double ScoreMultiplier => 1;
|
|
||||||
|
|
||||||
protected override CoverExpandDirection ExpandDirection => Direction.Value;
|
protected override CoverExpandDirection ExpandDirection => Direction.Value;
|
||||||
|
|
||||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[]
|
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[]
|
||||||
|
|||||||
@@ -7,9 +7,5 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
{
|
{
|
||||||
public class ManiaModDoubleTime : ModDoubleTime, IManiaRateAdjustmentMod
|
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 LocalisableString Description => @"Double the stages, double the fun!";
|
||||||
public override IconUsage? Icon => OsuIcon.ModDualStages;
|
public override IconUsage? Icon => OsuIcon.ModDualStages;
|
||||||
public override ModType Type => ModType.Conversion;
|
public override ModType Type => ModType.Conversion;
|
||||||
public override double ScoreMultiplier => 1;
|
|
||||||
|
|
||||||
private bool isForCurrentRuleset;
|
private bool isForCurrentRuleset;
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
public override string Acronym => "FI";
|
public override string Acronym => "FI";
|
||||||
public override IconUsage? Icon => OsuIcon.ModFadeIn;
|
public override IconUsage? Icon => OsuIcon.ModFadeIn;
|
||||||
public override LocalisableString Description => @"Keys appear out of nowhere!";
|
public override LocalisableString Description => @"Keys appear out of nowhere!";
|
||||||
public override double ScoreMultiplier => 1;
|
|
||||||
public override bool ValidForFreestyleAsRequiredMod => false;
|
public override bool ValidForFreestyleAsRequiredMod => false;
|
||||||
|
|
||||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[]
|
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 partial class ManiaModFlashlight : ModFlashlight<ManiaHitObject>
|
||||||
{
|
{
|
||||||
public override double ScoreMultiplier => 1;
|
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(ModHidden) };
|
public override Type[] IncompatibleMods => new[] { typeof(ModHidden) };
|
||||||
|
|
||||||
public override BindableFloat SizeMultiplier { get; } = new BindableFloat(1)
|
public override BindableFloat SizeMultiplier { get; } = new BindableFloat(1)
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
{
|
{
|
||||||
public class ManiaModHardRock : ModHardRock, IApplicableToHitObject
|
public class ManiaModHardRock : ModHardRock, IApplicableToHitObject
|
||||||
{
|
{
|
||||||
public override double ScoreMultiplier => 1;
|
|
||||||
public override bool Ranked => false;
|
public override bool Ranked => false;
|
||||||
|
|
||||||
public const double HIT_WINDOW_DIFFICULTY_MULTIPLIER = 1.4;
|
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;
|
private const float coverage_increase_per_combo = 0.5f;
|
||||||
|
|
||||||
public override LocalisableString Description => @"Keys fade out before you hit them!";
|
public override LocalisableString Description => @"Keys fade out before you hit them!";
|
||||||
public override double ScoreMultiplier => 1;
|
|
||||||
|
|
||||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[]
|
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 string Acronym => "HO";
|
||||||
|
|
||||||
public override double ScoreMultiplier => 0.9;
|
|
||||||
|
|
||||||
public override LocalisableString Description => @"Replaces all hold notes with normal notes.";
|
public override LocalisableString Description => @"Replaces all hold notes with normal notes.";
|
||||||
|
|
||||||
public override IconUsage? Icon => OsuIcon.ModHoldOff;
|
public override IconUsage? Icon => OsuIcon.ModHoldOff;
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
public override string Name => "Invert";
|
public override string Name => "Invert";
|
||||||
|
|
||||||
public override string Acronym => "IN";
|
public override string Acronym => "IN";
|
||||||
public override double ScoreMultiplier => 1;
|
|
||||||
|
|
||||||
public override LocalisableString Description => "Hold the keys. To the beat.";
|
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
|
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 LocalisableString Description => "No more timing the end of hold notes.";
|
||||||
|
|
||||||
public override double ScoreMultiplier => 0.9;
|
|
||||||
|
|
||||||
public override IconUsage? Icon => OsuIcon.ModNoRelease;
|
public override IconUsage? Icon => OsuIcon.ModNoRelease;
|
||||||
|
|
||||||
public override ModType Type => ModType.DifficultyReduction;
|
public override ModType Type => ModType.DifficultyReduction;
|
||||||
|
|||||||
@@ -0,0 +1,154 @@
|
|||||||
|
// 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.Mania.Mods;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Scoring
|
||||||
|
{
|
||||||
|
public class ManiaScoreMultiplierCalculator : ScoreMultiplierCalculator
|
||||||
|
{
|
||||||
|
public ManiaScoreMultiplierCalculator(ScoreMultiplierContext context)
|
||||||
|
: base(context)
|
||||||
|
{
|
||||||
|
#region Difficulty Reduction
|
||||||
|
|
||||||
|
Single<ManiaModEasy>(hasMultiplier: 0.5);
|
||||||
|
Single<ManiaModNoFail>(hasMultiplier: 0.5);
|
||||||
|
Single<ManiaModHalfTime>(hasMultiplier: halfTime => rateAdjustMultiplier(halfTime.SpeedChange.Value));
|
||||||
|
Single<ManiaModDaycore>(hasMultiplier: daycore => rateAdjustMultiplier(daycore.SpeedChange.Value));
|
||||||
|
Single<ManiaModNoRelease>(hasMultiplier: 0.9);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Difficulty Increase
|
||||||
|
|
||||||
|
// Hard Rock
|
||||||
|
// Sudden Death
|
||||||
|
// Perfect
|
||||||
|
// Double Time
|
||||||
|
// Nightcore
|
||||||
|
// Fade In
|
||||||
|
// Hidden
|
||||||
|
// Cover
|
||||||
|
// Flashlight
|
||||||
|
// Accuracy Challenge
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Conversion
|
||||||
|
|
||||||
|
// Random
|
||||||
|
// Dual Stages
|
||||||
|
// Mirror
|
||||||
|
Single<ManiaModDifficultyAdjust>(hasMultiplier: 0.5);
|
||||||
|
Single<ManiaModClassic>(hasMultiplier: _ => classicMultiplier(Context.Score));
|
||||||
|
// Invert
|
||||||
|
Single<ManiaModConstantSpeed>(hasMultiplier: 0.9);
|
||||||
|
Single<ManiaModHoldOff>(hasMultiplier: 0.9);
|
||||||
|
Single<ManiaModKey1>(hasMultiplier: keyModMultiplier(Context.Score));
|
||||||
|
Single<ManiaModKey2>(hasMultiplier: keyModMultiplier(Context.Score));
|
||||||
|
Single<ManiaModKey3>(hasMultiplier: keyModMultiplier(Context.Score));
|
||||||
|
Single<ManiaModKey4>(hasMultiplier: keyModMultiplier(Context.Score));
|
||||||
|
Single<ManiaModKey5>(hasMultiplier: keyModMultiplier(Context.Score));
|
||||||
|
Single<ManiaModKey6>(hasMultiplier: keyModMultiplier(Context.Score));
|
||||||
|
Single<ManiaModKey7>(hasMultiplier: keyModMultiplier(Context.Score));
|
||||||
|
Single<ManiaModKey8>(hasMultiplier: keyModMultiplier(Context.Score));
|
||||||
|
Single<ManiaModKey9>(hasMultiplier: keyModMultiplier(Context.Score));
|
||||||
|
Single<ManiaModKey10>(hasMultiplier: keyModMultiplier(Context.Score));
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Automation
|
||||||
|
|
||||||
|
// Autoplay
|
||||||
|
// Cinema
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Fun
|
||||||
|
|
||||||
|
Single<ModWindUp>(hasMultiplier: 0.5);
|
||||||
|
Single<ModWindDown>(hasMultiplier: 0.5);
|
||||||
|
// Muted
|
||||||
|
Single<ModAdaptiveSpeed>(hasMultiplier: 0.5);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region System
|
||||||
|
|
||||||
|
// Score V2
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double rateAdjustMultiplier(double speedChange)
|
||||||
|
{
|
||||||
|
// Round to the nearest multiple of 0.1.
|
||||||
|
double value = (int)(speedChange * 10) / 10.0;
|
||||||
|
|
||||||
|
// Offset back to 0.
|
||||||
|
value -= 1;
|
||||||
|
|
||||||
|
if (speedChange >= 1)
|
||||||
|
return 1 + value / 5;
|
||||||
|
else
|
||||||
|
return 0.6 + value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private const double old_key_mod_multiplier = 1;
|
||||||
|
private const double new_key_mod_multiplier = 0.9;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <para>
|
||||||
|
/// The mod multiplier was changed from 1.0x to 0.9x in https://github.com/ppy/osu/pull/30506
|
||||||
|
/// which was included in the https://osu.ppy.sh/home/changelog/tachyon/2025.718.0 release.
|
||||||
|
/// The replay version was not bumped in the change, meaning that the only usable indicator
|
||||||
|
/// of the mod multiplier changing is the client version.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Unfortunately not even the client version is available on server-side recorded replays
|
||||||
|
/// recorded prior to https://github.com/ppy/osu-server-spectator/pull/290,
|
||||||
|
/// which does not appear to have been deployed until August 1
|
||||||
|
/// (https://github.com/ppy/osu-server-spectator/releases/tag/2025.801.0).
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
private double keyModMultiplier(ScoreInfo? scoreInfo)
|
||||||
|
{
|
||||||
|
if (scoreInfo == null)
|
||||||
|
return new_key_mod_multiplier;
|
||||||
|
|
||||||
|
string clientVersion = scoreInfo.ClientVersion;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(clientVersion))
|
||||||
|
{
|
||||||
|
string[] pieces = clientVersion.Split('.');
|
||||||
|
|
||||||
|
if (int.TryParse(pieces[0], out int year) && int.TryParse(pieces[1], out int monthDay))
|
||||||
|
{
|
||||||
|
if (year < 2025 || (year == 2025 && monthDay < 718))
|
||||||
|
return old_key_mod_multiplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new_key_mod_multiplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client version not available, fallback to doing the best we can with the score's timestamp.
|
||||||
|
if (scoreInfo.Date < new DateTimeOffset(2025, 7, 18, 0, 0, 0, TimeSpan.Zero))
|
||||||
|
return old_key_mod_multiplier;
|
||||||
|
|
||||||
|
return new_key_mod_multiplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double classicMultiplier(ScoreInfo? score)
|
||||||
|
{
|
||||||
|
if (score != null && score.TotalScoreVersion < 30000017)
|
||||||
|
return 0.96;
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestTouchInputPlaceHitCircleDirectly()
|
public void TestTouchInputPlaceHitCircleDirectly()
|
||||||
{
|
{
|
||||||
AddStep("tap circle", () => tap(this.ChildrenOfType<EditorRadioButton>().Single(b => b.Button.Label == "HitCircle")));
|
AddStep("tap circle", () => tap(this.ChildrenOfType<EditorRadioButton>().Single(b => b.Button.Label == "Hit circle")));
|
||||||
|
|
||||||
AddStep("tap to place circle", () => tap(this.ChildrenOfType<Playfield>().Single()));
|
AddStep("tap to place circle", () => tap(this.ChildrenOfType<Playfield>().Single()));
|
||||||
AddAssert("circle placed correctly", () =>
|
AddAssert("circle placed correctly", () =>
|
||||||
@@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestTouchInputPlaceCircleAfterTouchingComposeArea()
|
public void TestTouchInputPlaceCircleAfterTouchingComposeArea()
|
||||||
{
|
{
|
||||||
AddStep("tap circle", () => tap(this.ChildrenOfType<EditorRadioButton>().Single(b => b.Button.Label == "HitCircle")));
|
AddStep("tap circle", () => tap(this.ChildrenOfType<EditorRadioButton>().Single(b => b.Button.Label == "Hit circle")));
|
||||||
|
|
||||||
AddStep("tap playfield", () => tap(this.ChildrenOfType<Playfield>().Single()));
|
AddStep("tap playfield", () => tap(this.ChildrenOfType<Playfield>().Single()));
|
||||||
AddAssert("circle placed", () => EditorBeatmap.HitObjects.Single(h => h.StartTime == EditorClock.CurrentTimeAccurate) is HitCircle);
|
AddAssert("circle placed", () => EditorBeatmap.HitObjects.Single(h => h.StartTime == EditorClock.CurrentTimeAccurate) is HitCircle);
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ using osu.Framework.Input;
|
|||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Osu.Edit;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
@@ -130,5 +133,74 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
AddAssert("slider has correct velocity", () => slider!.Velocity, () => Is.EqualTo(velocityBefore));
|
AddAssert("slider has correct velocity", () => slider!.Velocity, () => Is.EqualTo(velocityBefore));
|
||||||
AddAssert("slider has correct duration", () => slider!.Duration, () => Is.EqualTo(durationBefore));
|
AddAssert("slider has correct duration", () => slider!.Duration, () => Is.EqualTo(durationBefore));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestVelocityToolbox()
|
||||||
|
{
|
||||||
|
ExpandableSlider<double> velocitySlider = null!;
|
||||||
|
ExpandableButton useLastSliderButton = null!;
|
||||||
|
|
||||||
|
AddStep("enter editor", () => Game.ScreenStack.Push(new EditorLoader()));
|
||||||
|
AddUntilStep("wait for editor load", () => editor?.ReadyForUse == true);
|
||||||
|
AddStep("retrieve controls", () =>
|
||||||
|
{
|
||||||
|
var toolbox = this.ChildrenOfType<OsuSliderVelocityToolboxGroup>().Single();
|
||||||
|
velocitySlider = toolbox.ChildrenOfType<ExpandableSlider<double>>().Single();
|
||||||
|
useLastSliderButton = toolbox.ChildrenOfType<ExpandableButton>().Single();
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("velocity slider at 1x", () => velocitySlider.Current.Value, () => Is.EqualTo(1));
|
||||||
|
AddStep("expand right toolbox", () => InputManager.MoveMouseTo(this.ChildrenOfType<ExpandingToolboxContainer>().Last()));
|
||||||
|
AddUntilStep("wait for expand", () => useLastSliderButton.Expanded.Value, () => Is.True);
|
||||||
|
AddAssert("use last slider button disabled", () => useLastSliderButton.Enabled.Value, () => Is.False);
|
||||||
|
|
||||||
|
AddStep("seek to 5000", () => editorClock.Seek(5000));
|
||||||
|
AddStep("set 2x velocity", () => velocitySlider.Current.Value = 2);
|
||||||
|
placeSlider();
|
||||||
|
AddAssert("placed slider has 2x velocity", () => editorBeatmap.HitObjects.OfType<Slider>().Last().SliderVelocityMultiplier, () => Is.EqualTo(2));
|
||||||
|
AddStep("expand right toolbox", () => InputManager.MoveMouseTo(this.ChildrenOfType<ExpandingToolboxContainer>().Last()));
|
||||||
|
AddUntilStep("wait for expand", () => useLastSliderButton.Expanded.Value, () => Is.True);
|
||||||
|
AddAssert("use last slider button enabled", () => useLastSliderButton.Enabled.Value, () => Is.True);
|
||||||
|
|
||||||
|
AddStep("seek to 6000", () => editorClock.Seek(6000));
|
||||||
|
placeSlider();
|
||||||
|
AddAssert("placed slider has 2x velocity", () => editorBeatmap.HitObjects.OfType<Slider>().Last().SliderVelocityMultiplier, () => Is.EqualTo(2));
|
||||||
|
AddStep("expand right toolbox", () => InputManager.MoveMouseTo(this.ChildrenOfType<ExpandingToolboxContainer>().Last()));
|
||||||
|
AddUntilStep("wait for expand", () => useLastSliderButton.Expanded.Value, () => Is.True);
|
||||||
|
AddAssert("use last slider button enabled", () => useLastSliderButton.Enabled.Value, () => Is.True);
|
||||||
|
|
||||||
|
AddStep("seek to 9000", () => editorClock.Seek(9000));
|
||||||
|
AddStep("set 3x velocity", () => velocitySlider.Current.Value = 3);
|
||||||
|
placeSlider();
|
||||||
|
AddAssert("placed slider has 3x velocity", () => editorBeatmap.HitObjects.OfType<Slider>().Last().SliderVelocityMultiplier, () => Is.EqualTo(3));
|
||||||
|
AddStep("expand right toolbox", () => InputManager.MoveMouseTo(this.ChildrenOfType<ExpandingToolboxContainer>().Last()));
|
||||||
|
AddUntilStep("wait for expand", () => useLastSliderButton.Expanded.Value, () => Is.True);
|
||||||
|
AddAssert("use last slider button enabled", () => useLastSliderButton.Enabled.Value, () => Is.True);
|
||||||
|
|
||||||
|
AddStep("seek to 10000", () => editorClock.Seek(10000));
|
||||||
|
AddStep("set 1x velocity", () => velocitySlider.Current.Value = 1);
|
||||||
|
AddStep("use last slider velocity instead", () => useLastSliderButton.TriggerClick());
|
||||||
|
placeSlider();
|
||||||
|
AddAssert("placed slider has 3x velocity", () => editorBeatmap.HitObjects.OfType<Slider>().Last().SliderVelocityMultiplier, () => Is.EqualTo(3));
|
||||||
|
AddStep("expand right toolbox", () => InputManager.MoveMouseTo(this.ChildrenOfType<ExpandingToolboxContainer>().Last()));
|
||||||
|
AddUntilStep("wait for expand", () => useLastSliderButton.Expanded.Value, () => Is.True);
|
||||||
|
AddAssert("use last slider button disabled", () => useLastSliderButton.Enabled.Value, () => Is.False);
|
||||||
|
|
||||||
|
AddStep("seek back to 7000", () => editorClock.Seek(7000));
|
||||||
|
placeSlider();
|
||||||
|
AddAssert("placed slider has 2x velocity", () => editorBeatmap.HitObjects.OfType<Slider>().ElementAt(2).SliderVelocityMultiplier, () => Is.EqualTo(2));
|
||||||
|
AddStep("expand right toolbox", () => InputManager.MoveMouseTo(this.ChildrenOfType<ExpandingToolboxContainer>().Last()));
|
||||||
|
AddUntilStep("wait for expand", () => useLastSliderButton.Expanded.Value, () => Is.True);
|
||||||
|
AddAssert("use last slider button disabled", () => useLastSliderButton.Enabled.Value, () => Is.False);
|
||||||
|
|
||||||
|
void placeSlider()
|
||||||
|
{
|
||||||
|
AddStep("enter slider placement mode", () => InputManager.Key(Key.Number3));
|
||||||
|
AddStep("move mouse to top left", () => InputManager.MoveMouseTo(editor.ChildrenOfType<Playfield>().First().ScreenSpaceDrawQuad.TopLeft + new Vector2(50)));
|
||||||
|
AddStep("start placement", () => InputManager.Click(MouseButton.Left));
|
||||||
|
AddStep("move mouse to bottom right", () => InputManager.MoveMouseTo(editor.ChildrenOfType<Playfield>().First().ScreenSpaceDrawQuad.BottomRight - new Vector2(50)));
|
||||||
|
AddStep("end placement", () => InputManager.Click(MouseButton.Right));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
|||||||
{
|
{
|
||||||
public partial class TestSceneOsuModDifficultyAdjust : OsuModTestScene
|
public partial class TestSceneOsuModDifficultyAdjust : OsuModTestScene
|
||||||
{
|
{
|
||||||
|
protected override bool AllowFail => true;
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestNoAdjustment() => CreateModTest(new ModTestData
|
public void TestNoAdjustment() => CreateModTest(new ModTestData
|
||||||
{
|
{
|
||||||
@@ -72,6 +74,88 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
|||||||
PassCondition = () => checkSomeHit() && checkObjectsPreempt(450)
|
PassCondition = () => checkSomeHit() && checkObjectsPreempt(450)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestScoreMultiplierCorrectWithNoAdjustment() => CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new OsuModDifficultyAdjust(),
|
||||||
|
CreateBeatmap = () => new Beatmap
|
||||||
|
{
|
||||||
|
BeatmapInfo = new BeatmapInfo
|
||||||
|
{
|
||||||
|
Difficulty = new BeatmapDifficulty
|
||||||
|
{
|
||||||
|
CircleSize = 8
|
||||||
|
}
|
||||||
|
},
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 1000 },
|
||||||
|
new HitCircle { StartTime = 2000 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Autoplay = true,
|
||||||
|
PassCondition = () => Player.ScoreProcessor.TotalScore.Value == 1_000_000,
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestScoreMultiplierCorrectWithSingleAdjustment() => CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new OsuModDifficultyAdjust
|
||||||
|
{
|
||||||
|
ApproachRate = { Value = 7.3f }
|
||||||
|
},
|
||||||
|
CreateBeatmap = () => new Beatmap
|
||||||
|
{
|
||||||
|
BeatmapInfo = new BeatmapInfo
|
||||||
|
{
|
||||||
|
Difficulty = new BeatmapDifficulty
|
||||||
|
{
|
||||||
|
CircleSize = 8,
|
||||||
|
ApproachRate = 7,
|
||||||
|
OverallDifficulty = 6,
|
||||||
|
DrainRate = 5,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 1000 },
|
||||||
|
new HitCircle { StartTime = 2000 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Autoplay = true,
|
||||||
|
PassCondition = () => Player.ScoreProcessor.TotalScore.Value == 850_000,
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestScoreMultiplierCorrectWithMultipleAdjustments() => CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new OsuModDifficultyAdjust
|
||||||
|
{
|
||||||
|
ApproachRate = { Value = 6.8f },
|
||||||
|
OverallDifficulty = { Value = 6.6f }
|
||||||
|
},
|
||||||
|
CreateBeatmap = () => new Beatmap
|
||||||
|
{
|
||||||
|
BeatmapInfo = new BeatmapInfo
|
||||||
|
{
|
||||||
|
Difficulty = new BeatmapDifficulty
|
||||||
|
{
|
||||||
|
CircleSize = 8,
|
||||||
|
ApproachRate = 7,
|
||||||
|
OverallDifficulty = 6,
|
||||||
|
DrainRate = 5,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 1000 },
|
||||||
|
new HitCircle { StartTime = 2000 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Autoplay = true,
|
||||||
|
PassCondition = () => Player.ScoreProcessor.TotalScore.Value == 630_000,
|
||||||
|
});
|
||||||
|
|
||||||
private bool checkObjectsPreempt(double target)
|
private bool checkObjectsPreempt(double target)
|
||||||
{
|
{
|
||||||
var objects = Player.ChildrenOfType<DrawableHitCircle>();
|
var objects = Player.ChildrenOfType<DrawableHitCircle>();
|
||||||
|
|||||||
@@ -0,0 +1,297 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Tests.Rulesets;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests
|
||||||
|
{
|
||||||
|
public class OsuScoreMultiplierTest : RulesetScoreMultiplierTest
|
||||||
|
{
|
||||||
|
public OsuScoreMultiplierTest()
|
||||||
|
: base(new OsuRuleset())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly object[][] test_cases =
|
||||||
|
[
|
||||||
|
#region Difficulty Reduction
|
||||||
|
|
||||||
|
[new Mod[] { new OsuModEasy() }, 0.8],
|
||||||
|
[new Mod[] { new OsuModEasy { Retries = { Value = 1 } } }, 0.8],
|
||||||
|
[new Mod[] { new OsuModEasy { Retries = { Value = 3 } } }, 0.7],
|
||||||
|
[new Mod[] { new OsuModEasy { Retries = { Value = 5 } } }, 0.5],
|
||||||
|
[new Mod[] { new OsuModEasy { Retries = { Value = 8 } } }, 0.4],
|
||||||
|
|
||||||
|
[new Mod[] { new OsuModNoFail() }, 0.5],
|
||||||
|
|
||||||
|
[new Mod[] { new OsuModHalfTime { SpeedChange = { Value = 0.50 } } }, 0.20],
|
||||||
|
[new Mod[] { new OsuModHalfTime { SpeedChange = { Value = 0.55 } } }, 0.27],
|
||||||
|
[new Mod[] { new OsuModHalfTime { SpeedChange = { Value = 0.60 } } }, 0.34],
|
||||||
|
[new Mod[] { new OsuModHalfTime { SpeedChange = { Value = 0.65 } } }, 0.41],
|
||||||
|
[new Mod[] { new OsuModHalfTime { SpeedChange = { Value = 0.70 } } }, 0.48],
|
||||||
|
[new Mod[] { new OsuModHalfTime { SpeedChange = { Value = 0.75 } } }, 0.55],
|
||||||
|
[new Mod[] { new OsuModHalfTime { SpeedChange = { Value = 0.80 } } }, 0.62],
|
||||||
|
[new Mod[] { new OsuModHalfTime { SpeedChange = { Value = 0.85 } } }, 0.69],
|
||||||
|
[new Mod[] { new OsuModHalfTime { SpeedChange = { Value = 0.90 } } }, 0.76],
|
||||||
|
[new Mod[] { new OsuModHalfTime { SpeedChange = { Value = 0.95 } } }, 0.83],
|
||||||
|
[new Mod[] { new OsuModHalfTime { SpeedChange = { Value = 0.99 } } }, 0.83],
|
||||||
|
|
||||||
|
[new Mod[] { new OsuModDaycore { SpeedChange = { Value = 0.50 } } }, 0.20],
|
||||||
|
[new Mod[] { new OsuModDaycore { SpeedChange = { Value = 0.55 } } }, 0.27],
|
||||||
|
[new Mod[] { new OsuModDaycore { SpeedChange = { Value = 0.60 } } }, 0.34],
|
||||||
|
[new Mod[] { new OsuModDaycore { SpeedChange = { Value = 0.65 } } }, 0.41],
|
||||||
|
[new Mod[] { new OsuModDaycore { SpeedChange = { Value = 0.70 } } }, 0.48],
|
||||||
|
[new Mod[] { new OsuModDaycore { SpeedChange = { Value = 0.75 } } }, 0.55],
|
||||||
|
[new Mod[] { new OsuModDaycore { SpeedChange = { Value = 0.80 } } }, 0.62],
|
||||||
|
[new Mod[] { new OsuModDaycore { SpeedChange = { Value = 0.85 } } }, 0.69],
|
||||||
|
[new Mod[] { new OsuModDaycore { SpeedChange = { Value = 0.90 } } }, 0.76],
|
||||||
|
[new Mod[] { new OsuModDaycore { SpeedChange = { Value = 0.95 } } }, 0.83],
|
||||||
|
[new Mod[] { new OsuModDaycore { SpeedChange = { Value = 0.99 } } }, 0.83],
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Difficulty Increase
|
||||||
|
|
||||||
|
[new Mod[] { new OsuModHardRock() }, 1.09],
|
||||||
|
[new Mod[] { new OsuModSuddenDeath() }, 1],
|
||||||
|
[new Mod[] { new OsuModPerfect() }, 1],
|
||||||
|
|
||||||
|
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.01 } } }, 1.000],
|
||||||
|
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.05 } } }, 1.000],
|
||||||
|
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.10 } } }, 1.036],
|
||||||
|
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.15 } } }, 1.036],
|
||||||
|
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.20 } } }, 1.082],
|
||||||
|
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.25 } } }, 1.082],
|
||||||
|
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.30 } } }, 1.128],
|
||||||
|
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.35 } } }, 1.128],
|
||||||
|
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.40 } } }, 1.174],
|
||||||
|
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.45 } } }, 1.174],
|
||||||
|
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.50 } } }, 1.230],
|
||||||
|
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.55 } } }, 1.230],
|
||||||
|
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.60 } } }, 1.266],
|
||||||
|
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.65 } } }, 1.266],
|
||||||
|
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.70 } } }, 1.312],
|
||||||
|
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.75 } } }, 1.312],
|
||||||
|
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.80 } } }, 1.358],
|
||||||
|
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.85 } } }, 1.358],
|
||||||
|
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.90 } } }, 1.404],
|
||||||
|
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.95 } } }, 1.404],
|
||||||
|
[new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 2.00 } } }, 1.450],
|
||||||
|
|
||||||
|
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.01 } } }, 1.000],
|
||||||
|
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.05 } } }, 1.000],
|
||||||
|
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.10 } } }, 1.036],
|
||||||
|
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.15 } } }, 1.036],
|
||||||
|
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.20 } } }, 1.082],
|
||||||
|
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.25 } } }, 1.082],
|
||||||
|
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.30 } } }, 1.128],
|
||||||
|
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.35 } } }, 1.128],
|
||||||
|
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.40 } } }, 1.174],
|
||||||
|
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.45 } } }, 1.174],
|
||||||
|
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.50 } } }, 1.230],
|
||||||
|
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.55 } } }, 1.230],
|
||||||
|
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.60 } } }, 1.266],
|
||||||
|
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.65 } } }, 1.266],
|
||||||
|
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.70 } } }, 1.312],
|
||||||
|
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.75 } } }, 1.312],
|
||||||
|
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.80 } } }, 1.358],
|
||||||
|
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.85 } } }, 1.358],
|
||||||
|
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.90 } } }, 1.404],
|
||||||
|
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 1.95 } } }, 1.404],
|
||||||
|
[new Mod[] { new OsuModNightcore { SpeedChange = { Value = 2.00 } } }, 1.450],
|
||||||
|
|
||||||
|
[new Mod[] { new OsuModHidden() }, 1.04],
|
||||||
|
[new Mod[] { new OsuModHidden { OnlyFadeApproachCircles = { Value = true } } }, 1.02],
|
||||||
|
|
||||||
|
[new Mod[] { new OsuModTraceable() }, 1.02],
|
||||||
|
|
||||||
|
[new Mod[] { new OsuModFlashlight() }, 1.2],
|
||||||
|
[new Mod[] { new OsuModFlashlight { SizeMultiplier = { Value = 0.5f } } }, 1.2],
|
||||||
|
[new Mod[] { new OsuModFlashlight { SizeMultiplier = { Value = 0.9f } } }, 1.2],
|
||||||
|
[new Mod[] { new OsuModFlashlight { SizeMultiplier = { Value = 1.1f } } }, 1.18],
|
||||||
|
[new Mod[] { new OsuModFlashlight { SizeMultiplier = { Value = 1.5f } } }, 1.1],
|
||||||
|
[new Mod[] { new OsuModFlashlight { SizeMultiplier = { Value = 1.9f } } }, 1.02],
|
||||||
|
[new Mod[] { new OsuModFlashlight { SizeMultiplier = { Value = 2f } } }, 1.02],
|
||||||
|
[new Mod[] { new OsuModFlashlight { ComboBasedSize = { Value = false } } }, 1.04],
|
||||||
|
[new Mod[] { new OsuModFlashlight { SizeMultiplier = { Value = 1.9f }, ComboBasedSize = { Value = false } } }, 1.004],
|
||||||
|
|
||||||
|
[new Mod[] { new OsuModBlinds() }, 1.24],
|
||||||
|
[new Mod[] { new OsuModStrictTracking() }, 1],
|
||||||
|
[new Mod[] { new OsuModAccuracyChallenge() }, 1],
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Conversion
|
||||||
|
|
||||||
|
[new Mod[] { new OsuModTargetPractice() }, 0.01],
|
||||||
|
[new Mod[] { new OsuModDifficultyAdjust() }, 1],
|
||||||
|
|
||||||
|
[new Mod[] { new OsuModClassic() }, 0.985],
|
||||||
|
[new Mod[] { new OsuModClassic { ClassicNoteLock = { Value = false } } }, 0.96],
|
||||||
|
|
||||||
|
[new Mod[] { new OsuModRandom() }, 0.7],
|
||||||
|
[new Mod[] { new OsuModMirror() }, 1],
|
||||||
|
[new Mod[] { new OsuModAlternate() }, 1],
|
||||||
|
[new Mod[] { new OsuModSingleTap() }, 1],
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Automation
|
||||||
|
|
||||||
|
[new Mod[] { new OsuModAutoplay() }, 1],
|
||||||
|
[new Mod[] { new OsuModCinema() }, 1],
|
||||||
|
[new Mod[] { new OsuModRelax() }, 0.1],
|
||||||
|
[new Mod[] { new OsuModAutopilot() }, 0.1],
|
||||||
|
[new Mod[] { new OsuModSpunOut() }, 0.95],
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Fun
|
||||||
|
|
||||||
|
[new Mod[] { new OsuModTransform() }, 1],
|
||||||
|
[new Mod[] { new OsuModWiggle() }, 1],
|
||||||
|
[new Mod[] { new OsuModSpinIn() }, 1],
|
||||||
|
[new Mod[] { new OsuModGrow() }, 1],
|
||||||
|
|
||||||
|
[new Mod[] { new OsuModDeflate() }, 1],
|
||||||
|
[new Mod[] { new OsuModDeflate { StartScale = { Value = 5 } } }, 0.94],
|
||||||
|
|
||||||
|
[new Mod[] { new ModWindUp() }, 0.8 * 1 + 0.2 * 1.230],
|
||||||
|
[new Mod[] { new ModWindUp { InitialRate = { Value = 0.7 }, FinalRate = { Value = 1.2 } } }, 0.8 * 0.48 + 0.2 * 1.082],
|
||||||
|
[new Mod[] { new ModWindUp { InitialRate = { Value = 0.7 }, FinalRate = { Value = 0.9 } } }, 0.8 * 0.48 + 0.2 * 0.76],
|
||||||
|
[new Mod[] { new ModWindUp { InitialRate = { Value = 1.1 }, FinalRate = { Value = 1.4 } } }, 0.8 * 1.036 + 0.2 * 1.174],
|
||||||
|
|
||||||
|
[new Mod[] { new ModWindDown() }, 0.8 * 0.55 + 0.2 * 1],
|
||||||
|
[new Mod[] { new ModWindDown { InitialRate = { Value = 1.2 }, FinalRate = { Value = 0.7 } } }, 0.8 * 0.48 + 0.2 * 1.082],
|
||||||
|
[new Mod[] { new ModWindDown { InitialRate = { Value = 0.9 }, FinalRate = { Value = 0.7 } } }, 0.8 * 0.48 + 0.2 * 0.76],
|
||||||
|
[new Mod[] { new ModWindDown { InitialRate = { Value = 1.4 }, FinalRate = { Value = 1.1 } } }, 0.8 * 1.036 + 0.2 * 1.174],
|
||||||
|
|
||||||
|
[new Mod[] { new OsuModBarrelRoll() }, 1],
|
||||||
|
[new Mod[] { new OsuModApproachDifferent() }, 0.7],
|
||||||
|
[new Mod[] { new OsuModMuted() }, 1],
|
||||||
|
[new Mod[] { new OsuModNoScope() }, 1],
|
||||||
|
|
||||||
|
[new Mod[] { new OsuModMagnetised() }, 0.4],
|
||||||
|
[new Mod[] { new OsuModMagnetised { AttractionStrength = { Value = 0.05f } } }, 0.67],
|
||||||
|
[new Mod[] { new OsuModMagnetised { AttractionStrength = { Value = 0.2f } } }, 0.58],
|
||||||
|
[new Mod[] { new OsuModMagnetised { AttractionStrength = { Value = 0.7f } } }, 0.28],
|
||||||
|
[new Mod[] { new OsuModMagnetised { AttractionStrength = { Value = 1 } } }, 0.1],
|
||||||
|
|
||||||
|
[new Mod[] { new OsuModRepel() }, 1],
|
||||||
|
[new Mod[] { new ModAdaptiveSpeed() }, 0.1],
|
||||||
|
[new Mod[] { new OsuModFreezeFrame() }, 1],
|
||||||
|
[new Mod[] { new OsuModBubbles() }, 1],
|
||||||
|
[new Mod[] { new OsuModSynesthesia() }, 0.99],
|
||||||
|
[new Mod[] { new OsuModDepth() }, 1],
|
||||||
|
[new Mod[] { new OsuModBloom() }, 1],
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region System
|
||||||
|
|
||||||
|
[new Mod[] { new OsuModTouchDevice() }, 1],
|
||||||
|
[new Mod[] { new ModScoreV2() }, 1],
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Combinations
|
||||||
|
|
||||||
|
[new Mod[] { new OsuModHidden(), new OsuModHardRock() }, 1.04 * 1.09],
|
||||||
|
|
||||||
|
[new Mod[] { new OsuModHidden(), new OsuModWiggle() }, 1.02],
|
||||||
|
[new Mod[] { new OsuModHidden(), new OsuModGrow() }, 1.02],
|
||||||
|
[new Mod[] { new OsuModHidden(), new OsuModDeflate() }, 1.02],
|
||||||
|
[new Mod[] { new OsuModHidden(), new OsuModDeflate { StartScale = { Value = 4 } } }, 1.02 * 0.96],
|
||||||
|
[new Mod[] { new OsuModHidden(), new OsuModRepel() }, 1.02],
|
||||||
|
[new Mod[] { new OsuModHidden { OnlyFadeApproachCircles = { Value = true } }, new OsuModRepel() }, 1],
|
||||||
|
[new Mod[] { new OsuModHidden(), new OsuModDepth() }, 1.02],
|
||||||
|
[new Mod[] { new OsuModHidden(), new OsuModDepth(), new OsuModHardRock() }, 1.02 * 1.09],
|
||||||
|
|
||||||
|
[new Mod[] { new OsuModHidden(), new OsuModBlinds() }, 1.24],
|
||||||
|
[new Mod[] { new OsuModHidden(), new OsuModBlinds(), new OsuModHardRock() }, 1.24 * 1.09],
|
||||||
|
|
||||||
|
[new Mod[] { new OsuModTraceable(), new OsuModBlinds() }, 1.24],
|
||||||
|
[new Mod[] { new OsuModTraceable(), new OsuModBlinds(), new OsuModHardRock() }, 1.24 * 1.09],
|
||||||
|
|
||||||
|
[new Mod[] { new OsuModFlashlight(), new OsuModFreezeFrame() }, 1.1],
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
];
|
||||||
|
|
||||||
|
[TestCaseSource(nameof(test_cases))]
|
||||||
|
public void TestMultipliers(Mod[] mods, double expectedMultiplier)
|
||||||
|
=> TestModCombination(mods, expectedMultiplier);
|
||||||
|
|
||||||
|
[TestCase(null, null, null, null, 1)]
|
||||||
|
[TestCase(2.9f, null, null, null, 0.95)]
|
||||||
|
[TestCase(3.1f, null, null, null, 0.95)]
|
||||||
|
[TestCase(null, 3.9f, null, null, 0.95)]
|
||||||
|
[TestCase(null, 4.1f, null, null, 0.95)]
|
||||||
|
[TestCase(null, null, 4.9f, null, 0.95)]
|
||||||
|
[TestCase(null, null, 5.1f, null, 0.95)]
|
||||||
|
[TestCase(null, null, null, 5.9f, 0.95)]
|
||||||
|
[TestCase(null, null, null, 6.1f, 0.95)]
|
||||||
|
[TestCase(2.9f, 3.9f, null, null, 0.95 * 0.95)]
|
||||||
|
[TestCase(2.9f, 3.9f, 4.9f, null, 0.95 * 0.95 * 0.95)]
|
||||||
|
[TestCase(2.9f, 3.9f, 4.9f, 5.9f, 0.95 * 0.95 * 0.95 * 0.95)]
|
||||||
|
[TestCase(0.0f, null, null, null, 0.1)]
|
||||||
|
[TestCase(0.0f, 0.0f, 0.0f, 0.0f, 0.1)]
|
||||||
|
public void TestDifficultyAdjust(float? cs, float? ar, float? od, float? hp, double expectedMultiplier)
|
||||||
|
{
|
||||||
|
var difficulty = new BeatmapDifficulty
|
||||||
|
{
|
||||||
|
CircleSize = 3,
|
||||||
|
ApproachRate = 4,
|
||||||
|
OverallDifficulty = 5,
|
||||||
|
DrainRate = 6,
|
||||||
|
};
|
||||||
|
var mod = new OsuModDifficultyAdjust
|
||||||
|
{
|
||||||
|
CircleSize = { Value = cs },
|
||||||
|
ApproachRate = { Value = ar },
|
||||||
|
OverallDifficulty = { Value = od },
|
||||||
|
DrainRate = { Value = hp },
|
||||||
|
};
|
||||||
|
|
||||||
|
var calculator = Ruleset.CreateScoreMultiplierCalculator(new ScoreMultiplierContext(difficulty));
|
||||||
|
Assert.That(calculator.CalculateFor([mod]), Is.EqualTo(expectedMultiplier).Within(Precision.FLOAT_EPSILON));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase(30000001, 0.96)]
|
||||||
|
[TestCase(30000009, 0.96)]
|
||||||
|
[TestCase(30000016, 0.96)]
|
||||||
|
[TestCase(30000017, 0.985)]
|
||||||
|
[TestCase(null, 0.985)]
|
||||||
|
public void TestClassicMultiplierVersioning(int? totalScoreVersion, double expectedMultiplier)
|
||||||
|
{
|
||||||
|
var scoreInfo = totalScoreVersion != null ? new ScoreInfo { TotalScoreVersion = totalScoreVersion.Value } : null;
|
||||||
|
var calculator = Ruleset.CreateScoreMultiplierCalculator(new ScoreMultiplierContext(new BeatmapDifficulty(), scoreInfo));
|
||||||
|
Assert.That(calculator.CalculateFor([new OsuModClassic()]), Is.EqualTo(expectedMultiplier).Within(Precision.DOUBLE_EPSILON));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void VerySmallModMultiplier()
|
||||||
|
{
|
||||||
|
var mods = new Mod[]
|
||||||
|
{
|
||||||
|
new OsuModEasy { Retries = { Value = 10 } },
|
||||||
|
new OsuModNoFail(),
|
||||||
|
new OsuModHalfTime { SpeedChange = { Value = 0.5 } },
|
||||||
|
new OsuModTargetPractice(),
|
||||||
|
new OsuModClassic { ClassicNoteLock = { Value = false } },
|
||||||
|
new OsuModDeflate { StartScale = { Value = 25 } },
|
||||||
|
new OsuModMagnetised { AttractionStrength = { Value = 1 } },
|
||||||
|
new OsuModSynesthesia(),
|
||||||
|
};
|
||||||
|
var calculator = Ruleset.CreateScoreMultiplierCalculator(new ScoreMultiplierContext(new BeatmapDifficulty()));
|
||||||
|
Assert.That(calculator.CalculateFor(mods), Is.GreaterThan(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -792,7 +792,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
AddStep("export beatmap", () =>
|
AddStep("export beatmap", () =>
|
||||||
{
|
{
|
||||||
var beatmapEncoder = new LegacyBeatmapEncoder(playableBeatmap, null);
|
var beatmapEncoder = new LegacyBeatmapEncoder(playableBeatmap, null, null);
|
||||||
|
|
||||||
using (var stream = File.Open(Path.Combine(exportLocation, $"{testCaseName}.osu"), FileMode.Create))
|
using (var stream = File.Open(Path.Combine(exportLocation, $"{testCaseName}.osu"), FileMode.Create))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -54,6 +54,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private EditorClock? editorClock { get; set; }
|
private EditorClock? editorClock { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OsuSliderVelocityToolboxGroup? sliderVelocityToolbox { get; set; }
|
||||||
|
|
||||||
private Bindable<bool> limitedDistanceSnap { get; set; } = null!;
|
private Bindable<bool> limitedDistanceSnap { get; set; } = null!;
|
||||||
|
|
||||||
private readonly IncrementalBSplineBuilder bSplineBuilder = new IncrementalBSplineBuilder { Degree = 4 };
|
private readonly IncrementalBSplineBuilder bSplineBuilder = new IncrementalBSplineBuilder { Degree = 4 };
|
||||||
@@ -111,9 +114,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private EditorBeatmap editorBeatmap { get; set; } = null!;
|
|
||||||
|
|
||||||
public override SnapResult UpdateTimeAndPosition(Vector2 screenSpacePosition, double fallbackTime)
|
public override SnapResult UpdateTimeAndPosition(Vector2 screenSpacePosition, double fallbackTime)
|
||||||
{
|
{
|
||||||
var result = composer?.TrySnapToNearbyObjects(screenSpacePosition, fallbackTime);
|
var result = composer?.TrySnapToNearbyObjects(screenSpacePosition, fallbackTime);
|
||||||
@@ -129,11 +129,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
case SliderPlacementState.Initial:
|
case SliderPlacementState.Initial:
|
||||||
BeginPlacement();
|
BeginPlacement();
|
||||||
|
|
||||||
double? nearestSliderVelocity = (editorBeatmap
|
HitObject.SliderVelocityMultiplier = sliderVelocityToolbox?.SliderVelocity.Value ?? 1;
|
||||||
.HitObjects
|
|
||||||
.LastOrDefault(h => h is Slider && h.GetEndTime() < HitObject.StartTime) as Slider)?.SliderVelocityMultiplier;
|
|
||||||
|
|
||||||
HitObject.SliderVelocityMultiplier = nearestSliderVelocity ?? 1;
|
|
||||||
HitObject.Position = ToLocalSpace(result.ScreenSpacePosition);
|
HitObject.Position = ToLocalSpace(result.ScreenSpacePosition);
|
||||||
|
|
||||||
// Replacing the DifficultyControlPoint above doesn't trigger any kind of invalidation.
|
// Replacing the DifficultyControlPoint above doesn't trigger any kind of invalidation.
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
public partial class FreehandSliderToolboxGroup : EditorToolboxGroup
|
public partial class FreehandSliderToolboxGroup : EditorToolboxGroup
|
||||||
{
|
{
|
||||||
public FreehandSliderToolboxGroup()
|
public FreehandSliderToolboxGroup()
|
||||||
: base("slider")
|
: base("freehand")
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,14 +7,13 @@ using osu.Game.Graphics;
|
|||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Edit.Tools;
|
using osu.Game.Rulesets.Edit.Tools;
|
||||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Edit
|
namespace osu.Game.Rulesets.Osu.Edit
|
||||||
{
|
{
|
||||||
public class HitCircleCompositionTool : CompositionTool
|
public class HitCircleCompositionTool : CompositionTool
|
||||||
{
|
{
|
||||||
public HitCircleCompositionTool()
|
public HitCircleCompositionTool()
|
||||||
: base(nameof(HitCircle))
|
: base("Hit circle")
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -75,6 +75,9 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
[Cached(typeof(IDistanceSnapProvider))]
|
[Cached(typeof(IDistanceSnapProvider))]
|
||||||
public readonly OsuDistanceSnapProvider DistanceSnapProvider = new OsuDistanceSnapProvider();
|
public readonly OsuDistanceSnapProvider DistanceSnapProvider = new OsuDistanceSnapProvider();
|
||||||
|
|
||||||
|
[Cached]
|
||||||
|
private readonly OsuSliderVelocityToolboxGroup sliderVelocityToolboxGroup = new OsuSliderVelocityToolboxGroup();
|
||||||
|
|
||||||
[Cached]
|
[Cached]
|
||||||
protected readonly OsuGridToolboxGroup OsuGridToolboxGroup = new OsuGridToolboxGroup();
|
protected readonly OsuGridToolboxGroup OsuGridToolboxGroup = new OsuGridToolboxGroup();
|
||||||
|
|
||||||
@@ -111,6 +114,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
RightToolbox.AddRange(new Drawable[]
|
RightToolbox.AddRange(new Drawable[]
|
||||||
{
|
{
|
||||||
|
sliderVelocityToolboxGroup,
|
||||||
OsuGridToolboxGroup,
|
OsuGridToolboxGroup,
|
||||||
new TransformToolboxGroup
|
new TransformToolboxGroup
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,213 @@
|
|||||||
|
// 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.Linq;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Caching;
|
||||||
|
using osu.Framework.Extensions.LocalisationExtensions;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Screens.Edit;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Edit
|
||||||
|
{
|
||||||
|
public partial class OsuSliderVelocityToolboxGroup : EditorToolboxGroup
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the last slider's velocity should be used (if available).
|
||||||
|
/// </summary>
|
||||||
|
private bool useLastSliderVelocity;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The slider velocity to be used for new object placements.
|
||||||
|
/// </summary>
|
||||||
|
public IBindable<double> SliderVelocity => sliderVelocity;
|
||||||
|
|
||||||
|
private readonly BindableDouble sliderVelocity = new BindableDouble(1)
|
||||||
|
{
|
||||||
|
Precision = 0.01,
|
||||||
|
MinValue = 0.1,
|
||||||
|
MaxValue = 10,
|
||||||
|
};
|
||||||
|
|
||||||
|
private ExpandableSlider<double> slider = null!;
|
||||||
|
private ExpandableButton useLastSliderButton = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private EditorBeatmap editorBeatmap { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private EditorClock editorClock { get; set; } = null!;
|
||||||
|
|
||||||
|
private bool syncingBindables;
|
||||||
|
private double lastClockPosition = double.NegativeInfinity;
|
||||||
|
private readonly Cached<Slider?> sliderVelocitySourceObject = new Cached<Slider?>();
|
||||||
|
|
||||||
|
public OsuSliderVelocityToolboxGroup()
|
||||||
|
: base("velocity")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Spacing = new Vector2(5);
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
slider = new ExpandableSlider<double>
|
||||||
|
{
|
||||||
|
ExpandedLabelText = "Slider velocity",
|
||||||
|
Current = new BindableDouble(1)
|
||||||
|
{
|
||||||
|
Precision = 0.01,
|
||||||
|
MinValue = 0.1,
|
||||||
|
MaxValue = 10,
|
||||||
|
},
|
||||||
|
KeyboardStep = 0.1f,
|
||||||
|
},
|
||||||
|
useLastSliderButton = new ExpandableButton
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Action = () =>
|
||||||
|
{
|
||||||
|
useLastSliderVelocity = true;
|
||||||
|
sliderVelocitySourceObject.Invalidate();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
// set unconditionally to true initially.
|
||||||
|
// if there is no object available to get the slider velocity from, the code in `Update()` will handle that.
|
||||||
|
useLastSliderVelocity = true;
|
||||||
|
|
||||||
|
sliderVelocity.BindValueChanged(_ => updateSliderFromVelocity(), true);
|
||||||
|
slider.Current.BindValueChanged(_ =>
|
||||||
|
{
|
||||||
|
updateVelocityFromSlider();
|
||||||
|
updateContractedText();
|
||||||
|
});
|
||||||
|
updateContractedText();
|
||||||
|
useLastSliderButton.Expanded.BindValueChanged(_ => sliderVelocitySourceObject.Invalidate());
|
||||||
|
|
||||||
|
editorBeatmap.HitObjectAdded += invalidateSliderVelocitySourceObject;
|
||||||
|
editorBeatmap.HitObjectUpdated += invalidateSliderVelocitySourceObject;
|
||||||
|
editorBeatmap.HitObjectRemoved += invalidateSliderVelocitySourceObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateContractedText()
|
||||||
|
{
|
||||||
|
slider.ContractedLabelText = LocalisableString.Interpolate($@"SV: {slider.Current.Value.ToLocalisableString("N2")}x");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the displayed value of this toolbox's slider from a change to <see cref="SliderVelocity"/>
|
||||||
|
/// (which is the source-of-truth used for new object placements).
|
||||||
|
/// This is only relevant when <see cref="useLastSliderVelocity"/> is true,
|
||||||
|
/// in which case this code is responsible for propagating the velocity from <see cref="sliderVelocitySourceObject"/> to the slider.
|
||||||
|
/// </summary>
|
||||||
|
private void updateSliderFromVelocity()
|
||||||
|
{
|
||||||
|
if (syncingBindables)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!useLastSliderVelocity)
|
||||||
|
return;
|
||||||
|
|
||||||
|
syncingBindables = true;
|
||||||
|
slider.Current.Value = sliderVelocity.Value;
|
||||||
|
syncingBindables = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the value of <see cref="SliderVelocity"/> from a change to the slider's state.
|
||||||
|
/// This change is assumed to be user-provoked, and therefore <see cref="useLastSliderVelocity"/> is switched unconditionally off
|
||||||
|
/// as the presumed intent is to override the velocity from <see cref="sliderVelocitySourceObject"/>.
|
||||||
|
/// </summary>
|
||||||
|
private void updateVelocityFromSlider()
|
||||||
|
{
|
||||||
|
if (syncingBindables)
|
||||||
|
return;
|
||||||
|
|
||||||
|
syncingBindables = true;
|
||||||
|
useLastSliderVelocity = false;
|
||||||
|
sliderVelocity.Value = slider.Current.Value;
|
||||||
|
syncingBindables = false;
|
||||||
|
sliderVelocitySourceObject.Invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void invalidateSliderVelocitySourceObject(HitObject _) => sliderVelocitySourceObject.Invalidate();
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
if (editorClock.CurrentTime != lastClockPosition)
|
||||||
|
{
|
||||||
|
sliderVelocitySourceObject.Invalidate();
|
||||||
|
lastClockPosition = editorClock.CurrentTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Three possible causes of invalidation:
|
||||||
|
// - The user seeked the clock, which means a different velocity source object needs to be used.
|
||||||
|
// - Some change to the beatmap was made, which means the previously-used velocity source object may no longer be the most relevant one.
|
||||||
|
// - The user is interacting with the toolbox in a way that requires a visual state update
|
||||||
|
// (hovered to expand it, clicked the button to use last slider's velocity, or dragged the manual velocity slider).
|
||||||
|
// This is a procedural one, because `sliderVelocitySourceObject` will have been pointing at the correct object already,
|
||||||
|
// but to decrease unnecessary work being done every frame, the invalidation is explicitly re-triggered to update the toolbox state.
|
||||||
|
if (!sliderVelocitySourceObject.IsValid)
|
||||||
|
{
|
||||||
|
var lastSlider = getLastSlider();
|
||||||
|
sliderVelocitySourceObject.Value = lastSlider;
|
||||||
|
|
||||||
|
if (lastSlider == null)
|
||||||
|
{
|
||||||
|
useLastSliderButton.Enabled.Value = false;
|
||||||
|
useLastSliderButton.ExpandedLabelText = "No sliders to get velocity from";
|
||||||
|
useLastSliderButton.ContractedLabelText = default;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
useLastSliderButton.Enabled.Value = useLastSliderButton.Expanded.Value && !useLastSliderVelocity;
|
||||||
|
useLastSliderButton.ExpandedLabelText = useLastSliderVelocity
|
||||||
|
? "Using last slider's velocity"
|
||||||
|
: LocalisableString.Interpolate($@"Use last slider's velocity ({lastSlider.SliderVelocityMultiplier.ToLocalisableString("N2")}x)");
|
||||||
|
useLastSliderButton.ContractedLabelText = $@"current {lastSlider.SliderVelocityMultiplier.ToLocalisableString("N2")}x";
|
||||||
|
if (useLastSliderVelocity)
|
||||||
|
sliderVelocity.Value = lastSlider.SliderVelocityMultiplier;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Slider? getLastSlider()
|
||||||
|
{
|
||||||
|
return editorBeatmap
|
||||||
|
.HitObjects
|
||||||
|
.OfType<Slider>()
|
||||||
|
.LastOrDefault(h => h.StartTime <= editorClock.CurrentTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
if (editorBeatmap.IsNotNull())
|
||||||
|
{
|
||||||
|
editorBeatmap.HitObjectAdded -= invalidateSliderVelocitySourceObject;
|
||||||
|
editorBeatmap.HitObjectUpdated -= invalidateSliderVelocitySourceObject;
|
||||||
|
editorBeatmap.HitObjectRemoved -= invalidateSliderVelocitySourceObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,7 +21,6 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
{
|
{
|
||||||
public abstract partial class InputBlockingMod : Mod, IApplicableToDrawableRuleset<OsuHitObject>, IUpdatableByPlayfield
|
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 Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModRelax), typeof(OsuModCinema) };
|
||||||
public override ModType Type => ModType.Conversion;
|
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 Name => "Approach Different";
|
||||||
public override string Acronym => "AD";
|
public override string Acronym => "AD";
|
||||||
public override LocalisableString Description => "Never trust the approach circles...";
|
public override LocalisableString Description => "Never trust the approach circles...";
|
||||||
public override double ScoreMultiplier => 1;
|
|
||||||
public override IconUsage? Icon => OsuIcon.ModApproachDifferent;
|
public override IconUsage? Icon => OsuIcon.ModApproachDifferent;
|
||||||
|
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(IHidesApproachCircles), typeof(OsuModFreezeFrame) };
|
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 IconUsage? Icon => OsuIcon.ModAutopilot;
|
||||||
public override ModType Type => ModType.Automation;
|
public override ModType Type => ModType.Automation;
|
||||||
public override LocalisableString Description => @"Automatic cursor movement - just follow the rhythm.";
|
public override LocalisableString Description => @"Automatic cursor movement - just follow the rhythm.";
|
||||||
public override double ScoreMultiplier => 0.1;
|
|
||||||
|
|
||||||
public override Type[] IncompatibleMods => new[]
|
public override Type[] IncompatibleMods => new[]
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public override IconUsage? Icon => OsuIcon.ModBlinds;
|
public override IconUsage? Icon => OsuIcon.ModBlinds;
|
||||||
public override ModType Type => ModType.DifficultyIncrease;
|
public override ModType Type => ModType.DifficultyIncrease;
|
||||||
|
|
||||||
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
|
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModFlashlight) };
|
public override Type[] IncompatibleMods => new[] { typeof(OsuModFlashlight) };
|
||||||
public override bool Ranked => true;
|
public override bool Ranked => true;
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public override IconUsage? Icon => OsuIcon.ModBloom;
|
public override IconUsage? Icon => OsuIcon.ModBloom;
|
||||||
public override ModType Type => ModType.Fun;
|
public override ModType Type => ModType.Fun;
|
||||||
public override LocalisableString Description => "The cursor blooms into.. a larger cursor!";
|
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 MIN_SIZE = 1;
|
||||||
protected const float TRANSITION_DURATION = 100;
|
protected const float TRANSITION_DURATION = 100;
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModFlashlight), typeof(OsuModNoScope), typeof(ModTouchDevice) };
|
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 LocalisableString Description => "Don't let their popping distract you!";
|
||||||
|
|
||||||
public override double ScoreMultiplier => 1;
|
|
||||||
|
|
||||||
public override IconUsage? Icon => OsuIcon.ModBubbles;
|
public override IconUsage? Icon => OsuIcon.ModBubbles;
|
||||||
|
|
||||||
public override ModType Type => ModType.Fun;
|
public override ModType Type => ModType.Fun;
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public override IconUsage? Icon => OsuIcon.ModDepth;
|
public override IconUsage? Icon => OsuIcon.ModDepth;
|
||||||
public override ModType Type => ModType.Fun;
|
public override ModType Type => ModType.Fun;
|
||||||
public override LocalisableString Description => "3D. Almost.";
|
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();
|
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);
|
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 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();
|
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModBloom), typeof(OsuModBlinds) }).ToArray();
|
||||||
|
|
||||||
private const double default_follow_delay = 120;
|
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 IconUsage? Icon => OsuIcon.ModFreezeFrame;
|
||||||
|
|
||||||
public override double ScoreMultiplier => 1;
|
|
||||||
|
|
||||||
public override LocalisableString Description => "Burn the notes into your memory.";
|
public override LocalisableString Description => "Burn the notes into your memory.";
|
||||||
|
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
{
|
{
|
||||||
public class OsuModHardRock : ModHardRock, IApplicableToHitObject
|
public class OsuModHardRock : ModHardRock, IApplicableToHitObject
|
||||||
{
|
{
|
||||||
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1;
|
|
||||||
|
|
||||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModMirror)).ToArray();
|
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModMirror)).ToArray();
|
||||||
|
|
||||||
public void ApplyToHitObject(HitObject hitObject)
|
public void ApplyToHitObject(HitObject hitObject)
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public Bindable<bool> OnlyFadeApproachCircles { get; } = new BindableBool();
|
public Bindable<bool> OnlyFadeApproachCircles { get; } = new BindableBool();
|
||||||
|
|
||||||
public override LocalisableString Description => @"Play with no approach circles and fading circles/sliders.";
|
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) };
|
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 IconUsage? Icon => OsuIcon.ModMagnetised;
|
||||||
public override ModType Type => ModType.Fun;
|
public override ModType Type => ModType.Fun;
|
||||||
public override LocalisableString Description => "No need to chase the circles – your cursor is a magnet!";
|
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) };
|
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)]
|
[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 ModType Type => ModType.Fun;
|
||||||
|
|
||||||
public override double ScoreMultiplier => 1;
|
|
||||||
|
|
||||||
[SettingSource("Starting Size", "The initial size multiplier applied to all objects.")]
|
[SettingSource("Starting Size", "The initial size multiplier applied to all objects.")]
|
||||||
public abstract BindableNumber<float> StartScale { get; }
|
public abstract BindableNumber<float> StartScale { get; }
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public override IconUsage? Icon => OsuIcon.ModRepel;
|
public override IconUsage? Icon => OsuIcon.ModRepel;
|
||||||
public override ModType Type => ModType.Fun;
|
public override ModType Type => ModType.Fun;
|
||||||
public override LocalisableString Description => "Hit objects run away!";
|
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) };
|
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)]
|
[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 IconUsage? Icon => OsuIcon.ModSpinIn;
|
||||||
public override ModType Type => ModType.Fun;
|
public override ModType Type => ModType.Fun;
|
||||||
public override LocalisableString Description => "Circles spin in. No approach circles.";
|
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,
|
// 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.
|
// 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 IconUsage? Icon => OsuIcon.ModSpunOut;
|
||||||
public override ModType Type => ModType.Automation;
|
public override ModType Type => ModType.Automation;
|
||||||
public override LocalisableString Description => @"Spinners will be automatically completed.";
|
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 Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(OsuModAutopilot), typeof(OsuModTargetPractice) };
|
||||||
public override bool Ranked => UsesDefaultConfiguration;
|
public override bool Ranked => UsesDefaultConfiguration;
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public override IconUsage? Icon => OsuIcon.ModStrictTracking;
|
public override IconUsage? Icon => OsuIcon.ModStrictTracking;
|
||||||
public override ModType Type => ModType.DifficultyIncrease;
|
public override ModType Type => ModType.DifficultyIncrease;
|
||||||
public override LocalisableString Description => @"Once you start a slider, follow precisely or get a miss.";
|
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 override Type[] IncompatibleMods => new[] { typeof(ModClassic), typeof(OsuModTargetPractice) };
|
||||||
|
|
||||||
public void ApplyToDrawableHitObject(DrawableHitObject drawable)
|
public void ApplyToDrawableHitObject(DrawableHitObject drawable)
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public override ModType Type => ModType.Conversion;
|
public override ModType Type => ModType.Conversion;
|
||||||
public override IconUsage? Icon => OsuIcon.ModTargetPractice;
|
public override IconUsage? Icon => OsuIcon.ModTargetPractice;
|
||||||
public override LocalisableString Description => @"Practice keeping up with the beat of the song.";
|
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[]
|
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 IconUsage? Icon => OsuIcon.ModTraceable;
|
||||||
public override ModType Type => ModType.DifficultyIncrease;
|
public override ModType Type => ModType.DifficultyIncrease;
|
||||||
public override LocalisableString Description => "Put your faith in the approach circles...";
|
public override LocalisableString Description => "Put your faith in the approach circles...";
|
||||||
public override double ScoreMultiplier => 1;
|
|
||||||
public override bool Ranked => true;
|
public override bool Ranked => true;
|
||||||
|
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(IHidesApproachCircles), typeof(OsuModDepth) };
|
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 IconUsage? Icon => OsuIcon.ModTransform;
|
||||||
public override ModType Type => ModType.Fun;
|
public override ModType Type => ModType.Fun;
|
||||||
public override LocalisableString Description => "Everything rotates. EVERYTHING.";
|
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();
|
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModWiggle), typeof(OsuModMagnetised), typeof(OsuModRepel), typeof(OsuModFreezeFrame), typeof(OsuModDepth) }).ToArray();
|
||||||
|
|
||||||
private float theta;
|
private float theta;
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public override IconUsage? Icon => OsuIcon.ModWiggle;
|
public override IconUsage? Icon => OsuIcon.ModWiggle;
|
||||||
public override ModType Type => ModType.Fun;
|
public override ModType Type => ModType.Fun;
|
||||||
public override LocalisableString Description => "They just won't stay still...";
|
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) };
|
public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform), typeof(OsuModMagnetised), typeof(OsuModRepel), typeof(OsuModDepth) };
|
||||||
|
|
||||||
private const int wiggle_duration = 100; // (ms) Higher = fewer wiggles
|
private const int wiggle_duration = 100; // (ms) Higher = fewer wiggles
|
||||||
|
|||||||
@@ -234,6 +234,14 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override ScoreMultiplierCalculator CreateScoreMultiplierCalculator(ScoreMultiplierContext context)
|
||||||
|
{
|
||||||
|
if (context.Score != null && context.Score.TotalScoreVersion < 30000017)
|
||||||
|
return new OsuScoreMultiplierCalculatorV1(context);
|
||||||
|
|
||||||
|
return new OsuScoreMultiplierCalculatorV2(context);
|
||||||
|
}
|
||||||
|
|
||||||
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetOsu };
|
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetOsu };
|
||||||
|
|
||||||
public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new OsuDifficultyCalculator(RulesetInfo, beatmap);
|
public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new OsuDifficultyCalculator(RulesetInfo, beatmap);
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
// 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.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Scoring
|
||||||
|
{
|
||||||
|
public class OsuScoreMultiplierCalculatorV1 : ScoreMultiplierCalculator
|
||||||
|
{
|
||||||
|
public OsuScoreMultiplierCalculatorV1(ScoreMultiplierContext context)
|
||||||
|
: base(context)
|
||||||
|
{
|
||||||
|
#region Difficulty Reduction
|
||||||
|
|
||||||
|
Single<OsuModEasy>(hasMultiplier: 0.5);
|
||||||
|
Single<OsuModNoFail>(hasMultiplier: 0.5);
|
||||||
|
Single<OsuModHalfTime>(hasMultiplier: halfTime => rateAdjustMultiplier(halfTime.SpeedChange.Value));
|
||||||
|
Single<OsuModDaycore>(hasMultiplier: daycore => rateAdjustMultiplier(daycore.SpeedChange.Value));
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Difficulty Increase
|
||||||
|
|
||||||
|
Single<OsuModHardRock>(hasMultiplier: hardRock => hardRock.UsesDefaultConfiguration ? 1.06 : 1);
|
||||||
|
// Sudden Death
|
||||||
|
// Perfect
|
||||||
|
Single<OsuModDoubleTime>(hasMultiplier: doubleTime => rateAdjustMultiplier(doubleTime.SpeedChange.Value));
|
||||||
|
Single<OsuModNightcore>(hasMultiplier: nightcore => rateAdjustMultiplier(nightcore.SpeedChange.Value));
|
||||||
|
Single<OsuModHidden>(hasMultiplier: hidden => hidden.UsesDefaultConfiguration ? 1.06 : 1);
|
||||||
|
// Traceable
|
||||||
|
Single<OsuModFlashlight>(hasMultiplier: flashlight => flashlight.UsesDefaultConfiguration ? 1.12 : 1);
|
||||||
|
Single<OsuModBlinds>(hasMultiplier: blinds => blinds.UsesDefaultConfiguration ? 1.12 : 1);
|
||||||
|
// Strict Tracking
|
||||||
|
// Accuracy Challenge
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Conversion
|
||||||
|
|
||||||
|
Single<OsuModTargetPractice>(hasMultiplier: 0.1);
|
||||||
|
Single<OsuModDifficultyAdjust>(hasMultiplier: 0.5);
|
||||||
|
Single<OsuModClassic>(hasMultiplier: 0.96);
|
||||||
|
// Random
|
||||||
|
// Mirror
|
||||||
|
// Alternate
|
||||||
|
// Single Tap
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Automation
|
||||||
|
|
||||||
|
// Autoplay
|
||||||
|
// Cinema
|
||||||
|
Single<OsuModRelax>(hasMultiplier: 0.1);
|
||||||
|
Single<OsuModAutopilot>(hasMultiplier: 0.1);
|
||||||
|
Single<OsuModSpunOut>(hasMultiplier: 0.9);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Fun
|
||||||
|
|
||||||
|
// Transform
|
||||||
|
// Wiggle
|
||||||
|
// Spin In
|
||||||
|
// Grow
|
||||||
|
// Deflate
|
||||||
|
Single<ModWindUp>(hasMultiplier: 0.5);
|
||||||
|
Single<ModWindDown>(hasMultiplier: 0.5);
|
||||||
|
// Barrel Roll
|
||||||
|
// Approach Different
|
||||||
|
// Muted
|
||||||
|
// No Scope
|
||||||
|
Single<OsuModMagnetised>(hasMultiplier: 0.5);
|
||||||
|
// Repel
|
||||||
|
Single<ModAdaptiveSpeed>(hasMultiplier: 0.5);
|
||||||
|
// Freeze Frame
|
||||||
|
// Bubbles
|
||||||
|
Single<OsuModSynesthesia>(hasMultiplier: 0.8);
|
||||||
|
// Depth
|
||||||
|
// Bloom
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double rateAdjustMultiplier(double speedChange)
|
||||||
|
{
|
||||||
|
// Round to the nearest multiple of 0.1.
|
||||||
|
double value = (int)(speedChange * 10) / 10.0;
|
||||||
|
|
||||||
|
// Offset back to 0.
|
||||||
|
value -= 1;
|
||||||
|
|
||||||
|
if (speedChange >= 1)
|
||||||
|
return 1 + value / 5;
|
||||||
|
else
|
||||||
|
return 0.6 + value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
// 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.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Scoring
|
||||||
|
{
|
||||||
|
public class OsuScoreMultiplierCalculatorV2 : ScoreMultiplierCalculator
|
||||||
|
{
|
||||||
|
public OsuScoreMultiplierCalculatorV2(ScoreMultiplierContext context)
|
||||||
|
: base(context)
|
||||||
|
{
|
||||||
|
#region Difficulty Reduction
|
||||||
|
|
||||||
|
Single<OsuModEasy>(hasMultiplier: easyMultiplier);
|
||||||
|
Single<OsuModNoFail>(hasMultiplier: 0.5);
|
||||||
|
Single<OsuModHalfTime>(hasMultiplier: halfTime => halfTimeMultiplier(halfTime.SpeedChange.Value));
|
||||||
|
Single<OsuModDaycore>(hasMultiplier: daycore => halfTimeMultiplier(daycore.SpeedChange.Value));
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Difficulty Increase
|
||||||
|
|
||||||
|
Single<OsuModHardRock>(hasMultiplier: 1.09);
|
||||||
|
// Sudden Death (1.0x)
|
||||||
|
// Perfect (1.0x)
|
||||||
|
Single<OsuModDoubleTime>(hasMultiplier: doubleTime => doubleTimeMultiplier(doubleTime.SpeedChange.Value));
|
||||||
|
Single<OsuModNightcore>(hasMultiplier: nightcore => doubleTimeMultiplier(nightcore.SpeedChange.Value));
|
||||||
|
|
||||||
|
const double blinds_multiplier = 1.24;
|
||||||
|
|
||||||
|
Combination<OsuModHidden, OsuModBlinds>(hasMultiplier: (_, _) => blinds_multiplier);
|
||||||
|
|
||||||
|
Combination<OsuModHidden, OsuModWiggle>(hasMultiplier: (hidden, _) => hiddenMultiplier(hidden, otherModsProvideTimingInfo: true));
|
||||||
|
Combination<OsuModHidden, OsuModGrow>(hasMultiplier: (hidden, _) => hiddenMultiplier(hidden, otherModsProvideTimingInfo: true));
|
||||||
|
Combination<OsuModHidden, OsuModDeflate>(hasMultiplier: (hidden, deflate) => hiddenMultiplier(hidden, otherModsProvideTimingInfo: true) * deflateMultiplier(deflate));
|
||||||
|
Combination<OsuModHidden, OsuModRepel>(hasMultiplier: (hidden, _) => hiddenMultiplier(hidden, otherModsProvideTimingInfo: true));
|
||||||
|
Combination<OsuModHidden, OsuModDepth>(hasMultiplier: (hidden, _) => hiddenMultiplier(hidden, otherModsProvideTimingInfo: true));
|
||||||
|
|
||||||
|
Single<OsuModHidden>(hasMultiplier: hidden => hiddenMultiplier(hidden, otherModsProvideTimingInfo: false));
|
||||||
|
|
||||||
|
Combination<OsuModTraceable, OsuModBlinds>(hasMultiplier: (_, _) => blinds_multiplier);
|
||||||
|
Single<OsuModTraceable>(hasMultiplier: 1.02);
|
||||||
|
|
||||||
|
Combination<OsuModFlashlight, OsuModFreezeFrame>(hasMultiplier: (flashlight, _) => 1 + (flashlightMultiplier(flashlight) - 1) / 2);
|
||||||
|
Single<OsuModFlashlight>(hasMultiplier: flashlightMultiplier);
|
||||||
|
|
||||||
|
Single<OsuModBlinds>(hasMultiplier: blinds_multiplier);
|
||||||
|
// Strict Tracking (1.0x)
|
||||||
|
// Accuracy Challenge (1.0x)
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Conversion
|
||||||
|
|
||||||
|
Single<OsuModTargetPractice>(hasMultiplier: 0.01);
|
||||||
|
Single<OsuModDifficultyAdjust>(hasMultiplier: difficultyAdjust => difficultyAdjustMultiplier(difficultyAdjust, Context.BeatmapDifficultyWithoutMods));
|
||||||
|
Single<OsuModClassic>(hasMultiplier: classic => classic.ClassicNoteLock.Value ? 0.985 : 0.96);
|
||||||
|
Single<OsuModRandom>(hasMultiplier: 0.7);
|
||||||
|
// Mirror (1.0x)
|
||||||
|
// Alternate (1.0x)
|
||||||
|
// Single Tap (1.0x)
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Automation
|
||||||
|
|
||||||
|
// Autoplay (1.0x)
|
||||||
|
// Cinema (1.0x)
|
||||||
|
Single<OsuModRelax>(hasMultiplier: 0.1);
|
||||||
|
Single<OsuModAutopilot>(hasMultiplier: 0.1);
|
||||||
|
Single<OsuModSpunOut>(hasMultiplier: 0.95);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Fun
|
||||||
|
|
||||||
|
// Transform (1.0x)
|
||||||
|
// Wiggle (1.0x)
|
||||||
|
// Spin In (1.0x)
|
||||||
|
// Grow (1.0x)
|
||||||
|
Single<OsuModDeflate>(hasMultiplier: deflateMultiplier);
|
||||||
|
Single<ModWindUp>(hasMultiplier: timeRampMultiplier);
|
||||||
|
Single<ModWindDown>(hasMultiplier: timeRampMultiplier);
|
||||||
|
// Barrel Roll (1.0x)
|
||||||
|
Single<OsuModApproachDifferent>(hasMultiplier: 0.7);
|
||||||
|
// Muted (1.0x)
|
||||||
|
// No Scope (1.0x)
|
||||||
|
Single<OsuModMagnetised>(hasMultiplier: magnetised => 0.7 - magnetised.AttractionStrength.Value * 0.6);
|
||||||
|
// Repel (1.0x)
|
||||||
|
Single<ModAdaptiveSpeed>(hasMultiplier: 0.1);
|
||||||
|
// Freeze Frame (1.0x)
|
||||||
|
// Bubbles (1.0x)
|
||||||
|
Single<OsuModSynesthesia>(hasMultiplier: 0.99);
|
||||||
|
// Depth (1.0x)
|
||||||
|
// Bloom (1.0x)
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region System
|
||||||
|
|
||||||
|
// Touch Device (1.0x)
|
||||||
|
// Score V2 (1.0x)
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double easyMultiplier(OsuModEasy easy)
|
||||||
|
{
|
||||||
|
// 0.8x base multiplier
|
||||||
|
// Reduce by 0.1x per extra life
|
||||||
|
double value = 0.8 - Math.Max(0, 0.1 * (easy.Retries.Value - easy.Retries.Default));
|
||||||
|
|
||||||
|
return Math.Max(0.4, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double halfTimeMultiplier(double speedChange)
|
||||||
|
{
|
||||||
|
// 0.2x at 0.5x speed, +0.07x per 0.05x speed increment.
|
||||||
|
// Default HT (0.75x) = 0.55
|
||||||
|
return (int)(speedChange * 20) / 20.0 * 1.4 - 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double doubleTimeMultiplier(double speedChange)
|
||||||
|
{
|
||||||
|
// Floor to the nearest multiple of 0.1.
|
||||||
|
double value = (int)(speedChange * 10) / 10.0;
|
||||||
|
|
||||||
|
// 0.01 penalty for non-default rates.
|
||||||
|
double penalty = value != 1.5 && value != 1.0 ? 0.01 : 0.0;
|
||||||
|
|
||||||
|
// Linear from 1.0 to 1.46, minus the penalty.
|
||||||
|
// Default DT (1.5x) = 1.23
|
||||||
|
return (value - 1) * 0.46 + 1 - penalty;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double hiddenMultiplier(OsuModHidden hidden, bool otherModsProvideTimingInfo)
|
||||||
|
{
|
||||||
|
double value = 1.04;
|
||||||
|
|
||||||
|
if (hidden.OnlyFadeApproachCircles.Value)
|
||||||
|
value -= 0.02;
|
||||||
|
|
||||||
|
if (otherModsProvideTimingInfo)
|
||||||
|
value -= 0.02;
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double flashlightMultiplier(OsuModFlashlight flashlight)
|
||||||
|
{
|
||||||
|
// Multiplier of 1.2x, reduced by 0.02 per 0.1 increase in flashlight size.
|
||||||
|
double value = Math.Max(1.02, Math.Min(1.2, 1.2 - 0.2 * (flashlight.SizeMultiplier.Value - 1)));
|
||||||
|
|
||||||
|
if (!flashlight.ComboBasedSize.Value)
|
||||||
|
value = 1 + (value - 1) / 5;
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double difficultyAdjustMultiplier(OsuModDifficultyAdjust difficultyAdjust, IBeatmapDifficultyInfo beatmapDifficulty)
|
||||||
|
{
|
||||||
|
double selectedCircleSize = difficultyAdjust.CircleSize.Value ?? beatmapDifficulty.CircleSize;
|
||||||
|
double selectedDrainRate = difficultyAdjust.DrainRate.Value ?? beatmapDifficulty.DrainRate;
|
||||||
|
double selectedOverallDifficulty = difficultyAdjust.OverallDifficulty.Value ?? beatmapDifficulty.OverallDifficulty;
|
||||||
|
double selectedApproachRate = difficultyAdjust.ApproachRate.Value ?? beatmapDifficulty.ApproachRate;
|
||||||
|
|
||||||
|
double csDifference = Math.Abs(selectedCircleSize - beatmapDifficulty.CircleSize);
|
||||||
|
double hpDifference = Math.Abs(selectedDrainRate - beatmapDifficulty.DrainRate);
|
||||||
|
double odDifference = Math.Abs(selectedOverallDifficulty - beatmapDifficulty.OverallDifficulty);
|
||||||
|
double arDifference = Math.Abs(selectedApproachRate - beatmapDifficulty.ApproachRate);
|
||||||
|
|
||||||
|
// Per parameter, reduce multiplier by 0.05x per 0.1 change.
|
||||||
|
double csMultiplier = Math.Max(0.1, 1.0 - csDifference * 0.5);
|
||||||
|
double hpMultiplier = Math.Max(0.1, 1.0 - hpDifference * 0.5);
|
||||||
|
double odMultiplier = Math.Max(0.1, 1.0 - odDifference * 0.5);
|
||||||
|
double arMultiplier = Math.Max(0.1, 1.0 - arDifference * 0.5);
|
||||||
|
|
||||||
|
return Math.Max(0.1, csMultiplier * hpMultiplier * odMultiplier * arMultiplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double timeRampMultiplier(ModTimeRamp timeRamp)
|
||||||
|
{
|
||||||
|
double minSpeed = Math.Min(timeRamp.InitialRate.Value, timeRamp.FinalRate.Value);
|
||||||
|
double maxSpeed = Math.Max(timeRamp.InitialRate.Value, timeRamp.FinalRate.Value);
|
||||||
|
|
||||||
|
double minMultiplier = minSpeed < 1 ? halfTimeMultiplier(minSpeed) : doubleTimeMultiplier(minSpeed);
|
||||||
|
double maxMultiplier = maxSpeed < 1 ? halfTimeMultiplier(maxSpeed) : doubleTimeMultiplier(maxSpeed);
|
||||||
|
|
||||||
|
return 0.8 * minMultiplier + 0.2 * maxMultiplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double deflateMultiplier(OsuModDeflate deflate)
|
||||||
|
=> 1.0 - Math.Max(0, 0.02 * (deflate.StartScale.Value - deflate.StartScale.Default));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Rulesets.Taiko.Mods;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Tests.Rulesets;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Tests
|
||||||
|
{
|
||||||
|
public class TaikoScoreMultiplierTest : RulesetScoreMultiplierTest
|
||||||
|
{
|
||||||
|
public TaikoScoreMultiplierTest()
|
||||||
|
: base(new TaikoRuleset())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly object[][] test_cases =
|
||||||
|
[
|
||||||
|
#region Difficulty Reduction
|
||||||
|
|
||||||
|
[new Mod[] { new TaikoModEasy() }, 0.5],
|
||||||
|
[new Mod[] { new TaikoModNoFail() }, 0.5],
|
||||||
|
|
||||||
|
[new Mod[] { new TaikoModHalfTime { SpeedChange = { Value = 0.50 } } }, 0.1],
|
||||||
|
[new Mod[] { new TaikoModHalfTime { SpeedChange = { Value = 0.55 } } }, 0.1],
|
||||||
|
[new Mod[] { new TaikoModHalfTime { SpeedChange = { Value = 0.60 } } }, 0.2],
|
||||||
|
[new Mod[] { new TaikoModHalfTime { SpeedChange = { Value = 0.65 } } }, 0.2],
|
||||||
|
[new Mod[] { new TaikoModHalfTime { SpeedChange = { Value = 0.70 } } }, 0.3],
|
||||||
|
[new Mod[] { new TaikoModHalfTime { SpeedChange = { Value = 0.75 } } }, 0.3],
|
||||||
|
[new Mod[] { new TaikoModHalfTime { SpeedChange = { Value = 0.80 } } }, 0.4],
|
||||||
|
[new Mod[] { new TaikoModHalfTime { SpeedChange = { Value = 0.85 } } }, 0.4],
|
||||||
|
[new Mod[] { new TaikoModHalfTime { SpeedChange = { Value = 0.90 } } }, 0.5],
|
||||||
|
[new Mod[] { new TaikoModHalfTime { SpeedChange = { Value = 0.95 } } }, 0.5],
|
||||||
|
[new Mod[] { new TaikoModHalfTime { SpeedChange = { Value = 0.99 } } }, 0.5],
|
||||||
|
|
||||||
|
[new Mod[] { new TaikoModDaycore { SpeedChange = { Value = 0.50 } } }, 0.1],
|
||||||
|
[new Mod[] { new TaikoModDaycore { SpeedChange = { Value = 0.55 } } }, 0.1],
|
||||||
|
[new Mod[] { new TaikoModDaycore { SpeedChange = { Value = 0.60 } } }, 0.2],
|
||||||
|
[new Mod[] { new TaikoModDaycore { SpeedChange = { Value = 0.65 } } }, 0.2],
|
||||||
|
[new Mod[] { new TaikoModDaycore { SpeedChange = { Value = 0.70 } } }, 0.3],
|
||||||
|
[new Mod[] { new TaikoModDaycore { SpeedChange = { Value = 0.75 } } }, 0.3],
|
||||||
|
[new Mod[] { new TaikoModDaycore { SpeedChange = { Value = 0.80 } } }, 0.4],
|
||||||
|
[new Mod[] { new TaikoModDaycore { SpeedChange = { Value = 0.85 } } }, 0.4],
|
||||||
|
[new Mod[] { new TaikoModDaycore { SpeedChange = { Value = 0.90 } } }, 0.5],
|
||||||
|
[new Mod[] { new TaikoModDaycore { SpeedChange = { Value = 0.95 } } }, 0.5],
|
||||||
|
[new Mod[] { new TaikoModDaycore { SpeedChange = { Value = 0.99 } } }, 0.5],
|
||||||
|
|
||||||
|
[new Mod[] { new TaikoModSimplifiedRhythm() }, 0.6],
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Difficulty Increase
|
||||||
|
|
||||||
|
[new Mod[] { new TaikoModHardRock() }, 1.06],
|
||||||
|
[new Mod[] { new TaikoModSuddenDeath() }, 1],
|
||||||
|
[new Mod[] { new TaikoModPerfect() }, 1],
|
||||||
|
|
||||||
|
[new Mod[] { new TaikoModDoubleTime { SpeedChange = { Value = 1.01 } } }, 1.00],
|
||||||
|
[new Mod[] { new TaikoModDoubleTime { SpeedChange = { Value = 1.05 } } }, 1.00],
|
||||||
|
[new Mod[] { new TaikoModDoubleTime { SpeedChange = { Value = 1.10 } } }, 1.02],
|
||||||
|
[new Mod[] { new TaikoModDoubleTime { SpeedChange = { Value = 1.15 } } }, 1.02],
|
||||||
|
[new Mod[] { new TaikoModDoubleTime { SpeedChange = { Value = 1.20 } } }, 1.04],
|
||||||
|
[new Mod[] { new TaikoModDoubleTime { SpeedChange = { Value = 1.25 } } }, 1.04],
|
||||||
|
[new Mod[] { new TaikoModDoubleTime { SpeedChange = { Value = 1.30 } } }, 1.06],
|
||||||
|
[new Mod[] { new TaikoModDoubleTime { SpeedChange = { Value = 1.35 } } }, 1.06],
|
||||||
|
[new Mod[] { new TaikoModDoubleTime { SpeedChange = { Value = 1.40 } } }, 1.08],
|
||||||
|
[new Mod[] { new TaikoModDoubleTime { SpeedChange = { Value = 1.45 } } }, 1.08],
|
||||||
|
[new Mod[] { new TaikoModDoubleTime { SpeedChange = { Value = 1.50 } } }, 1.10],
|
||||||
|
[new Mod[] { new TaikoModDoubleTime { SpeedChange = { Value = 1.55 } } }, 1.10],
|
||||||
|
[new Mod[] { new TaikoModDoubleTime { SpeedChange = { Value = 1.60 } } }, 1.12],
|
||||||
|
[new Mod[] { new TaikoModDoubleTime { SpeedChange = { Value = 1.65 } } }, 1.12],
|
||||||
|
[new Mod[] { new TaikoModDoubleTime { SpeedChange = { Value = 1.70 } } }, 1.14],
|
||||||
|
[new Mod[] { new TaikoModDoubleTime { SpeedChange = { Value = 1.75 } } }, 1.14],
|
||||||
|
[new Mod[] { new TaikoModDoubleTime { SpeedChange = { Value = 1.80 } } }, 1.16],
|
||||||
|
[new Mod[] { new TaikoModDoubleTime { SpeedChange = { Value = 1.85 } } }, 1.16],
|
||||||
|
[new Mod[] { new TaikoModDoubleTime { SpeedChange = { Value = 1.90 } } }, 1.18],
|
||||||
|
[new Mod[] { new TaikoModDoubleTime { SpeedChange = { Value = 1.95 } } }, 1.18],
|
||||||
|
[new Mod[] { new TaikoModDoubleTime { SpeedChange = { Value = 2.00 } } }, 1.20],
|
||||||
|
|
||||||
|
[new Mod[] { new TaikoModNightcore { SpeedChange = { Value = 1.01 } } }, 1.00],
|
||||||
|
[new Mod[] { new TaikoModNightcore { SpeedChange = { Value = 1.05 } } }, 1.00],
|
||||||
|
[new Mod[] { new TaikoModNightcore { SpeedChange = { Value = 1.10 } } }, 1.02],
|
||||||
|
[new Mod[] { new TaikoModNightcore { SpeedChange = { Value = 1.15 } } }, 1.02],
|
||||||
|
[new Mod[] { new TaikoModNightcore { SpeedChange = { Value = 1.20 } } }, 1.04],
|
||||||
|
[new Mod[] { new TaikoModNightcore { SpeedChange = { Value = 1.25 } } }, 1.04],
|
||||||
|
[new Mod[] { new TaikoModNightcore { SpeedChange = { Value = 1.30 } } }, 1.06],
|
||||||
|
[new Mod[] { new TaikoModNightcore { SpeedChange = { Value = 1.35 } } }, 1.06],
|
||||||
|
[new Mod[] { new TaikoModNightcore { SpeedChange = { Value = 1.40 } } }, 1.08],
|
||||||
|
[new Mod[] { new TaikoModNightcore { SpeedChange = { Value = 1.45 } } }, 1.08],
|
||||||
|
[new Mod[] { new TaikoModNightcore { SpeedChange = { Value = 1.50 } } }, 1.10],
|
||||||
|
[new Mod[] { new TaikoModNightcore { SpeedChange = { Value = 1.55 } } }, 1.10],
|
||||||
|
[new Mod[] { new TaikoModNightcore { SpeedChange = { Value = 1.60 } } }, 1.12],
|
||||||
|
[new Mod[] { new TaikoModNightcore { SpeedChange = { Value = 1.65 } } }, 1.12],
|
||||||
|
[new Mod[] { new TaikoModNightcore { SpeedChange = { Value = 1.70 } } }, 1.14],
|
||||||
|
[new Mod[] { new TaikoModNightcore { SpeedChange = { Value = 1.75 } } }, 1.14],
|
||||||
|
[new Mod[] { new TaikoModNightcore { SpeedChange = { Value = 1.80 } } }, 1.16],
|
||||||
|
[new Mod[] { new TaikoModNightcore { SpeedChange = { Value = 1.85 } } }, 1.16],
|
||||||
|
[new Mod[] { new TaikoModNightcore { SpeedChange = { Value = 1.90 } } }, 1.18],
|
||||||
|
[new Mod[] { new TaikoModNightcore { SpeedChange = { Value = 1.95 } } }, 1.18],
|
||||||
|
[new Mod[] { new TaikoModNightcore { SpeedChange = { Value = 2.00 } } }, 1.20],
|
||||||
|
|
||||||
|
[new Mod[] { new TaikoModHidden() }, 1.06],
|
||||||
|
|
||||||
|
[new Mod[] { new TaikoModFlashlight() }, 1.12],
|
||||||
|
[new Mod[] { new TaikoModFlashlight { ComboBasedSize = { Value = false } } }, 1],
|
||||||
|
|
||||||
|
[new Mod[] { new ModAccuracyChallenge() }, 1],
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Conversion
|
||||||
|
|
||||||
|
[new Mod[] { new TaikoModRandom() }, 1],
|
||||||
|
[new Mod[] { new TaikoModDifficultyAdjust() }, 0.5],
|
||||||
|
[new Mod[] { new TaikoModClassic() }, 1],
|
||||||
|
[new Mod[] { new TaikoModSwap() }, 1],
|
||||||
|
[new Mod[] { new TaikoModSingleTap() }, 1],
|
||||||
|
[new Mod[] { new TaikoModConstantSpeed() }, 0.9],
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Automation
|
||||||
|
|
||||||
|
[new Mod[] { new TaikoModAutoplay() }, 1],
|
||||||
|
[new Mod[] { new TaikoModCinema() }, 1],
|
||||||
|
[new Mod[] { new TaikoModRelax() }, 0.1],
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Fun
|
||||||
|
|
||||||
|
[new Mod[] { new ModWindUp() }, 0.5],
|
||||||
|
[new Mod[] { new ModWindDown() }, 0.5],
|
||||||
|
[new Mod[] { new TaikoModMuted() }, 1],
|
||||||
|
[new Mod[] { new ModAdaptiveSpeed() }, 0.5],
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region System
|
||||||
|
|
||||||
|
[new Mod[] { new ModScoreV2() }, 1],
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Combinations
|
||||||
|
|
||||||
|
[new Mod[] { new TaikoModHidden(), new TaikoModHardRock() }, 1.06 * 1.06]
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
];
|
||||||
|
|
||||||
|
[TestCaseSource(nameof(test_cases))]
|
||||||
|
public void TestMultipliers(Mod[] mods, double expectedMultiplier)
|
||||||
|
=> TestModCombination(mods, expectedMultiplier);
|
||||||
|
|
||||||
|
[TestCase(30000001, 0.96)]
|
||||||
|
[TestCase(30000009, 0.96)]
|
||||||
|
[TestCase(30000016, 0.96)]
|
||||||
|
[TestCase(30000017, 1)]
|
||||||
|
[TestCase(null, 1)]
|
||||||
|
public void TestClassicMultiplierVersioning(int? totalScoreVersion, double expectedMultiplier)
|
||||||
|
{
|
||||||
|
var scoreInfo = totalScoreVersion != null ? new ScoreInfo { TotalScoreVersion = totalScoreVersion.Value } : null;
|
||||||
|
var calculator = Ruleset.CreateScoreMultiplierCalculator(new ScoreMultiplierContext(new BeatmapDifficulty(), scoreInfo));
|
||||||
|
Assert.That(calculator.CalculateFor([new TaikoModClassic()]), Is.EqualTo(expectedMultiplier).Within(Precision.DOUBLE_EPSILON));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
|||||||
currentStoryboard = new Storyboard();
|
currentStoryboard = new Storyboard();
|
||||||
|
|
||||||
for (int i = 0; i < 10; i++)
|
for (int i = 0; i < 10; i++)
|
||||||
currentStoryboard.GetLayer("Foreground").Add(new StoryboardSprite($"test{i}", Anchor.Centre, Vector2.Zero));
|
currentStoryboard.GetLayer("Foreground").Add(new StoryboardSprite(StoryboardElementSource.Beatmap, $"test{i}", Anchor.Centre, Vector2.Zero));
|
||||||
});
|
});
|
||||||
|
|
||||||
CreateTest();
|
CreateTest();
|
||||||
@@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
|||||||
currentStoryboard = new Storyboard();
|
currentStoryboard = new Storyboard();
|
||||||
|
|
||||||
for (int i = 0; i < 10; i++)
|
for (int i = 0; i < 10; i++)
|
||||||
currentStoryboard.GetLayer("Overlay").Add(new StoryboardSprite($"test{i}", Anchor.Centre, Vector2.Zero));
|
currentStoryboard.GetLayer("Overlay").Add(new StoryboardSprite(StoryboardElementSource.Beatmap, $"test{i}", Anchor.Centre, Vector2.Zero));
|
||||||
});
|
});
|
||||||
|
|
||||||
CreateTest();
|
CreateTest();
|
||||||
|
|||||||
@@ -2,22 +2,22 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Edit.Tools;
|
using osu.Game.Rulesets.Edit.Tools;
|
||||||
using osu.Game.Rulesets.Taiko.Edit.Blueprints;
|
using osu.Game.Rulesets.Taiko.Edit.Blueprints;
|
||||||
using osu.Game.Rulesets.Taiko.Objects;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Edit
|
namespace osu.Game.Rulesets.Taiko.Edit
|
||||||
{
|
{
|
||||||
public class DrumRollCompositionTool : CompositionTool
|
public class DrumRollCompositionTool : CompositionTool
|
||||||
{
|
{
|
||||||
public DrumRollCompositionTool()
|
public DrumRollCompositionTool()
|
||||||
: base(nameof(DrumRoll))
|
: base("Drum roll")
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders);
|
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.EditorDrumRoll };
|
||||||
|
|
||||||
public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new DrumRollPlacementBlueprint();
|
public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new DrumRollPlacementBlueprint();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Edit.Tools;
|
using osu.Game.Rulesets.Edit.Tools;
|
||||||
using osu.Game.Rulesets.Taiko.Edit.Blueprints;
|
using osu.Game.Rulesets.Taiko.Edit.Blueprints;
|
||||||
@@ -17,7 +18,7 @@ namespace osu.Game.Rulesets.Taiko.Edit
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles);
|
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.EditorHit };
|
||||||
|
|
||||||
public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new HitPlacementBlueprint();
|
public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new HitPlacementBlueprint();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Edit.Tools;
|
using osu.Game.Rulesets.Edit.Tools;
|
||||||
using osu.Game.Rulesets.Taiko.Edit.Blueprints;
|
using osu.Game.Rulesets.Taiko.Edit.Blueprints;
|
||||||
@@ -17,7 +18,7 @@ namespace osu.Game.Rulesets.Taiko.Edit
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners);
|
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.EditorSwell };
|
||||||
|
|
||||||
public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new SwellPlacementBlueprint();
|
public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new SwellPlacementBlueprint();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,15 @@
|
|||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Taiko.Edit.Blueprints;
|
using osu.Game.Rulesets.Taiko.Edit.Blueprints;
|
||||||
|
using osu.Game.Screens.Edit.Components.TernaryButtons;
|
||||||
using osu.Game.Screens.Edit.Compose.Components;
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@@ -21,6 +26,29 @@ namespace osu.Game.Rulesets.Taiko.Edit
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override Drawable CreateNewComboButton() => new NewComboTernaryButton
|
||||||
|
{
|
||||||
|
Current = NewCombo,
|
||||||
|
CreateIcon = () => new Container
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new SpriteIcon
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomLeft,
|
||||||
|
Origin = Anchor.BottomLeft,
|
||||||
|
Icon = OsuIcon.EditorHit,
|
||||||
|
Size = new Vector2(15),
|
||||||
|
},
|
||||||
|
new SpriteIcon
|
||||||
|
{
|
||||||
|
Icon = OsuIcon.EditorNewComboSparkles,
|
||||||
|
Size = new Vector2(20),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
protected override SelectionHandler<HitObject> CreateSelectionHandler() => new TaikoSelectionHandler();
|
protected override SelectionHandler<HitObject> CreateSelectionHandler() => new TaikoSelectionHandler();
|
||||||
|
|
||||||
public override HitObjectSelectionBlueprint CreateHitObjectBlueprintFor(HitObject hitObject) =>
|
public override HitObjectSelectionBlueprint CreateHitObjectBlueprintFor(HitObject hitObject) =>
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
|||||||
{
|
{
|
||||||
public override string Name => "Constant Speed";
|
public override string Name => "Constant Speed";
|
||||||
public override string Acronym => "CS";
|
public override string Acronym => "CS";
|
||||||
public override double ScoreMultiplier => 0.9;
|
|
||||||
public override LocalisableString Description => "No more tricky speed changes!";
|
public override LocalisableString Description => "No more tricky speed changes!";
|
||||||
public override IconUsage? Icon => OsuIcon.ModConstantSpeed;
|
public override IconUsage? Icon => OsuIcon.ModConstantSpeed;
|
||||||
public override ModType Type => ModType.Conversion;
|
public override ModType Type => ModType.Conversion;
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
|||||||
{
|
{
|
||||||
public partial class TaikoModFlashlight : ModFlashlight<TaikoHitObject>
|
public partial class TaikoModFlashlight : ModFlashlight<TaikoHitObject>
|
||||||
{
|
{
|
||||||
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
|
|
||||||
|
|
||||||
public override BindableFloat SizeMultiplier { get; } = new BindableFloat(1)
|
public override BindableFloat SizeMultiplier { get; } = new BindableFloat(1)
|
||||||
{
|
{
|
||||||
MinValue = 0.5f,
|
MinValue = 0.5f,
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
|||||||
{
|
{
|
||||||
public class TaikoModHardRock : ModHardRock
|
public class TaikoModHardRock : ModHardRock
|
||||||
{
|
{
|
||||||
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Multiplier factor added to the scrolling speed.
|
/// Multiplier factor added to the scrolling speed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
|||||||
public class TaikoModHidden : ModHidden, IApplicableToDrawableRuleset<TaikoHitObject>
|
public class TaikoModHidden : ModHidden, IApplicableToDrawableRuleset<TaikoHitObject>
|
||||||
{
|
{
|
||||||
public override LocalisableString Description => @"Beats fade out before you hit them!";
|
public override LocalisableString Description => @"Beats fade out before you hit them!";
|
||||||
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// How far away from the hit target should hitobjects start to fade out.
|
/// 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 Name => "Simplified Rhythm";
|
||||||
public override string Acronym => "SR";
|
public override string Acronym => "SR";
|
||||||
public override double ScoreMultiplier => 0.6;
|
|
||||||
public override LocalisableString Description => "Simplify tricky rhythms!";
|
public override LocalisableString Description => "Simplify tricky rhythms!";
|
||||||
public override IconUsage? Icon => OsuIcon.ModSimplifiedRhythm;
|
public override IconUsage? Icon => OsuIcon.ModSimplifiedRhythm;
|
||||||
public override ModType Type => ModType.DifficultyReduction;
|
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 LocalisableString Description => @"One key for dons, one key for kats.";
|
||||||
|
|
||||||
public override bool Ranked => true;
|
public override bool Ranked => true;
|
||||||
public override double ScoreMultiplier => 1.0;
|
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModRelax), typeof(TaikoModCinema) };
|
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModRelax), typeof(TaikoModCinema) };
|
||||||
public override ModType Type => ModType.Conversion;
|
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 LocalisableString Description => @"Dons become kats, kats become dons";
|
||||||
public override IconUsage? Icon => OsuIcon.ModSwap;
|
public override IconUsage? Icon => OsuIcon.ModSwap;
|
||||||
public override ModType Type => ModType.Conversion;
|
public override ModType Type => ModType.Conversion;
|
||||||
public override double ScoreMultiplier => 1;
|
|
||||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModRandom)).ToArray();
|
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModRandom)).ToArray();
|
||||||
public override bool Ranked => true;
|
public override bool Ranked => true;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
// 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.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Rulesets.Taiko.Mods;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Scoring
|
||||||
|
{
|
||||||
|
public class TaikoScoreMultiplierCalculator : ScoreMultiplierCalculator
|
||||||
|
{
|
||||||
|
public TaikoScoreMultiplierCalculator(ScoreMultiplierContext context)
|
||||||
|
: base(context)
|
||||||
|
{
|
||||||
|
#region Difficulty Reduction
|
||||||
|
|
||||||
|
Single<TaikoModEasy>(hasMultiplier: 0.5);
|
||||||
|
Single<TaikoModNoFail>(hasMultiplier: 0.5);
|
||||||
|
Single<TaikoModHalfTime>(hasMultiplier: halfTime => rateAdjustMultiplier(halfTime.SpeedChange.Value));
|
||||||
|
Single<TaikoModDaycore>(hasMultiplier: daycore => rateAdjustMultiplier(daycore.SpeedChange.Value));
|
||||||
|
Single<TaikoModSimplifiedRhythm>(hasMultiplier: 0.6);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Difficulty Increase
|
||||||
|
|
||||||
|
Single<TaikoModHardRock>(hasMultiplier: hardRock => hardRock.UsesDefaultConfiguration ? 1.06 : 1);
|
||||||
|
// Sudden Death
|
||||||
|
// Perfect
|
||||||
|
Single<TaikoModDoubleTime>(hasMultiplier: doubleTime => rateAdjustMultiplier(doubleTime.SpeedChange.Value));
|
||||||
|
Single<TaikoModNightcore>(hasMultiplier: nightcore => rateAdjustMultiplier(nightcore.SpeedChange.Value));
|
||||||
|
Single<TaikoModHidden>(hasMultiplier: hidden => hidden.UsesDefaultConfiguration ? 1.06 : 1);
|
||||||
|
Single<TaikoModFlashlight>(hasMultiplier: flashlight => flashlight.UsesDefaultConfiguration ? 1.12 : 1);
|
||||||
|
// Accuracy Challenge
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Conversion
|
||||||
|
|
||||||
|
// Random
|
||||||
|
Single<TaikoModDifficultyAdjust>(hasMultiplier: 0.5);
|
||||||
|
Single<TaikoModClassic>(hasMultiplier: _ => classicMultiplier(Context.Score));
|
||||||
|
// Swap
|
||||||
|
// Single Tap
|
||||||
|
Single<TaikoModConstantSpeed>(hasMultiplier: 0.9);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Automation
|
||||||
|
|
||||||
|
// Autoplay
|
||||||
|
// Cinema
|
||||||
|
Single<TaikoModRelax>(hasMultiplier: 0.1);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Fun
|
||||||
|
|
||||||
|
Single<ModWindUp>(hasMultiplier: 0.5);
|
||||||
|
Single<ModWindDown>(hasMultiplier: 0.5);
|
||||||
|
// Muted
|
||||||
|
Single<ModAdaptiveSpeed>(hasMultiplier: 0.5);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region System
|
||||||
|
|
||||||
|
// Score V2
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double rateAdjustMultiplier(double speedChange)
|
||||||
|
{
|
||||||
|
// Round to the nearest multiple of 0.1.
|
||||||
|
double value = (int)(speedChange * 10) / 10.0;
|
||||||
|
|
||||||
|
// Offset back to 0.
|
||||||
|
value -= 1;
|
||||||
|
|
||||||
|
if (speedChange >= 1)
|
||||||
|
return 1 + value / 5;
|
||||||
|
else
|
||||||
|
return 0.6 + value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double classicMultiplier(ScoreInfo? score)
|
||||||
|
{
|
||||||
|
if (score != null && score.TotalScoreVersion < 30000017)
|
||||||
|
return 0.96;
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -188,6 +188,8 @@ namespace osu.Game.Rulesets.Taiko
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override ScoreMultiplierCalculator CreateScoreMultiplierCalculator(ScoreMultiplierContext context) => new TaikoScoreMultiplierCalculator(context);
|
||||||
|
|
||||||
public override string Description => "osu!taiko";
|
public override string Description => "osu!taiko";
|
||||||
|
|
||||||
public override string ShortName => SHORT_NAME;
|
public override string ShortName => SHORT_NAME;
|
||||||
|
|||||||
@@ -41,14 +41,14 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
|
|
||||||
private static IEnumerable<string> allBeatmaps = beatmaps_resource_store.GetAvailableResources().Where(res => res.EndsWith(".osu", StringComparison.Ordinal));
|
private static IEnumerable<string> allBeatmaps = beatmaps_resource_store.GetAvailableResources().Where(res => res.EndsWith(".osu", StringComparison.Ordinal));
|
||||||
|
|
||||||
|
public record BeatmapComponents(IBeatmap Beatmap, LegacySkin Skin, Storyboard Storyboard);
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestUnsupportedStoryboardEvents()
|
public void TestStoryboardEvents()
|
||||||
{
|
{
|
||||||
const string name = "Resources/storyboard_only_video.osu";
|
const string name = "Resources/storyboard_only_video.osu";
|
||||||
|
|
||||||
var decoded = DecodeFromLegacy(beatmaps_resource_store.GetStream(name), beatmaps_resource_store, name);
|
var decoded = DecodeFromLegacy(beatmaps_resource_store.GetStream(name), beatmaps_resource_store, name);
|
||||||
Assert.That(decoded.beatmap.UnhandledEventLines.Count, Is.EqualTo(1));
|
|
||||||
Assert.That(decoded.beatmap.UnhandledEventLines.Single(), Is.EqualTo("Video,0,\"video.avi\""));
|
|
||||||
|
|
||||||
var memoryStream = EncodeToLegacy(decoded);
|
var memoryStream = EncodeToLegacy(decoded);
|
||||||
|
|
||||||
@@ -63,8 +63,8 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
var decoded = DecodeFromLegacy(beatmaps_resource_store.GetStream(name), beatmaps_resource_store, name);
|
var decoded = DecodeFromLegacy(beatmaps_resource_store.GetStream(name), beatmaps_resource_store, name);
|
||||||
var decodedAfterEncode = DecodeFromLegacy(EncodeToLegacy(decoded), beatmaps_resource_store, name);
|
var decodedAfterEncode = DecodeFromLegacy(EncodeToLegacy(decoded), beatmaps_resource_store, name);
|
||||||
|
|
||||||
Sort(decoded.beatmap);
|
Sort(decoded.Beatmap);
|
||||||
Sort(decodedAfterEncode.beatmap);
|
Sort(decodedAfterEncode.Beatmap);
|
||||||
|
|
||||||
CompareBeatmaps(decoded, decodedAfterEncode);
|
CompareBeatmaps(decoded, decodedAfterEncode);
|
||||||
}
|
}
|
||||||
@@ -76,10 +76,10 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
var decodedAfterEncode = DecodeFromLegacy(EncodeToLegacy(decoded), beatmaps_resource_store, name);
|
var decodedAfterEncode = DecodeFromLegacy(EncodeToLegacy(decoded), beatmaps_resource_store, name);
|
||||||
|
|
||||||
// run an extra convert. this is expected to be stable.
|
// run an extra convert. this is expected to be stable.
|
||||||
decodedAfterEncode.beatmap = convert(decodedAfterEncode.beatmap);
|
decodedAfterEncode = decodedAfterEncode with { Beatmap = convert(decodedAfterEncode.Beatmap) };
|
||||||
|
|
||||||
Sort(decoded.beatmap);
|
Sort(decoded.Beatmap);
|
||||||
Sort(decodedAfterEncode.beatmap);
|
Sort(decodedAfterEncode.Beatmap);
|
||||||
|
|
||||||
CompareBeatmaps(decoded, decodedAfterEncode);
|
CompareBeatmaps(decoded, decodedAfterEncode);
|
||||||
}
|
}
|
||||||
@@ -91,7 +91,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
|
|
||||||
// we are testing that the transfer of relevant data to hitobjects (from legacy control points) sticks through encode/decode.
|
// we are testing that the transfer of relevant data to hitobjects (from legacy control points) sticks through encode/decode.
|
||||||
// before the encode step, the legacy information is removed here.
|
// before the encode step, the legacy information is removed here.
|
||||||
decoded.beatmap.ControlPointInfo = removeLegacyControlPointTypes(decoded.beatmap.ControlPointInfo);
|
decoded.Beatmap.ControlPointInfo = removeLegacyControlPointTypes(decoded.Beatmap.ControlPointInfo);
|
||||||
|
|
||||||
var decodedAfterEncode = DecodeFromLegacy(EncodeToLegacy(decoded), beatmaps_resource_store, name);
|
var decodedAfterEncode = DecodeFromLegacy(EncodeToLegacy(decoded), beatmaps_resource_store, name);
|
||||||
|
|
||||||
@@ -120,17 +120,21 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void CompareBeatmaps((IBeatmap beatmap, TestLegacySkin skin) expected, (IBeatmap beatmap, TestLegacySkin skin) actual)
|
public static void CompareBeatmaps(BeatmapComponents expected, BeatmapComponents actual)
|
||||||
{
|
{
|
||||||
// Check all control points that are still considered to be at a global level.
|
// Check all control points that are still considered to be at a global level.
|
||||||
Assert.That(actual.beatmap.ControlPointInfo.TimingPoints.Serialize(), Is.EqualTo(expected.beatmap.ControlPointInfo.TimingPoints.Serialize()));
|
Assert.That(actual.Beatmap.ControlPointInfo.TimingPoints.Serialize(), Is.EqualTo(expected.Beatmap.ControlPointInfo.TimingPoints.Serialize()));
|
||||||
Assert.That(actual.beatmap.ControlPointInfo.EffectPoints.Serialize(), Is.EqualTo(expected.beatmap.ControlPointInfo.EffectPoints.Serialize()));
|
Assert.That(actual.Beatmap.ControlPointInfo.EffectPoints.Serialize(), Is.EqualTo(expected.Beatmap.ControlPointInfo.EffectPoints.Serialize()));
|
||||||
|
|
||||||
// Check all hitobjects.
|
// Check all hitobjects.
|
||||||
Assert.That(actual.beatmap.HitObjects.Serialize(), Is.EqualTo(expected.beatmap.HitObjects.Serialize()));
|
Assert.That(actual.Beatmap.HitObjects.Serialize(), Is.EqualTo(expected.Beatmap.HitObjects.Serialize()));
|
||||||
|
|
||||||
// Check skin.
|
// Check skin.
|
||||||
ClassicAssert.True(areComboColoursEqual(expected.skin.Configuration, actual.skin.Configuration));
|
ClassicAssert.True(areComboColoursEqual(expected.Skin.Configuration, actual.Skin.Configuration));
|
||||||
|
|
||||||
|
// Do a rough pass on storyboard layers.
|
||||||
|
foreach (string layer in actual.Storyboard.Layers.Concat(expected.Storyboard.Layers).Select(l => l.Name).Distinct())
|
||||||
|
Assert.That(actual.Storyboard.GetLayer(layer).Elements.Count, Is.EqualTo(expected.Storyboard.GetLayer(layer).Elements.Count));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -153,9 +157,9 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var encoded = EncodeToLegacy((beatmap, new TestLegacySkin(beatmaps_resource_store, string.Empty)));
|
var encoded = EncodeToLegacy(new BeatmapComponents(beatmap, new TestLegacySkin(beatmaps_resource_store, string.Empty), new Storyboard()));
|
||||||
var decodedAfterEncode = DecodeFromLegacy(encoded, beatmaps_resource_store, string.Empty);
|
var decodedAfterEncode = DecodeFromLegacy(encoded, beatmaps_resource_store, string.Empty);
|
||||||
var decodedSlider = (Slider)decodedAfterEncode.beatmap.HitObjects[0];
|
var decodedSlider = (Slider)decodedAfterEncode.Beatmap.HitObjects[0];
|
||||||
Assert.That(decodedSlider.Path.ControlPoints.Count, Is.EqualTo(4));
|
Assert.That(decodedSlider.Path.ControlPoints.Count, Is.EqualTo(4));
|
||||||
Assert.That(decodedSlider.Path.ControlPoints[0].Type, Is.EqualTo(PathType.BSpline(3)));
|
Assert.That(decodedSlider.Path.ControlPoints[0].Type, Is.EqualTo(PathType.BSpline(3)));
|
||||||
Assert.That(decodedSlider.Path.ControlPoints[2].Type, Is.EqualTo(PathType.BSpline(3)));
|
Assert.That(decodedSlider.Path.ControlPoints[2].Type, Is.EqualTo(PathType.BSpline(3)));
|
||||||
@@ -183,9 +187,9 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var encoded = EncodeToLegacy((beatmap, new TestLegacySkin(beatmaps_resource_store, string.Empty)));
|
var encoded = EncodeToLegacy(new BeatmapComponents(beatmap, new TestLegacySkin(beatmaps_resource_store, string.Empty), new Storyboard()));
|
||||||
var decodedAfterEncode = DecodeFromLegacy(encoded, beatmaps_resource_store, string.Empty);
|
var decodedAfterEncode = DecodeFromLegacy(encoded, beatmaps_resource_store, string.Empty);
|
||||||
var decodedSlider = (Slider)decodedAfterEncode.beatmap.HitObjects[0];
|
var decodedSlider = (Slider)decodedAfterEncode.Beatmap.HitObjects[0];
|
||||||
Assert.That(decodedSlider.Path.ControlPoints.Count, Is.EqualTo(5));
|
Assert.That(decodedSlider.Path.ControlPoints.Count, Is.EqualTo(5));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,9 +215,9 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var encoded = EncodeToLegacy((new Beatmap(), beatmapSkin));
|
var encoded = EncodeToLegacy(new BeatmapComponents(new Beatmap(), beatmapSkin, new Storyboard()));
|
||||||
var decodedAfterEncode = DecodeFromLegacy(encoded, beatmaps_resource_store, string.Empty);
|
var decodedAfterEncode = DecodeFromLegacy(encoded, beatmaps_resource_store, string.Empty);
|
||||||
Assert.That(decodedAfterEncode.skin.Configuration.CustomComboColours, Has.Count.EqualTo(8));
|
Assert.That(decodedAfterEncode.Skin.Configuration.CustomComboColours, Has.Count.EqualTo(8));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -234,9 +238,9 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
HitObjects = { originalSlider }
|
HitObjects = { originalSlider }
|
||||||
};
|
};
|
||||||
|
|
||||||
var encoded = EncodeToLegacy((beatmap, new TestLegacySkin(beatmaps_resource_store, string.Empty)));
|
var encoded = EncodeToLegacy(new BeatmapComponents(beatmap, new TestLegacySkin(beatmaps_resource_store, string.Empty), new Storyboard()));
|
||||||
var decodedAfterEncode = DecodeFromLegacy(encoded, beatmaps_resource_store, string.Empty, version: LegacyBeatmapEncoder.FIRST_LAZER_VERSION);
|
var decodedAfterEncode = DecodeFromLegacy(encoded, beatmaps_resource_store, string.Empty, version: LegacyBeatmapEncoder.FIRST_LAZER_VERSION);
|
||||||
var decodedSlider = (Slider)decodedAfterEncode.beatmap.HitObjects[0];
|
var decodedSlider = (Slider)decodedAfterEncode.Beatmap.HitObjects[0];
|
||||||
Assert.That(decodedSlider.Path.ControlPoints.Select(p => p.Position),
|
Assert.That(decodedSlider.Path.ControlPoints.Select(p => p.Position),
|
||||||
Is.EquivalentTo(originalSlider.Path.ControlPoints.Select(p => p.Position)));
|
Is.EquivalentTo(originalSlider.Path.ControlPoints.Select(p => p.Position)));
|
||||||
}
|
}
|
||||||
@@ -254,17 +258,17 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var encoded = EncodeToLegacy((beatmap, new TestLegacySkin(beatmaps_resource_store, string.Empty)));
|
var encoded = EncodeToLegacy(new BeatmapComponents(beatmap, new TestLegacySkin(beatmaps_resource_store, string.Empty), new Storyboard()));
|
||||||
var decodedAfterEncode = DecodeFromLegacy(encoded, beatmaps_resource_store, string.Empty);
|
var decodedAfterEncode = DecodeFromLegacy(encoded, beatmaps_resource_store, string.Empty);
|
||||||
|
|
||||||
Assert.That(decodedAfterEncode.beatmap.HitObjects[0].Samples[0].Suffix, Is.Null);
|
Assert.That(decodedAfterEncode.Beatmap.HitObjects[0].Samples[0].Suffix, Is.Null);
|
||||||
Assert.That(decodedAfterEncode.beatmap.HitObjects[0].Samples[0].UseBeatmapSamples, Is.False);
|
Assert.That(decodedAfterEncode.Beatmap.HitObjects[0].Samples[0].UseBeatmapSamples, Is.False);
|
||||||
|
|
||||||
Assert.That(decodedAfterEncode.beatmap.HitObjects[1].Samples[0].Suffix, Is.Null);
|
Assert.That(decodedAfterEncode.Beatmap.HitObjects[1].Samples[0].Suffix, Is.Null);
|
||||||
Assert.That(decodedAfterEncode.beatmap.HitObjects[1].Samples[0].UseBeatmapSamples, Is.True);
|
Assert.That(decodedAfterEncode.Beatmap.HitObjects[1].Samples[0].UseBeatmapSamples, Is.True);
|
||||||
|
|
||||||
Assert.That(decodedAfterEncode.beatmap.HitObjects[2].Samples[0].Suffix, Is.EqualTo("3"));
|
Assert.That(decodedAfterEncode.Beatmap.HitObjects[2].Samples[0].Suffix, Is.EqualTo("3"));
|
||||||
Assert.That(decodedAfterEncode.beatmap.HitObjects[2].Samples[0].UseBeatmapSamples, Is.True);
|
Assert.That(decodedAfterEncode.Beatmap.HitObjects[2].Samples[0].UseBeatmapSamples, Is.True);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool areComboColoursEqual(IHasComboColours a, IHasComboColours b)
|
private static bool areComboColoursEqual(IHasComboColours a, IHasComboColours b)
|
||||||
@@ -289,7 +293,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static (IBeatmap beatmap, TestLegacySkin skin) DecodeFromLegacy(Stream stream, IResourceStore<byte[]> beatmapsResourceStore, string name, int version = LegacyDecoder<Beatmap>.LATEST_VERSION)
|
public static BeatmapComponents DecodeFromLegacy(Stream stream, IResourceStore<byte[]> beatmapsResourceStore, string name, int version = LegacyDecoder<Beatmap>.LATEST_VERSION)
|
||||||
{
|
{
|
||||||
using (var reader = new LineBufferedReader(stream))
|
using (var reader = new LineBufferedReader(stream))
|
||||||
{
|
{
|
||||||
@@ -297,7 +301,9 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
var beatmapSkin = new TestLegacySkin(beatmapsResourceStore, name);
|
var beatmapSkin = new TestLegacySkin(beatmapsResourceStore, name);
|
||||||
stream.Seek(0, SeekOrigin.Begin);
|
stream.Seek(0, SeekOrigin.Begin);
|
||||||
beatmapSkin.Configuration = new LegacySkinDecoder().Decode(reader);
|
beatmapSkin.Configuration = new LegacySkinDecoder().Decode(reader);
|
||||||
return (convert(beatmap), beatmapSkin);
|
stream.Seek(0, SeekOrigin.Begin);
|
||||||
|
var storyboard = new LegacyStoryboardDecoder().Decode(reader);
|
||||||
|
return new BeatmapComponents(convert(beatmap), beatmapSkin, storyboard);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,13 +315,13 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static MemoryStream EncodeToLegacy((IBeatmap beatmap, ISkin skin) fullBeatmap)
|
public static MemoryStream EncodeToLegacy(BeatmapComponents fullBeatmap)
|
||||||
{
|
{
|
||||||
var (beatmap, beatmapSkin) = fullBeatmap;
|
var (beatmap, beatmapSkin, storyboard) = fullBeatmap;
|
||||||
var stream = new MemoryStream();
|
var stream = new MemoryStream();
|
||||||
|
|
||||||
using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true))
|
using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true))
|
||||||
new LegacyBeatmapEncoder(beatmap, beatmapSkin).Encode(writer);
|
new LegacyBeatmapEncoder(beatmap, beatmapSkin, storyboard).Encode(writer);
|
||||||
|
|
||||||
stream.Position = 0;
|
stream.Position = 0;
|
||||||
|
|
||||||
|
|||||||
@@ -552,6 +552,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
[Ignore("Temporarily ignored pending correct addressing of treatment of `TotalScoreVersion` in replays")]
|
||||||
public void TestTotalScoreWithoutModsBackwardsPopulatedIfMissing()
|
public void TestTotalScoreWithoutModsBackwardsPopulatedIfMissing()
|
||||||
{
|
{
|
||||||
var ruleset = new OsuRuleset().RulesetInfo;
|
var ruleset = new OsuRuleset().RulesetInfo;
|
||||||
|
|||||||
@@ -0,0 +1,400 @@
|
|||||||
|
// 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.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.Formats;
|
||||||
|
using osu.Game.IO;
|
||||||
|
using osu.Game.Storyboards;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Beatmaps.Formats
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class LegacyStoryboardEncoderTest
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestBackground()
|
||||||
|
{
|
||||||
|
var initial = createComponents();
|
||||||
|
initial.Beatmap.BeatmapInfo.Metadata.BackgroundFile = "bg.jpg";
|
||||||
|
|
||||||
|
var encoded = encode(initial);
|
||||||
|
var decodedAfterEncode = decode(encoded);
|
||||||
|
|
||||||
|
Assert.That(decodedAfterEncode.Beatmap.BeatmapInfo.Metadata.BackgroundFile, Is.EqualTo("bg.jpg"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBackgroundOffset()
|
||||||
|
{
|
||||||
|
var initial = createComponents();
|
||||||
|
initial.Beatmap.BeatmapInfo.Metadata.BackgroundFile = "bg_offset.jpg";
|
||||||
|
initial.Storyboard.BackgroundOffset = new Vector2(0, 45);
|
||||||
|
|
||||||
|
var encoded = encode(initial);
|
||||||
|
var decodedAfterEncode = decode(encoded);
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(decodedAfterEncode.Beatmap.BeatmapInfo.Metadata.BackgroundFile, Is.EqualTo("bg_offset.jpg"));
|
||||||
|
Assert.That(decodedAfterEncode.Storyboard.BackgroundOffset, Is.EqualTo(new Vector2(0, 45)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestVideos()
|
||||||
|
{
|
||||||
|
var initial = createComponents();
|
||||||
|
|
||||||
|
initial.Storyboard.GetLayer("Video").Add(new StoryboardVideo(StoryboardElementSource.Beatmap, "video1.avi", 0));
|
||||||
|
initial.Storyboard.GetLayer("Video").Add(new StoryboardVideo(StoryboardElementSource.Shared, "video2.mp4", 1234));
|
||||||
|
|
||||||
|
var encoded = encode(initial);
|
||||||
|
var decodedAfterEncode = decode(encoded);
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
var videoLayer = decodedAfterEncode.Storyboard.GetLayer("Video");
|
||||||
|
Assert.That(videoLayer.Elements, Has.Count.EqualTo(2));
|
||||||
|
|
||||||
|
Assert.That(videoLayer.Elements[0].Source, Is.EqualTo(StoryboardElementSource.Beatmap));
|
||||||
|
Assert.That(videoLayer.Elements[0].Path, Is.EqualTo("video1.avi"));
|
||||||
|
Assert.That(videoLayer.Elements[0].StartTime, Is.EqualTo(0));
|
||||||
|
|
||||||
|
Assert.That(videoLayer.Elements[1].Source, Is.EqualTo(StoryboardElementSource.Shared));
|
||||||
|
Assert.That(videoLayer.Elements[1].Path, Is.EqualTo("video2.mp4"));
|
||||||
|
Assert.That(videoLayer.Elements[1].StartTime, Is.EqualTo(1234));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestVideoWithCommands()
|
||||||
|
{
|
||||||
|
var initial = createComponents();
|
||||||
|
|
||||||
|
var video = new StoryboardVideo(StoryboardElementSource.Beatmap, "video1.avi", 0);
|
||||||
|
video.Commands.AddScale(Easing.None, 0, 0, 0.7f, 0.7f);
|
||||||
|
initial.Storyboard.GetLayer("Video").Add(video);
|
||||||
|
|
||||||
|
var encoded = encode(initial);
|
||||||
|
var decodedAfterEncode = decode(encoded);
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
var decodedVideo = (StoryboardVideo)decodedAfterEncode.Storyboard.GetLayer("Video").Elements.Single();
|
||||||
|
|
||||||
|
Assert.That(decodedVideo.Source, Is.EqualTo(StoryboardElementSource.Beatmap));
|
||||||
|
Assert.That(decodedVideo.Path, Is.EqualTo("video1.avi"));
|
||||||
|
Assert.That(decodedVideo.StartTime, Is.EqualTo(0));
|
||||||
|
|
||||||
|
Assert.That(decodedVideo.Commands.Scale, Has.Count.EqualTo(1));
|
||||||
|
var scaleCommand = (decodedVideo.Commands.Scale.Single());
|
||||||
|
Assert.That(scaleCommand.Easing, Is.EqualTo(Easing.None));
|
||||||
|
Assert.That(scaleCommand.StartTime, Is.EqualTo(0));
|
||||||
|
Assert.That(scaleCommand.EndTime, Is.EqualTo(0));
|
||||||
|
Assert.That(scaleCommand.StartValue, Is.EqualTo(0.7f));
|
||||||
|
Assert.That(scaleCommand.EndValue, Is.EqualTo(0.7f));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSpritesAndAnimations()
|
||||||
|
{
|
||||||
|
var initial = createComponents();
|
||||||
|
|
||||||
|
initial.Storyboard.GetLayer("Background").Add(new StoryboardSprite(StoryboardElementSource.Beatmap, "1.png", Anchor.TopLeft, new Vector2()));
|
||||||
|
initial.Storyboard.GetLayer("Fail").Add(new StoryboardSprite(StoryboardElementSource.Shared, "2.png", Anchor.Centre, new Vector2(-3)));
|
||||||
|
initial.Storyboard.GetLayer("Pass").Add(new StoryboardSprite(StoryboardElementSource.Shared, "3.png", Anchor.BottomRight, new Vector2(30, -30)));
|
||||||
|
initial.Storyboard.GetLayer("Foreground").Add(new StoryboardAnimation(StoryboardElementSource.Beatmap, "anim1", Anchor.CentreLeft, new Vector2(30), frameCount: 10, frameDelay: 30, AnimationLoopType.LoopForever));
|
||||||
|
initial.Storyboard.GetLayer("Overlay").Add(new StoryboardAnimation(StoryboardElementSource.Shared, "anim2", Anchor.CentreRight, new Vector2(30), frameCount: 4, frameDelay: 100, AnimationLoopType.LoopOnce));
|
||||||
|
|
||||||
|
var encoded = encode(initial);
|
||||||
|
var decodedAfterEncode = decode(encoded);
|
||||||
|
|
||||||
|
var sb = decodedAfterEncode.Storyboard;
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
var backgroundSprite = (StoryboardSprite)sb.GetLayer("Background").Elements.Single();
|
||||||
|
Assert.That(backgroundSprite.Source, Is.EqualTo(StoryboardElementSource.Beatmap));
|
||||||
|
Assert.That(backgroundSprite.Path, Is.EqualTo("1.png"));
|
||||||
|
Assert.That(backgroundSprite.Origin, Is.EqualTo(Anchor.TopLeft));
|
||||||
|
Assert.That(backgroundSprite.InitialPosition, Is.EqualTo(new Vector2()));
|
||||||
|
|
||||||
|
var failSprite = (StoryboardSprite)sb.GetLayer("Fail").Elements.Single();
|
||||||
|
Assert.That(failSprite.Source, Is.EqualTo(StoryboardElementSource.Shared));
|
||||||
|
Assert.That(failSprite.Path, Is.EqualTo("2.png"));
|
||||||
|
Assert.That(failSprite.Origin, Is.EqualTo(Anchor.Centre));
|
||||||
|
Assert.That(failSprite.InitialPosition, Is.EqualTo(new Vector2(-3)));
|
||||||
|
|
||||||
|
var passSprite = (StoryboardSprite)sb.GetLayer("Pass").Elements.Single();
|
||||||
|
Assert.That(passSprite.Source, Is.EqualTo(StoryboardElementSource.Shared));
|
||||||
|
Assert.That(passSprite.Path, Is.EqualTo("3.png"));
|
||||||
|
Assert.That(passSprite.Origin, Is.EqualTo(Anchor.BottomRight));
|
||||||
|
Assert.That(passSprite.InitialPosition, Is.EqualTo(new Vector2(30, -30)));
|
||||||
|
|
||||||
|
var foregroundAnimation = (StoryboardAnimation)sb.GetLayer("Foreground").Elements.Single();
|
||||||
|
Assert.That(foregroundAnimation.Source, Is.EqualTo(StoryboardElementSource.Beatmap));
|
||||||
|
Assert.That(foregroundAnimation.Path, Is.EqualTo("anim1"));
|
||||||
|
Assert.That(foregroundAnimation.Origin, Is.EqualTo(Anchor.CentreLeft));
|
||||||
|
Assert.That(foregroundAnimation.InitialPosition, Is.EqualTo(new Vector2(30)));
|
||||||
|
Assert.That(foregroundAnimation.FrameCount, Is.EqualTo(10));
|
||||||
|
Assert.That(foregroundAnimation.FrameDelay, Is.EqualTo(30));
|
||||||
|
Assert.That(foregroundAnimation.LoopType, Is.EqualTo(AnimationLoopType.LoopForever));
|
||||||
|
|
||||||
|
var overlayAnimation = (StoryboardAnimation)sb.GetLayer("Overlay").Elements.Single();
|
||||||
|
Assert.That(overlayAnimation.Source, Is.EqualTo(StoryboardElementSource.Shared));
|
||||||
|
Assert.That(overlayAnimation.Path, Is.EqualTo("anim2"));
|
||||||
|
Assert.That(overlayAnimation.Origin, Is.EqualTo(Anchor.CentreRight));
|
||||||
|
Assert.That(overlayAnimation.InitialPosition, Is.EqualTo(new Vector2(30)));
|
||||||
|
Assert.That(overlayAnimation.FrameCount, Is.EqualTo(4));
|
||||||
|
Assert.That(overlayAnimation.FrameDelay, Is.EqualTo(100));
|
||||||
|
Assert.That(overlayAnimation.LoopType, Is.EqualTo(AnimationLoopType.LoopOnce));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCommands()
|
||||||
|
{
|
||||||
|
var initial = createComponents();
|
||||||
|
|
||||||
|
var sprite = new StoryboardSprite(StoryboardElementSource.Beatmap, "test.jpg", Anchor.Centre, new Vector2(300));
|
||||||
|
sprite.Commands.AddAlpha(Easing.InBack, 100, 200, 0, 1);
|
||||||
|
sprite.Commands.AddBlendingParameters(Easing.None, 300, 300, BlendingParameters.Additive, BlendingParameters.Additive);
|
||||||
|
sprite.Commands.AddColour(Easing.InCubic, 400, 500, Color4.White, Color4.Aquamarine);
|
||||||
|
sprite.Commands.AddFlipH(Easing.InOutQuad, 600, 600, true, true);
|
||||||
|
sprite.Commands.AddFlipV(Easing.InOutQuad, 800, 900, true, false);
|
||||||
|
sprite.Commands.AddRotation(Easing.OutSine, 1000, 1100, 0, 720);
|
||||||
|
sprite.Commands.AddScale(Easing.OutQuint, 1200, 1300, 1, 4);
|
||||||
|
sprite.Commands.AddVectorScale(Easing.InCirc, 1400, 1500, new Vector2(4), new Vector2(3, 1));
|
||||||
|
sprite.Commands.AddX(Easing.InOutQuad, 1600, 1700, 300, 500);
|
||||||
|
sprite.Commands.AddY(Easing.OutBounce, 1800, 1800, 300, 100);
|
||||||
|
initial.Storyboard.GetLayer("Background").Add(sprite);
|
||||||
|
|
||||||
|
var encoded = encode(initial);
|
||||||
|
var decodedAfterEncode = decode(encoded);
|
||||||
|
|
||||||
|
var decodedSprite = (StoryboardSprite)decodedAfterEncode.Storyboard.GetLayer("Background").Elements.Single();
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
var alphaCommand = decodedSprite.Commands.Alpha.Single();
|
||||||
|
Assert.That(alphaCommand.Easing, Is.EqualTo(Easing.InBack));
|
||||||
|
Assert.That(alphaCommand.StartTime, Is.EqualTo(100));
|
||||||
|
Assert.That(alphaCommand.EndTime, Is.EqualTo(200));
|
||||||
|
Assert.That(alphaCommand.StartValue, Is.EqualTo(0));
|
||||||
|
Assert.That(alphaCommand.EndValue, Is.EqualTo(1));
|
||||||
|
|
||||||
|
var blendingCommand = decodedSprite.Commands.BlendingParameters.Single();
|
||||||
|
Assert.That(blendingCommand.Easing, Is.EqualTo(Easing.None));
|
||||||
|
Assert.That(blendingCommand.StartTime, Is.EqualTo(300));
|
||||||
|
Assert.That(blendingCommand.EndTime, Is.EqualTo(300));
|
||||||
|
Assert.That(blendingCommand.StartValue, Is.EqualTo(BlendingParameters.Additive));
|
||||||
|
Assert.That(blendingCommand.EndValue, Is.EqualTo(BlendingParameters.Additive));
|
||||||
|
|
||||||
|
var colourCommand = decodedSprite.Commands.Colour.Single();
|
||||||
|
Assert.That(colourCommand.Easing, Is.EqualTo(Easing.InCubic));
|
||||||
|
Assert.That(colourCommand.StartTime, Is.EqualTo(400));
|
||||||
|
Assert.That(colourCommand.EndTime, Is.EqualTo(500));
|
||||||
|
Assert.That(colourCommand.StartValue, Is.EqualTo(Color4.White));
|
||||||
|
Assert.That(colourCommand.EndValue, Is.EqualTo(Color4.Aquamarine));
|
||||||
|
|
||||||
|
var flipHCommand = decodedSprite.Commands.FlipH.Single();
|
||||||
|
Assert.That(flipHCommand.Easing, Is.EqualTo(Easing.InOutQuad));
|
||||||
|
Assert.That(flipHCommand.StartTime, Is.EqualTo(600));
|
||||||
|
Assert.That(flipHCommand.EndTime, Is.EqualTo(600));
|
||||||
|
Assert.That(flipHCommand.StartValue, Is.EqualTo(true));
|
||||||
|
Assert.That(flipHCommand.EndValue, Is.EqualTo(true));
|
||||||
|
|
||||||
|
var flipVCommand = decodedSprite.Commands.FlipV.Single();
|
||||||
|
Assert.That(flipVCommand.Easing, Is.EqualTo(Easing.InOutQuad));
|
||||||
|
Assert.That(flipVCommand.StartTime, Is.EqualTo(800));
|
||||||
|
Assert.That(flipVCommand.EndTime, Is.EqualTo(900));
|
||||||
|
Assert.That(flipVCommand.StartValue, Is.EqualTo(true));
|
||||||
|
Assert.That(flipVCommand.EndValue, Is.EqualTo(false));
|
||||||
|
|
||||||
|
var rotationCommand = decodedSprite.Commands.Rotation.Single();
|
||||||
|
Assert.That(rotationCommand.Easing, Is.EqualTo(Easing.OutSine));
|
||||||
|
Assert.That(rotationCommand.StartTime, Is.EqualTo(1000));
|
||||||
|
Assert.That(rotationCommand.EndTime, Is.EqualTo(1100));
|
||||||
|
Assert.That(rotationCommand.StartValue, Is.EqualTo(0));
|
||||||
|
Assert.That(rotationCommand.EndValue, Is.EqualTo(720));
|
||||||
|
|
||||||
|
var scaleCommand = decodedSprite.Commands.Scale.Single();
|
||||||
|
Assert.That(scaleCommand.Easing, Is.EqualTo(Easing.OutQuint));
|
||||||
|
Assert.That(scaleCommand.StartTime, Is.EqualTo(1200));
|
||||||
|
Assert.That(scaleCommand.EndTime, Is.EqualTo(1300));
|
||||||
|
Assert.That(scaleCommand.StartValue, Is.EqualTo(1));
|
||||||
|
Assert.That(scaleCommand.EndValue, Is.EqualTo(4));
|
||||||
|
|
||||||
|
var vectorScaleCommand = decodedSprite.Commands.VectorScale.Single();
|
||||||
|
Assert.That(vectorScaleCommand.Easing, Is.EqualTo(Easing.InCirc));
|
||||||
|
Assert.That(vectorScaleCommand.StartTime, Is.EqualTo(1400));
|
||||||
|
Assert.That(vectorScaleCommand.EndTime, Is.EqualTo(1500));
|
||||||
|
Assert.That(vectorScaleCommand.StartValue, Is.EqualTo(new Vector2(4)));
|
||||||
|
Assert.That(vectorScaleCommand.EndValue, Is.EqualTo(new Vector2(3, 1)));
|
||||||
|
|
||||||
|
var xCommand = decodedSprite.Commands.X.Single();
|
||||||
|
Assert.That(xCommand.Easing, Is.EqualTo(Easing.InOutQuad));
|
||||||
|
Assert.That(xCommand.StartTime, Is.EqualTo(1600));
|
||||||
|
Assert.That(xCommand.EndTime, Is.EqualTo(1700));
|
||||||
|
Assert.That(xCommand.StartValue, Is.EqualTo(300));
|
||||||
|
Assert.That(xCommand.EndValue, Is.EqualTo(500));
|
||||||
|
|
||||||
|
var yCommand = decodedSprite.Commands.Y.Single();
|
||||||
|
Assert.That(yCommand.Easing, Is.EqualTo(Easing.OutBounce));
|
||||||
|
Assert.That(yCommand.StartTime, Is.EqualTo(1800));
|
||||||
|
Assert.That(yCommand.EndTime, Is.EqualTo(1800));
|
||||||
|
Assert.That(yCommand.StartValue, Is.EqualTo(300));
|
||||||
|
Assert.That(yCommand.EndValue, Is.EqualTo(100));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestLoopingGroup()
|
||||||
|
{
|
||||||
|
var initial = createComponents();
|
||||||
|
|
||||||
|
var sprite = new StoryboardSprite(StoryboardElementSource.Beatmap, "test.jpg", Anchor.Centre, new Vector2(300));
|
||||||
|
var loopingGroup = sprite.AddLoopingGroup(1000, 44);
|
||||||
|
loopingGroup.AddAlpha(Easing.OutQuint, 1000, 1500, 0, 1);
|
||||||
|
initial.Storyboard.GetLayer("Background").Add(sprite);
|
||||||
|
|
||||||
|
var encoded = encode(initial);
|
||||||
|
var decodedAfterEncode = decode(encoded);
|
||||||
|
|
||||||
|
var decodedSprite = (StoryboardSprite)decodedAfterEncode.Storyboard.GetLayer("Background").Elements.Single();
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(decodedSprite.LoopingGroups, Has.Count.EqualTo(1));
|
||||||
|
var decodedLoopingGroup = decodedSprite.LoopingGroups.Single();
|
||||||
|
Assert.That(decodedLoopingGroup.StartTime, Is.EqualTo(1000));
|
||||||
|
Assert.That(decodedLoopingGroup.TotalIterations, Is.EqualTo(45));
|
||||||
|
|
||||||
|
var alphaCommand = decodedLoopingGroup.Alpha.Single();
|
||||||
|
Assert.That(alphaCommand.Easing, Is.EqualTo(Easing.OutQuint));
|
||||||
|
Assert.That(alphaCommand.StartTime, Is.EqualTo(1000));
|
||||||
|
Assert.That(alphaCommand.EndTime, Is.EqualTo(1500));
|
||||||
|
Assert.That(alphaCommand.StartValue, Is.EqualTo(0));
|
||||||
|
Assert.That(alphaCommand.EndValue, Is.EqualTo(1));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestTriggerGroup()
|
||||||
|
{
|
||||||
|
var initial = createComponents();
|
||||||
|
|
||||||
|
var sprite = new StoryboardSprite(StoryboardElementSource.Beatmap, "test.jpg", Anchor.Centre, new Vector2(300));
|
||||||
|
var triggerGroup = sprite.AddTriggerGroup("Passing", 0, 100000, 33);
|
||||||
|
triggerGroup.AddAlpha(Easing.OutQuint, 0, 500, 0, 1);
|
||||||
|
initial.Storyboard.GetLayer("Background").Add(sprite);
|
||||||
|
|
||||||
|
var encoded = encode(initial);
|
||||||
|
var decodedAfterEncode = decode(encoded);
|
||||||
|
|
||||||
|
var decodedSprite = (StoryboardSprite)decodedAfterEncode.Storyboard.GetLayer("Background").Elements.Single();
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(decodedSprite.TriggerGroups, Has.Count.EqualTo(1));
|
||||||
|
var decodedTriggerGroup = decodedSprite.TriggerGroups.Single();
|
||||||
|
Assert.That(decodedTriggerGroup.TriggerName, Is.EqualTo("Passing"));
|
||||||
|
Assert.That(decodedTriggerGroup.TriggerStartTime, Is.EqualTo(0));
|
||||||
|
Assert.That(decodedTriggerGroup.TriggerEndTime, Is.EqualTo(100000));
|
||||||
|
Assert.That(decodedTriggerGroup.GroupNumber, Is.EqualTo(33));
|
||||||
|
|
||||||
|
var alphaCommand = decodedTriggerGroup.Alpha.Single();
|
||||||
|
Assert.That(alphaCommand.Easing, Is.EqualTo(Easing.OutQuint));
|
||||||
|
Assert.That(alphaCommand.StartTime, Is.EqualTo(0));
|
||||||
|
Assert.That(alphaCommand.EndTime, Is.EqualTo(500));
|
||||||
|
Assert.That(alphaCommand.StartValue, Is.EqualTo(0));
|
||||||
|
Assert.That(alphaCommand.EndValue, Is.EqualTo(1));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestStoryboardSamples()
|
||||||
|
{
|
||||||
|
var initial = createComponents();
|
||||||
|
|
||||||
|
initial.Storyboard.GetLayer("Pass").Add(new StoryboardSampleInfo(StoryboardElementSource.Beatmap, "pass.wav", 4000, 85));
|
||||||
|
initial.Storyboard.GetLayer("Fail").Add(new StoryboardSampleInfo(StoryboardElementSource.Shared, "fail.wav", 4000, 100));
|
||||||
|
|
||||||
|
var encoded = encode(initial);
|
||||||
|
var decodedAfterEncode = decode(encoded);
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
var passingSample = (StoryboardSampleInfo)decodedAfterEncode.Storyboard.GetLayer("Pass").Elements.Single();
|
||||||
|
Assert.That(passingSample.Source, Is.EqualTo(StoryboardElementSource.Beatmap));
|
||||||
|
Assert.That(passingSample.Path, Is.EqualTo("pass.wav"));
|
||||||
|
Assert.That(passingSample.StartTime, Is.EqualTo(4000));
|
||||||
|
Assert.That(passingSample.Volume, Is.EqualTo(85));
|
||||||
|
|
||||||
|
var failingSample = (StoryboardSampleInfo)decodedAfterEncode.Storyboard.GetLayer("Fail").Elements.Single();
|
||||||
|
Assert.That(failingSample.Source, Is.EqualTo(StoryboardElementSource.Shared));
|
||||||
|
Assert.That(failingSample.Path, Is.EqualTo("fail.wav"));
|
||||||
|
Assert.That(failingSample.StartTime, Is.EqualTo(4000));
|
||||||
|
Assert.That(failingSample.Volume, Is.EqualTo(100));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private record DecodedBeatmapComponents(IBeatmap Beatmap, Storyboard Storyboard);
|
||||||
|
|
||||||
|
private record EncodedBeatmapComponents(MemoryStream Beatmap, MemoryStream Storyboard);
|
||||||
|
|
||||||
|
private DecodedBeatmapComponents createComponents()
|
||||||
|
{
|
||||||
|
var beatmapInfo = new BeatmapInfo();
|
||||||
|
var beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
BeatmapInfo = beatmapInfo
|
||||||
|
};
|
||||||
|
var storyboard = new Storyboard
|
||||||
|
{
|
||||||
|
Beatmap = beatmap,
|
||||||
|
BeatmapInfo = beatmapInfo
|
||||||
|
};
|
||||||
|
|
||||||
|
return new DecodedBeatmapComponents(beatmap, storyboard);
|
||||||
|
}
|
||||||
|
|
||||||
|
private EncodedBeatmapComponents encode(DecodedBeatmapComponents decoded)
|
||||||
|
{
|
||||||
|
var beatmapStream = new MemoryStream();
|
||||||
|
using (var beatmapWriter = new StreamWriter(beatmapStream, Encoding.UTF8, 1024, leaveOpen: true))
|
||||||
|
new LegacyBeatmapEncoder(decoded.Beatmap, null, decoded.Storyboard).Encode(beatmapWriter);
|
||||||
|
beatmapStream.Position = 0;
|
||||||
|
|
||||||
|
var storyboardStream = new MemoryStream();
|
||||||
|
using (var storyboardWriter = new StreamWriter(storyboardStream, Encoding.UTF8, 1024, leaveOpen: true))
|
||||||
|
new LegacyStoryboardEncoder(decoded.Storyboard).EncodeStandaloneStoryboard(storyboardWriter);
|
||||||
|
storyboardStream.Position = 0;
|
||||||
|
|
||||||
|
return new EncodedBeatmapComponents(beatmapStream, storyboardStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DecodedBeatmapComponents decode(EncodedBeatmapComponents encoded)
|
||||||
|
{
|
||||||
|
using var beatmapReader = new LineBufferedReader(encoded.Beatmap, leaveOpen: true);
|
||||||
|
var beatmap = new LegacyBeatmapDecoder().Decode(beatmapReader);
|
||||||
|
|
||||||
|
encoded.Beatmap.Position = 0;
|
||||||
|
using var storyboardReader = new LineBufferedReader(encoded.Storyboard, leaveOpen: true);
|
||||||
|
var storyboard = new LegacyStoryboardDecoder().Decode(beatmapReader, storyboardReader);
|
||||||
|
|
||||||
|
encoded.Beatmap.Position = 0;
|
||||||
|
encoded.Storyboard.Position = 0;
|
||||||
|
|
||||||
|
return new DecodedBeatmapComponents(beatmap, storyboard);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -122,6 +122,29 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
() => Is.EqualTo(384).Within(0.00001));
|
() => Is.EqualTo(384).Within(0.00001));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBackgroundSpecificationPreserved()
|
||||||
|
{
|
||||||
|
IWorkingBeatmap beatmap = null!;
|
||||||
|
MemoryStream outStream = null!;
|
||||||
|
|
||||||
|
// Ensure importer encoding is correct
|
||||||
|
AddStep("import beatmap", () => beatmap = importBeatmapFromArchives(@"241526 Soleily - Renatus.osz"));
|
||||||
|
AddAssert("beatmap background is correct", () => beatmap.BeatmapInfo.Metadata.BackgroundFile, () => Is.EqualTo("machinetop_background.jpg"));
|
||||||
|
|
||||||
|
// Ensure exporter legacy conversion is correct
|
||||||
|
AddStep("export", () =>
|
||||||
|
{
|
||||||
|
outStream = new MemoryStream();
|
||||||
|
|
||||||
|
new LegacyBeatmapExporter(LocalStorage)
|
||||||
|
.ExportToStream((BeatmapSetInfo)beatmap.BeatmapInfo.BeatmapSet!, outStream, null);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("import beatmap again", () => beatmap = importBeatmapFromStream(outStream));
|
||||||
|
AddAssert("beatmap background is still correct", () => beatmap.BeatmapInfo.Metadata.BackgroundFile, () => Is.EqualTo("machinetop_background.jpg"));
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestExportStability()
|
public void TestExportStability()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ namespace osu.Game.Tests.Editing.Checks
|
|||||||
{
|
{
|
||||||
var storyboard = new Storyboard();
|
var storyboard = new Storyboard();
|
||||||
var layer = storyboard.GetLayer("Video");
|
var layer = storyboard.GetLayer("Video");
|
||||||
layer.Add(new StoryboardVideo("abc123.mp4", 0));
|
layer.Add(new StoryboardVideo(StoryboardElementSource.Beatmap, "abc123.mp4", 0));
|
||||||
|
|
||||||
var mockWorkingBeatmap = new Mock<TestWorkingBeatmap>(beatmap, null!, null!);
|
var mockWorkingBeatmap = new Mock<TestWorkingBeatmap>(beatmap, null!, null!);
|
||||||
mockWorkingBeatmap.Setup(w => w.GetStream(It.IsAny<string>())).Returns(resourceStream);
|
mockWorkingBeatmap.Setup(w => w.GetStream(It.IsAny<string>())).Returns(resourceStream);
|
||||||
|
|||||||
@@ -260,7 +260,7 @@ namespace osu.Game.Tests.Editing.Checks
|
|||||||
if (hasStoryboard)
|
if (hasStoryboard)
|
||||||
{
|
{
|
||||||
storyboard = new Storyboard();
|
storyboard = new Storyboard();
|
||||||
storyboard.GetLayer("Background").Add(new StoryboardSprite("test.png", Anchor.Centre, Vector2.Zero));
|
storyboard.GetLayer("Background").Add(new StoryboardSprite(StoryboardElementSource.Beatmap, "test.png", Anchor.Centre, Vector2.Zero));
|
||||||
}
|
}
|
||||||
|
|
||||||
var verifiedCurrentBeatmap = new BeatmapVerifierContext.VerifiedBeatmap(new TestWorkingBeatmap(currentBeatmap, storyboard), currentBeatmap);
|
var verifiedCurrentBeatmap = new BeatmapVerifierContext.VerifiedBeatmap(new TestWorkingBeatmap(currentBeatmap, storyboard), currentBeatmap);
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ namespace osu.Game.Tests.Editing.Checks
|
|||||||
{
|
{
|
||||||
var storyboard = new Storyboard();
|
var storyboard = new Storyboard();
|
||||||
|
|
||||||
var video = new StoryboardVideo("abc123.mp4", 0);
|
var video = new StoryboardVideo(StoryboardElementSource.Beatmap, "abc123.mp4", 0);
|
||||||
|
|
||||||
storyboard.GetLayer("Video").Add(video);
|
storyboard.GetLayer("Video").Add(video);
|
||||||
|
|
||||||
@@ -98,7 +98,7 @@ namespace osu.Game.Tests.Editing.Checks
|
|||||||
{
|
{
|
||||||
var storyboard = new Storyboard();
|
var storyboard = new Storyboard();
|
||||||
|
|
||||||
var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero);
|
var sprite = new StoryboardSprite(StoryboardElementSource.Beatmap, "unknown", Anchor.TopLeft, Vector2.Zero);
|
||||||
|
|
||||||
storyboard.GetLayer("Background").Add(sprite);
|
storyboard.GetLayer("Background").Add(sprite);
|
||||||
|
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ namespace osu.Game.Tests.Editing.Checks
|
|||||||
{
|
{
|
||||||
var storyboard = new Storyboard();
|
var storyboard = new Storyboard();
|
||||||
var layer = storyboard.GetLayer("Video");
|
var layer = storyboard.GetLayer("Video");
|
||||||
layer.Add(new StoryboardVideo("abc123.mp4", 0));
|
layer.Add(new StoryboardVideo(StoryboardElementSource.Beatmap, "abc123.mp4", 0));
|
||||||
|
|
||||||
var mockWorkingBeatmap = new Mock<TestWorkingBeatmap>(beatmap, null!, null!);
|
var mockWorkingBeatmap = new Mock<TestWorkingBeatmap>(beatmap, null!, null!);
|
||||||
mockWorkingBeatmap.Setup(w => w.GetStream(It.IsAny<string>())).Returns(resourceStream);
|
mockWorkingBeatmap.Setup(w => w.GetStream(It.IsAny<string>())).Returns(resourceStream);
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ namespace osu.Game.Tests.Editing.Checks
|
|||||||
};
|
};
|
||||||
|
|
||||||
var storyboard = new Storyboard();
|
var storyboard = new Storyboard();
|
||||||
storyboard.GetLayer("Video").Add(new StoryboardVideo(path, startTime));
|
storyboard.GetLayer("Video").Add(new StoryboardVideo(StoryboardElementSource.Beatmap, path, startTime));
|
||||||
|
|
||||||
var working = new TestWorkingBeatmap(beatmap, storyboard);
|
var working = new TestWorkingBeatmap(beatmap, storyboard);
|
||||||
return new BeatmapVerifierContext.VerifiedBeatmap(working, beatmap);
|
return new BeatmapVerifierContext.VerifiedBeatmap(working, beatmap);
|
||||||
|
|||||||
@@ -362,7 +362,7 @@ namespace osu.Game.Tests.Editing
|
|||||||
using (var encoded = new MemoryStream())
|
using (var encoded = new MemoryStream())
|
||||||
{
|
{
|
||||||
using (var sw = new StreamWriter(encoded))
|
using (var sw = new StreamWriter(encoded))
|
||||||
new LegacyBeatmapEncoder(beatmap, null).Encode(sw);
|
new LegacyBeatmapEncoder(beatmap, null, null).Encode(sw);
|
||||||
|
|
||||||
return encoded.ToArray();
|
return encoded.ToArray();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
AccuracyJudgementCount = 1,
|
AccuracyJudgementCount = 1,
|
||||||
ComboPortion = 0,
|
ComboPortion = 0,
|
||||||
BonusPortion = 0
|
BonusPortion = 0
|
||||||
}, DateTimeOffset.Now)
|
}, DateTimeOffset.Now, [], 0, [])
|
||||||
});
|
});
|
||||||
|
|
||||||
Assert.That(scoreProcessor.TotalScore.Value, Is.Zero);
|
Assert.That(scoreProcessor.TotalScore.Value, Is.Zero);
|
||||||
@@ -99,7 +99,7 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
AccuracyJudgementCount = 0,
|
AccuracyJudgementCount = 0,
|
||||||
ComboPortion = 0,
|
ComboPortion = 0,
|
||||||
BonusPortion = 0
|
BonusPortion = 0
|
||||||
}, DateTimeOffset.Now)
|
}, DateTimeOffset.Now, [], 0, [])
|
||||||
});
|
});
|
||||||
|
|
||||||
Assert.That(scoreProcessor.TotalScore.Value, Is.Zero);
|
Assert.That(scoreProcessor.TotalScore.Value, Is.Zero);
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
{
|
{
|
||||||
Child = new FrameStabilityContainer
|
Child = new FrameStabilityContainer
|
||||||
{
|
{
|
||||||
Child = sample = new DrawableStoryboardSample(new StoryboardSampleInfo(string.Empty, 0, 1))
|
Child = sample = new DrawableStoryboardSample(new StoryboardSampleInfo(StoryboardElementSource.Beatmap, string.Empty, 0, 1))
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -109,7 +109,7 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
{
|
{
|
||||||
Child = new FrameStabilityContainer
|
Child = new FrameStabilityContainer
|
||||||
{
|
{
|
||||||
Child = sample = new DrawableStoryboardSample(new StoryboardSampleInfo(string.Empty, 0, 1))
|
Child = sample = new DrawableStoryboardSample(new StoryboardSampleInfo(StoryboardElementSource.Beatmap, string.Empty, 0, 1))
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -141,7 +141,7 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
Child = beatmapSkinSourceContainer
|
Child = beatmapSkinSourceContainer
|
||||||
});
|
});
|
||||||
|
|
||||||
beatmapSkinSourceContainer.Add(sample = new DrawableStoryboardSample(new StoryboardSampleInfo("test-sample", 1, 1))
|
beatmapSkinSourceContainer.Add(sample = new DrawableStoryboardSample(new StoryboardSampleInfo(StoryboardElementSource.Beatmap, "test-sample", 1, 1))
|
||||||
{
|
{
|
||||||
Clock = gameplayContainer
|
Clock = gameplayContainer
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -91,7 +91,6 @@ namespace osu.Game.Tests.Mods
|
|||||||
{
|
{
|
||||||
public override string Name => "Non-matching setting type mod";
|
public override string Name => "Non-matching setting type mod";
|
||||||
public override LocalisableString Description => "Description";
|
public override LocalisableString Description => "Description";
|
||||||
public override double ScoreMultiplier => 1;
|
|
||||||
public override ModType Type => ModType.Conversion;
|
public override ModType Type => ModType.Conversion;
|
||||||
|
|
||||||
[SettingSource("Test setting")]
|
[SettingSource("Test setting")]
|
||||||
|
|||||||
@@ -421,7 +421,6 @@ namespace osu.Game.Tests.Mods
|
|||||||
public override string Name => string.Empty;
|
public override string Name => string.Empty;
|
||||||
public override LocalisableString Description => string.Empty;
|
public override LocalisableString Description => string.Empty;
|
||||||
public override string Acronym => string.Empty;
|
public override string Acronym => string.Empty;
|
||||||
public override double ScoreMultiplier => 1;
|
|
||||||
public override bool HasImplementation => true;
|
public override bool HasImplementation => true;
|
||||||
public override bool ValidForMultiplayer => false;
|
public override bool ValidForMultiplayer => false;
|
||||||
public override bool ValidForMultiplayerAsFreeMod => false;
|
public override bool ValidForMultiplayerAsFreeMod => false;
|
||||||
@@ -432,7 +431,6 @@ namespace osu.Game.Tests.Mods
|
|||||||
public override string Name => string.Empty;
|
public override string Name => string.Empty;
|
||||||
public override LocalisableString Description => string.Empty;
|
public override LocalisableString Description => string.Empty;
|
||||||
public override string Acronym => string.Empty;
|
public override string Acronym => string.Empty;
|
||||||
public override double ScoreMultiplier => 1;
|
|
||||||
public override bool HasImplementation => true;
|
public override bool HasImplementation => true;
|
||||||
public override bool ValidForMultiplayerAsFreeMod => false;
|
public override bool ValidForMultiplayerAsFreeMod => false;
|
||||||
}
|
}
|
||||||
@@ -441,7 +439,6 @@ namespace osu.Game.Tests.Mods
|
|||||||
{
|
{
|
||||||
public override string Name => string.Empty;
|
public override string Name => string.Empty;
|
||||||
public override LocalisableString Description => string.Empty;
|
public override LocalisableString Description => string.Empty;
|
||||||
public override double ScoreMultiplier => 1;
|
|
||||||
public override string Acronym => string.Empty;
|
public override string Acronym => string.Empty;
|
||||||
public override bool HasImplementation => true;
|
public override bool HasImplementation => true;
|
||||||
public override bool ValidForFreestyleAsRequiredMod => false;
|
public override bool ValidForFreestyleAsRequiredMod => false;
|
||||||
|
|||||||
@@ -59,8 +59,6 @@ namespace osu.Game.Tests.Mods
|
|||||||
|
|
||||||
public abstract class TestModCustomisable : Mod, IApplicableMod
|
public abstract class TestModCustomisable : Mod, IApplicableMod
|
||||||
{
|
{
|
||||||
public override double ScoreMultiplier => 1.0;
|
|
||||||
|
|
||||||
public override LocalisableString Description => "This is a customisable test mod.";
|
public override LocalisableString Description => "This is a customisable test mod.";
|
||||||
|
|
||||||
public override ModType Type => ModType.Conversion;
|
public override ModType Type => ModType.Conversion;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user