Merge branch 'master' into visible-playfield-boundary
@ -2,7 +2,7 @@
|
|||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ProjectModuleManager">
|
<component name="ProjectModuleManager">
|
||||||
<modules>
|
<modules>
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/.idea.osu.Desktop/riderModule.iml" filepath="$PROJECT_DIR$/.idea/.idea.osu.Desktop/riderModule.iml" />
|
<module fileurl="file://$PROJECT_DIR$/.idea/.idea.osu.Desktop/.idea/riderModule.iml" filepath="$PROJECT_DIR$/.idea/.idea.osu.Desktop/.idea/riderModule.iml" />
|
||||||
</modules>
|
</modules>
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
@ -38,7 +38,13 @@ If you are looking to install or test osu! without setting up a development envi
|
|||||||
|
|
||||||
If your platform is not listed above, there is still a chance you can manually build it by following the instructions below.
|
If your platform is not listed above, there is still a chance you can manually build it by following the instructions below.
|
||||||
|
|
||||||
## Developing or debugging
|
## Developing a custom ruleset
|
||||||
|
|
||||||
|
osu! is designed to have extensible modular gameplay modes, called "rulesets". Building one of these allows a developer to harness the power of osu! for their own game style. To get started working on a ruleset, we have some templates available [here](https://github.com/ppy/osu-templates).
|
||||||
|
|
||||||
|
You can see some examples of custom rulesets by visiting the [custom ruleset directory](https://github.com/ppy/osu/issues/5852).
|
||||||
|
|
||||||
|
## Developing osu!
|
||||||
|
|
||||||
Please make sure you have the following prerequisites:
|
Please make sure you have the following prerequisites:
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@
|
|||||||
<Reference Include="Java.Interop" />
|
<Reference Include="Java.Interop" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.904.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.1016.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.904.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.1009.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -9,7 +9,7 @@ using osu.Framework.Android;
|
|||||||
|
|
||||||
namespace osu.Android
|
namespace osu.Android
|
||||||
{
|
{
|
||||||
[Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullSensor, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false)]
|
[Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false)]
|
||||||
public class OsuGameActivity : AndroidGameActivity
|
public class OsuGameActivity : AndroidGameActivity
|
||||||
{
|
{
|
||||||
protected override Framework.Game CreateGame() => new OsuGameAndroid();
|
protected override Framework.Game CreateGame() => new OsuGameAndroid();
|
||||||
|
@ -29,6 +29,11 @@ namespace osu.Desktop.Updater
|
|||||||
|
|
||||||
private static readonly Logger logger = Logger.GetLogger("updater");
|
private static readonly Logger logger = Logger.GetLogger("updater");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether an update has been downloaded but not yet applied.
|
||||||
|
/// </summary>
|
||||||
|
private bool updatePending;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(NotificationOverlay notification)
|
private void load(NotificationOverlay notification)
|
||||||
{
|
{
|
||||||
@ -37,9 +42,9 @@ namespace osu.Desktop.Updater
|
|||||||
Splat.Locator.CurrentMutable.Register(() => new SquirrelLogger(), typeof(Splat.ILogger));
|
Splat.Locator.CurrentMutable.Register(() => new SquirrelLogger(), typeof(Splat.ILogger));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task PerformUpdateCheck() => await checkForUpdateAsync();
|
protected override async Task<bool> PerformUpdateCheck() => await checkForUpdateAsync();
|
||||||
|
|
||||||
private async Task checkForUpdateAsync(bool useDeltaPatching = true, UpdateProgressNotification notification = null)
|
private async Task<bool> checkForUpdateAsync(bool useDeltaPatching = true, UpdateProgressNotification notification = null)
|
||||||
{
|
{
|
||||||
// should we schedule a retry on completion of this check?
|
// should we schedule a retry on completion of this check?
|
||||||
bool scheduleRecheck = true;
|
bool scheduleRecheck = true;
|
||||||
@ -49,9 +54,19 @@ namespace osu.Desktop.Updater
|
|||||||
updateManager ??= await UpdateManager.GitHubUpdateManager(@"https://github.com/ppy/osu", @"osulazer", null, null, true);
|
updateManager ??= await UpdateManager.GitHubUpdateManager(@"https://github.com/ppy/osu", @"osulazer", null, null, true);
|
||||||
|
|
||||||
var info = await updateManager.CheckForUpdate(!useDeltaPatching);
|
var info = await updateManager.CheckForUpdate(!useDeltaPatching);
|
||||||
|
|
||||||
if (info.ReleasesToApply.Count == 0)
|
if (info.ReleasesToApply.Count == 0)
|
||||||
|
{
|
||||||
|
if (updatePending)
|
||||||
|
{
|
||||||
|
// the user may have dismissed the completion notice, so show it again.
|
||||||
|
notificationOverlay.Post(new UpdateCompleteNotification(this));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// no updates available. bail and retry later.
|
// no updates available. bail and retry later.
|
||||||
return;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (notification == null)
|
if (notification == null)
|
||||||
{
|
{
|
||||||
@ -72,6 +87,7 @@ namespace osu.Desktop.Updater
|
|||||||
await updateManager.ApplyReleases(info, p => notification.Progress = p / 100f);
|
await updateManager.ApplyReleases(info, p => notification.Progress = p / 100f);
|
||||||
|
|
||||||
notification.State = ProgressNotificationState.Completed;
|
notification.State = ProgressNotificationState.Completed;
|
||||||
|
updatePending = true;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@ -103,6 +119,8 @@ namespace osu.Desktop.Updater
|
|||||||
Scheduler.AddDelayed(async () => await checkForUpdateAsync(), 60000 * 30);
|
Scheduler.AddDelayed(async () => await checkForUpdateAsync(), 60000 * 30);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
@ -111,10 +129,27 @@ namespace osu.Desktop.Updater
|
|||||||
updateManager?.Dispose();
|
updateManager?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class UpdateCompleteNotification : ProgressCompletionNotification
|
||||||
|
{
|
||||||
|
[Resolved]
|
||||||
|
private OsuGame game { get; set; }
|
||||||
|
|
||||||
|
public UpdateCompleteNotification(SquirrelUpdateManager updateManager)
|
||||||
|
{
|
||||||
|
Text = @"Update ready to install. Click to restart!";
|
||||||
|
|
||||||
|
Activated = () =>
|
||||||
|
{
|
||||||
|
updateManager.PrepareUpdateAsync()
|
||||||
|
.ContinueWith(_ => updateManager.Schedule(() => game.GracefullyExit()));
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class UpdateProgressNotification : ProgressNotification
|
private class UpdateProgressNotification : ProgressNotification
|
||||||
{
|
{
|
||||||
private readonly SquirrelUpdateManager updateManager;
|
private readonly SquirrelUpdateManager updateManager;
|
||||||
private OsuGame game;
|
|
||||||
|
|
||||||
public UpdateProgressNotification(SquirrelUpdateManager updateManager)
|
public UpdateProgressNotification(SquirrelUpdateManager updateManager)
|
||||||
{
|
{
|
||||||
@ -123,23 +158,12 @@ namespace osu.Desktop.Updater
|
|||||||
|
|
||||||
protected override Notification CreateCompletionNotification()
|
protected override Notification CreateCompletionNotification()
|
||||||
{
|
{
|
||||||
return new ProgressCompletionNotification
|
return new UpdateCompleteNotification(updateManager);
|
||||||
{
|
|
||||||
Text = @"Update ready to install. Click to restart!",
|
|
||||||
Activated = () =>
|
|
||||||
{
|
|
||||||
updateManager.PrepareUpdateAsync()
|
|
||||||
.ContinueWith(_ => updateManager.Schedule(() => game.GracefullyExit()));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours, OsuGame game)
|
private void load(OsuColour colours)
|
||||||
{
|
{
|
||||||
this.game = game;
|
|
||||||
|
|
||||||
IconContent.AddRange(new Drawable[]
|
IconContent.AddRange(new Drawable[]
|
||||||
{
|
{
|
||||||
new Box
|
new Box
|
||||||
|
@ -5,24 +5,24 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
|
using osu.Game;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
|
|
||||||
namespace osu.Desktop.Windows
|
namespace osu.Desktop.Windows
|
||||||
{
|
{
|
||||||
public class GameplayWinKeyBlocker : Component
|
public class GameplayWinKeyBlocker : Component
|
||||||
{
|
{
|
||||||
private Bindable<bool> allowScreenSuspension;
|
|
||||||
private Bindable<bool> disableWinKey;
|
private Bindable<bool> disableWinKey;
|
||||||
|
private Bindable<bool> localUserPlaying;
|
||||||
|
|
||||||
private GameHost host;
|
[Resolved]
|
||||||
|
private GameHost host { get; set; }
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(GameHost host, OsuConfigManager config)
|
private void load(OsuGame game, OsuConfigManager config)
|
||||||
{
|
{
|
||||||
this.host = host;
|
localUserPlaying = game.LocalUserPlaying.GetBoundCopy();
|
||||||
|
localUserPlaying.BindValueChanged(_ => updateBlocking());
|
||||||
allowScreenSuspension = host.AllowScreenSuspension.GetBoundCopy();
|
|
||||||
allowScreenSuspension.BindValueChanged(_ => updateBlocking());
|
|
||||||
|
|
||||||
disableWinKey = config.GetBindable<bool>(OsuSetting.GameplayDisableWinKey);
|
disableWinKey = config.GetBindable<bool>(OsuSetting.GameplayDisableWinKey);
|
||||||
disableWinKey.BindValueChanged(_ => updateBlocking(), true);
|
disableWinKey.BindValueChanged(_ => updateBlocking(), true);
|
||||||
@ -30,7 +30,7 @@ namespace osu.Desktop.Windows
|
|||||||
|
|
||||||
private void updateBlocking()
|
private void updateBlocking()
|
||||||
{
|
{
|
||||||
bool shouldDisable = disableWinKey.Value && !allowScreenSuspension.Value;
|
bool shouldDisable = disableWinKey.Value && localUserPlaying.Value;
|
||||||
|
|
||||||
if (shouldDisable)
|
if (shouldDisable)
|
||||||
host.InputThread.Scheduler.Add(WindowsKey.Disable);
|
host.InputThread.Scheduler.Add(WindowsKey.Disable);
|
||||||
|
@ -14,6 +14,7 @@ using osu.Game.Tests.Beatmaps;
|
|||||||
namespace osu.Game.Rulesets.Catch.Tests
|
namespace osu.Game.Rulesets.Catch.Tests
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
|
[Timeout(10000)]
|
||||||
public class CatchBeatmapConversionTest : BeatmapConversionTest<ConvertValue>
|
public class CatchBeatmapConversionTest : BeatmapConversionTest<ConvertValue>
|
||||||
{
|
{
|
||||||
protected override string ResourceAssembly => "osu.Game.Rulesets.Catch";
|
protected override string ResourceAssembly => "osu.Game.Rulesets.Catch";
|
||||||
|
@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods
|
|||||||
public void TestDroplet(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new Droplet { StartTime = 1000 }), shouldMiss);
|
public void TestDroplet(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new Droplet { StartTime = 1000 }), shouldMiss);
|
||||||
|
|
||||||
// We only care about testing misses, hits are tested via JuiceStream
|
// We only care about testing misses, hits are tested via JuiceStream
|
||||||
[TestCase(false)]
|
[TestCase(true)]
|
||||||
public void TestTinyDroplet(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new TinyDroplet { StartTime = 1000 }), shouldMiss);
|
public void TestTinyDroplet(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new TinyDroplet { StartTime = 1000 }), shouldMiss);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
BIN
osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-0.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-1.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-2.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-3.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-4.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-5.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-6.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-7.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-8.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-9.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 923 B |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 2.4 KiB |
@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
Schedule(() =>
|
Schedule(() =>
|
||||||
{
|
{
|
||||||
area.AttemptCatch(fruit);
|
area.AttemptCatch(fruit);
|
||||||
area.OnResult(drawable, new JudgementResult(fruit, new CatchJudgement()) { Type = miss ? HitResult.Miss : HitResult.Great });
|
area.OnNewResult(drawable, new JudgementResult(fruit, new CatchJudgement()) { Type = miss ? HitResult.Miss : HitResult.Great });
|
||||||
|
|
||||||
drawable.Expire();
|
drawable.Expire();
|
||||||
});
|
});
|
||||||
|
65
osu.Game.Rulesets.Catch.Tests/TestSceneComboCounter.cs
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
|
using osu.Game.Rulesets.Judgements;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Tests
|
||||||
|
{
|
||||||
|
public class TestSceneComboCounter : CatchSkinnableTestScene
|
||||||
|
{
|
||||||
|
private ScoreProcessor scoreProcessor;
|
||||||
|
|
||||||
|
private Color4 judgedObjectColour = Color4.White;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp() => Schedule(() =>
|
||||||
|
{
|
||||||
|
scoreProcessor = new ScoreProcessor();
|
||||||
|
|
||||||
|
SetContents(() => new CatchComboDisplay
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Scale = new Vector2(2.5f),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCatchComboCounter()
|
||||||
|
{
|
||||||
|
AddRepeatStep("perform hit", () => performJudgement(HitResult.Great), 20);
|
||||||
|
AddStep("perform miss", () => performJudgement(HitResult.Miss));
|
||||||
|
|
||||||
|
AddStep("randomize judged object colour", () =>
|
||||||
|
{
|
||||||
|
judgedObjectColour = new Color4(
|
||||||
|
RNG.NextSingle(1f),
|
||||||
|
RNG.NextSingle(1f),
|
||||||
|
RNG.NextSingle(1f),
|
||||||
|
1f
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void performJudgement(HitResult type, Judgement judgement = null)
|
||||||
|
{
|
||||||
|
var judgedObject = new DrawableFruit(new Fruit()) { AccentColour = { Value = judgedObjectColour } };
|
||||||
|
|
||||||
|
var result = new JudgementResult(judgedObject.HitObject, judgement ?? new Judgement()) { Type = type };
|
||||||
|
scoreProcessor.ApplyResult(result);
|
||||||
|
|
||||||
|
foreach (var counter in CreatedDrawables.Cast<CatchComboDisplay>())
|
||||||
|
counter.OnNewResult(judgedObject, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,7 @@ using osu.Game.Beatmaps;
|
|||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
@ -20,7 +21,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
|||||||
|
|
||||||
public override bool CanConvert() => Beatmap.HitObjects.All(h => h is IHasXPosition);
|
public override bool CanConvert() => Beatmap.HitObjects.All(h => h is IHasXPosition);
|
||||||
|
|
||||||
protected override IEnumerable<CatchHitObject> ConvertHitObject(HitObject obj, IBeatmap beatmap)
|
protected override IEnumerable<CatchHitObject> ConvertHitObject(HitObject obj, IBeatmap beatmap, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var positionData = obj as IHasXPosition;
|
var positionData = obj as IHasXPosition;
|
||||||
var comboData = obj as IHasCombo;
|
var comboData = obj as IHasCombo;
|
||||||
|
@ -21,13 +21,11 @@ using osu.Game.Rulesets.Difficulty;
|
|||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using System;
|
using System;
|
||||||
using osu.Framework.Testing;
|
|
||||||
using osu.Game.Rulesets.Catch.Skinning;
|
using osu.Game.Rulesets.Catch.Skinning;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch
|
namespace osu.Game.Rulesets.Catch
|
||||||
{
|
{
|
||||||
[ExcludeFromDynamicCompile]
|
|
||||||
public class CatchRuleset : Ruleset, ILegacyRuleset
|
public class CatchRuleset : Ruleset, ILegacyRuleset
|
||||||
{
|
{
|
||||||
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => new DrawableCatchRuleset(this, beatmap, mods);
|
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => new DrawableCatchRuleset(this, beatmap, mods);
|
||||||
@ -143,11 +141,40 @@ namespace osu.Game.Rulesets.Catch
|
|||||||
|
|
||||||
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetCatch };
|
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetCatch };
|
||||||
|
|
||||||
|
protected override IEnumerable<HitResult> GetValidHitResults()
|
||||||
|
{
|
||||||
|
return new[]
|
||||||
|
{
|
||||||
|
HitResult.Great,
|
||||||
|
|
||||||
|
HitResult.LargeTickHit,
|
||||||
|
HitResult.SmallTickHit,
|
||||||
|
HitResult.LargeBonus,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string GetDisplayNameForHitResult(HitResult result)
|
||||||
|
{
|
||||||
|
switch (result)
|
||||||
|
{
|
||||||
|
case HitResult.LargeTickHit:
|
||||||
|
return "large droplet";
|
||||||
|
|
||||||
|
case HitResult.SmallTickHit:
|
||||||
|
return "small droplet";
|
||||||
|
|
||||||
|
case HitResult.LargeBonus:
|
||||||
|
return "banana";
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.GetDisplayNameForHitResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchDifficultyCalculator(this, beatmap);
|
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchDifficultyCalculator(this, beatmap);
|
||||||
|
|
||||||
public override ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => new CatchLegacySkinTransformer(source);
|
public override ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => new CatchLegacySkinTransformer(source);
|
||||||
|
|
||||||
public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new CatchPerformanceCalculator(this, beatmap, score);
|
public override PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => new CatchPerformanceCalculator(this, attributes, score);
|
||||||
|
|
||||||
public int LegacyID => 2;
|
public int LegacyID => 2;
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ namespace osu.Game.Rulesets.Catch
|
|||||||
Droplet,
|
Droplet,
|
||||||
CatcherIdle,
|
CatcherIdle,
|
||||||
CatcherFail,
|
CatcherFail,
|
||||||
CatcherKiai
|
CatcherKiai,
|
||||||
|
CatchComboCounter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,5 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
|||||||
public class CatchDifficultyAttributes : DifficultyAttributes
|
public class CatchDifficultyAttributes : DifficultyAttributes
|
||||||
{
|
{
|
||||||
public double ApproachRate;
|
public double ApproachRate;
|
||||||
public int MaxCombo;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
using osu.Game.Beatmaps;
|
|
||||||
using osu.Game.Rulesets.Difficulty;
|
using osu.Game.Rulesets.Difficulty;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
@ -25,8 +24,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
|||||||
private int tinyTicksMissed;
|
private int tinyTicksMissed;
|
||||||
private int misses;
|
private int misses;
|
||||||
|
|
||||||
public CatchPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, ScoreInfo score)
|
public CatchPerformanceCalculator(Ruleset ruleset, DifficultyAttributes attributes, ScoreInfo score)
|
||||||
: base(ruleset, beatmap, score)
|
: base(ruleset, attributes, score)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,7 +33,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
|||||||
{
|
{
|
||||||
mods = Score.Mods;
|
mods = Score.Mods;
|
||||||
|
|
||||||
fruitsHit = Score.Statistics.GetOrDefault(HitResult.Perfect);
|
fruitsHit = Score.Statistics.GetOrDefault(HitResult.Great);
|
||||||
ticksHit = Score.Statistics.GetOrDefault(HitResult.LargeTickHit);
|
ticksHit = Score.Statistics.GetOrDefault(HitResult.LargeTickHit);
|
||||||
tinyTicksHit = Score.Statistics.GetOrDefault(HitResult.SmallTickHit);
|
tinyTicksHit = Score.Statistics.GetOrDefault(HitResult.SmallTickHit);
|
||||||
tinyTicksMissed = Score.Statistics.GetOrDefault(HitResult.SmallTickMiss);
|
tinyTicksMissed = Score.Statistics.GetOrDefault(HitResult.SmallTickMiss);
|
||||||
|
@ -8,31 +8,7 @@ namespace osu.Game.Rulesets.Catch.Judgements
|
|||||||
{
|
{
|
||||||
public class CatchBananaJudgement : CatchJudgement
|
public class CatchBananaJudgement : CatchJudgement
|
||||||
{
|
{
|
||||||
public override bool AffectsCombo => false;
|
public override HitResult MaxResult => HitResult.LargeBonus;
|
||||||
|
|
||||||
protected override int NumericResultFor(HitResult result)
|
|
||||||
{
|
|
||||||
switch (result)
|
|
||||||
{
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
case HitResult.Perfect:
|
|
||||||
return 1100;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override double HealthIncreaseFor(HitResult result)
|
|
||||||
{
|
|
||||||
switch (result)
|
|
||||||
{
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
case HitResult.Perfect:
|
|
||||||
return DEFAULT_MAX_HEALTH_INCREASE * 0.75;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool ShouldExplodeFor(JudgementResult result) => true;
|
public override bool ShouldExplodeFor(JudgementResult result) => true;
|
||||||
}
|
}
|
||||||
|
@ -7,16 +7,6 @@ namespace osu.Game.Rulesets.Catch.Judgements
|
|||||||
{
|
{
|
||||||
public class CatchDropletJudgement : CatchJudgement
|
public class CatchDropletJudgement : CatchJudgement
|
||||||
{
|
{
|
||||||
protected override int NumericResultFor(HitResult result)
|
public override HitResult MaxResult => HitResult.LargeTickHit;
|
||||||
{
|
|
||||||
switch (result)
|
|
||||||
{
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
case HitResult.Perfect:
|
|
||||||
return 30;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,19 +9,7 @@ namespace osu.Game.Rulesets.Catch.Judgements
|
|||||||
{
|
{
|
||||||
public class CatchJudgement : Judgement
|
public class CatchJudgement : Judgement
|
||||||
{
|
{
|
||||||
public override HitResult MaxResult => HitResult.Perfect;
|
public override HitResult MaxResult => HitResult.Great;
|
||||||
|
|
||||||
protected override int NumericResultFor(HitResult result)
|
|
||||||
{
|
|
||||||
switch (result)
|
|
||||||
{
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
case HitResult.Perfect:
|
|
||||||
return 300;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether fruit on the platter should explode or drop.
|
/// Whether fruit on the platter should explode or drop.
|
||||||
|
@ -7,30 +7,6 @@ namespace osu.Game.Rulesets.Catch.Judgements
|
|||||||
{
|
{
|
||||||
public class CatchTinyDropletJudgement : CatchJudgement
|
public class CatchTinyDropletJudgement : CatchJudgement
|
||||||
{
|
{
|
||||||
public override bool AffectsCombo => false;
|
public override HitResult MaxResult => HitResult.SmallTickHit;
|
||||||
|
|
||||||
protected override int NumericResultFor(HitResult result)
|
|
||||||
{
|
|
||||||
switch (result)
|
|
||||||
{
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
case HitResult.Perfect:
|
|
||||||
return 10;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override double HealthIncreaseFor(HitResult result)
|
|
||||||
{
|
|
||||||
switch (result)
|
|
||||||
{
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
case HitResult.Perfect:
|
|
||||||
return 0.02;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Scoring;
|
|
||||||
using osu.Game.Rulesets.Catch.UI;
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
@ -86,7 +85,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
if (CheckPosition == null) return;
|
if (CheckPosition == null) return;
|
||||||
|
|
||||||
if (timeOffset >= 0 && Result != null)
|
if (timeOffset >= 0 && Result != null)
|
||||||
ApplyResult(r => r.Type = CheckPosition.Invoke(HitObject) ? HitResult.Perfect : HitResult.Miss);
|
ApplyResult(r => r.Type = CheckPosition.Invoke(HitObject) ? r.Judgement.MaxResult : r.Judgement.MinResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateStateTransforms(ArmedState state)
|
protected override void UpdateStateTransforms(ArmedState state)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@ -56,6 +57,7 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
Volume = s.Volume
|
Volume = s.Volume
|
||||||
}).ToList();
|
}).ToList();
|
||||||
|
|
||||||
|
int nodeIndex = 0;
|
||||||
SliderEventDescriptor? lastEvent = null;
|
SliderEventDescriptor? lastEvent = null;
|
||||||
|
|
||||||
foreach (var e in SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset, cancellationToken))
|
foreach (var e in SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset, cancellationToken))
|
||||||
@ -105,7 +107,7 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
case SliderEventType.Repeat:
|
case SliderEventType.Repeat:
|
||||||
AddNested(new Fruit
|
AddNested(new Fruit
|
||||||
{
|
{
|
||||||
Samples = Samples,
|
Samples = this.GetNodeSamples(nodeIndex++),
|
||||||
StartTime = e.Time,
|
StartTime = e.Time,
|
||||||
X = X + Path.PositionAt(e.PathProgress).X,
|
X = X + Path.PositionAt(e.PathProgress).X,
|
||||||
});
|
});
|
||||||
@ -119,7 +121,7 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
public double Duration
|
public double Duration
|
||||||
{
|
{
|
||||||
get => this.SpanCount() * Path.Distance / Velocity;
|
get => this.SpanCount() * Path.Distance / Velocity;
|
||||||
set => throw new System.NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed.
|
set => throw new NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed.
|
||||||
}
|
}
|
||||||
|
|
||||||
public double EndTime => StartTime + Duration;
|
public double EndTime => StartTime + Duration;
|
||||||
|
@ -31,6 +31,9 @@ namespace osu.Game.Rulesets.Catch.Replays
|
|||||||
|
|
||||||
public override Replay Generate()
|
public override Replay Generate()
|
||||||
{
|
{
|
||||||
|
if (Beatmap.HitObjects.Count == 0)
|
||||||
|
return Replay;
|
||||||
|
|
||||||
// todo: add support for HT DT
|
// todo: add support for HT DT
|
||||||
const double dash_speed = Catcher.BASE_SPEED;
|
const double dash_speed = Catcher.BASE_SPEED;
|
||||||
const double movement_speed = dash_speed / 2;
|
const double movement_speed = dash_speed / 2;
|
||||||
|
@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Catch.Scoring
|
|||||||
{
|
{
|
||||||
switch (result)
|
switch (result)
|
||||||
{
|
{
|
||||||
case HitResult.Perfect:
|
case HitResult.Great:
|
||||||
case HitResult.Miss:
|
case HitResult.Miss:
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Catch.Scoring
|
|||||||
{
|
{
|
||||||
public class CatchScoreProcessor : ScoreProcessor
|
public class CatchScoreProcessor : ScoreProcessor
|
||||||
{
|
{
|
||||||
public override HitWindows CreateHitWindows() => new CatchHitWindows();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,11 +7,17 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
using static osu.Game.Skinning.LegacySkinConfiguration;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Skinning
|
namespace osu.Game.Rulesets.Catch.Skinning
|
||||||
{
|
{
|
||||||
public class CatchLegacySkinTransformer : LegacySkinTransformer
|
public class CatchLegacySkinTransformer : LegacySkinTransformer
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// For simplicity, let's use legacy combo font texture existence as a way to identify legacy skins from default.
|
||||||
|
/// </summary>
|
||||||
|
private bool providesComboCounter => this.HasFont(GetConfig<LegacySetting, string>(LegacySetting.ComboPrefix)?.Value ?? "score");
|
||||||
|
|
||||||
public CatchLegacySkinTransformer(ISkinSource source)
|
public CatchLegacySkinTransformer(ISkinSource source)
|
||||||
: base(source)
|
: base(source)
|
||||||
{
|
{
|
||||||
@ -19,6 +25,16 @@ namespace osu.Game.Rulesets.Catch.Skinning
|
|||||||
|
|
||||||
public override Drawable GetDrawableComponent(ISkinComponent component)
|
public override Drawable GetDrawableComponent(ISkinComponent component)
|
||||||
{
|
{
|
||||||
|
if (component is HUDSkinComponent hudComponent)
|
||||||
|
{
|
||||||
|
switch (hudComponent.Component)
|
||||||
|
{
|
||||||
|
case HUDSkinComponents.ComboCounter:
|
||||||
|
// catch may provide its own combo counter; hide the default.
|
||||||
|
return providesComboCounter ? Drawable.Empty() : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!(component is CatchSkinComponent catchSkinComponent))
|
if (!(component is CatchSkinComponent catchSkinComponent))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
@ -52,6 +68,13 @@ namespace osu.Game.Rulesets.Catch.Skinning
|
|||||||
case CatchSkinComponents.CatcherKiai:
|
case CatchSkinComponents.CatcherKiai:
|
||||||
return this.GetAnimation("fruit-catcher-kiai", true, true, true) ??
|
return this.GetAnimation("fruit-catcher-kiai", true, true, true) ??
|
||||||
this.GetAnimation("fruit-ryuuta", true, true, true);
|
this.GetAnimation("fruit-ryuuta", true, true, true);
|
||||||
|
|
||||||
|
case CatchSkinComponents.CatchComboCounter:
|
||||||
|
|
||||||
|
if (providesComboCounter)
|
||||||
|
return new LegacyCatchComboCounter(Source);
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
103
osu.Game.Rulesets.Catch/Skinning/LegacyCatchComboCounter.cs
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
// 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.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
using static osu.Game.Skinning.LegacySkinConfiguration;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Skinning
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A combo counter implementation that visually behaves almost similar to stable's osu!catch combo counter.
|
||||||
|
/// </summary>
|
||||||
|
public class LegacyCatchComboCounter : CompositeDrawable, ICatchComboCounter
|
||||||
|
{
|
||||||
|
private readonly LegacyRollingCounter counter;
|
||||||
|
|
||||||
|
private readonly LegacyRollingCounter explosion;
|
||||||
|
|
||||||
|
public LegacyCatchComboCounter(ISkin skin)
|
||||||
|
{
|
||||||
|
var fontName = skin.GetConfig<LegacySetting, string>(LegacySetting.ComboPrefix)?.Value ?? "score";
|
||||||
|
var fontOverlap = skin.GetConfig<LegacySetting, float>(LegacySetting.ComboOverlap)?.Value ?? -2f;
|
||||||
|
|
||||||
|
AutoSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
Alpha = 0f;
|
||||||
|
Anchor = Anchor.Centre;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
Scale = new Vector2(0.8f);
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
explosion = new LegacyRollingCounter(skin, fontName, fontOverlap)
|
||||||
|
{
|
||||||
|
Alpha = 0.65f,
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Scale = new Vector2(1.5f),
|
||||||
|
},
|
||||||
|
counter = new LegacyRollingCounter(skin, fontName, fontOverlap)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private int lastDisplayedCombo;
|
||||||
|
|
||||||
|
public void UpdateCombo(int combo, Color4? hitObjectColour = null)
|
||||||
|
{
|
||||||
|
if (combo == lastDisplayedCombo)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// There may still be existing transforms to the counter (including value change after 250ms),
|
||||||
|
// finish them immediately before new transforms.
|
||||||
|
counter.SetCountWithoutRolling(lastDisplayedCombo);
|
||||||
|
|
||||||
|
lastDisplayedCombo = combo;
|
||||||
|
|
||||||
|
if (Time.Elapsed < 0)
|
||||||
|
{
|
||||||
|
// needs more work to make rewind somehow look good.
|
||||||
|
// basically we want the previous increment to play... or turning off RemoveCompletedTransforms (not feasible from a performance angle).
|
||||||
|
Hide();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combo fell to zero, roll down and fade out the counter.
|
||||||
|
if (combo == 0)
|
||||||
|
{
|
||||||
|
counter.Current.Value = 0;
|
||||||
|
explosion.Current.Value = 0;
|
||||||
|
|
||||||
|
this.FadeOut(400, Easing.Out);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.FadeInFromZero().Then().Delay(1000).FadeOut(300);
|
||||||
|
|
||||||
|
counter.ScaleTo(1.5f)
|
||||||
|
.ScaleTo(0.8f, 250, Easing.Out)
|
||||||
|
.OnComplete(c => c.SetCountWithoutRolling(combo));
|
||||||
|
|
||||||
|
counter.Delay(250)
|
||||||
|
.ScaleTo(1f)
|
||||||
|
.ScaleTo(1.1f, 60).Then().ScaleTo(1f, 30);
|
||||||
|
|
||||||
|
explosion.Colour = hitObjectColour ?? Color4.White;
|
||||||
|
|
||||||
|
explosion.SetCountWithoutRolling(combo);
|
||||||
|
explosion.ScaleTo(1.5f)
|
||||||
|
.ScaleTo(1.9f, 400, Easing.Out)
|
||||||
|
.FadeOutFromOne(400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
62
osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
// 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 JetBrains.Annotations;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Judgements;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.UI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a component that displays a skinned <see cref="ICatchComboCounter"/> and handles combo judgement results for updating it accordingly.
|
||||||
|
/// </summary>
|
||||||
|
public class CatchComboDisplay : SkinnableDrawable
|
||||||
|
{
|
||||||
|
private int currentCombo;
|
||||||
|
|
||||||
|
[CanBeNull]
|
||||||
|
public ICatchComboCounter ComboCounter => Drawable as ICatchComboCounter;
|
||||||
|
|
||||||
|
public CatchComboDisplay()
|
||||||
|
: base(new CatchSkinComponent(CatchSkinComponents.CatchComboCounter), _ => Empty())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
|
||||||
|
{
|
||||||
|
base.SkinChanged(skin, allowFallback);
|
||||||
|
ComboCounter?.UpdateCombo(currentCombo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnNewResult(DrawableCatchHitObject judgedObject, JudgementResult result)
|
||||||
|
{
|
||||||
|
if (!result.Type.AffectsCombo() || !result.HasResult)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!result.IsHit)
|
||||||
|
{
|
||||||
|
updateCombo(0, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCombo(result.ComboAtJudgement + 1, judgedObject.AccentColour.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnRevertResult(DrawableCatchHitObject judgedObject, JudgementResult result)
|
||||||
|
{
|
||||||
|
if (!result.Type.AffectsCombo() || !result.HasResult)
|
||||||
|
return;
|
||||||
|
|
||||||
|
updateCombo(result.ComboAtJudgement, judgedObject.AccentColour.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateCombo(int newCombo, Color4? hitObjectColour)
|
||||||
|
{
|
||||||
|
currentCombo = newCombo;
|
||||||
|
ComboCounter?.UpdateCombo(newCombo, hitObjectColour);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
explodingFruitContainer,
|
explodingFruitContainer,
|
||||||
CatcherArea.MovableCatcher.CreateProxiedContent(),
|
CatcherArea.MovableCatcher.CreateProxiedContent(),
|
||||||
HitObjectContainer,
|
HitObjectContainer,
|
||||||
CatcherArea
|
CatcherArea,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,6 +62,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
public override void Add(DrawableHitObject h)
|
public override void Add(DrawableHitObject h)
|
||||||
{
|
{
|
||||||
h.OnNewResult += onNewResult;
|
h.OnNewResult += onNewResult;
|
||||||
|
h.OnRevertResult += onRevertResult;
|
||||||
|
|
||||||
base.Add(h);
|
base.Add(h);
|
||||||
|
|
||||||
@ -70,6 +71,9 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
||||||
=> CatcherArea.OnResult((DrawableCatchHitObject)judgedObject, result);
|
=> CatcherArea.OnNewResult((DrawableCatchHitObject)judgedObject, result);
|
||||||
|
|
||||||
|
private void onRevertResult(DrawableHitObject judgedObject, JudgementResult result)
|
||||||
|
=> CatcherArea.OnRevertResult((DrawableCatchHitObject)judgedObject, result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ using osu.Game.Rulesets.Catch.Objects.Drawables;
|
|||||||
using osu.Game.Rulesets.Catch.Replays;
|
using osu.Game.Rulesets.Catch.Replays;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@ -23,6 +24,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
public Func<CatchHitObject, DrawableHitObject<CatchHitObject>> CreateDrawableRepresentation;
|
public Func<CatchHitObject, DrawableHitObject<CatchHitObject>> CreateDrawableRepresentation;
|
||||||
|
|
||||||
public readonly Catcher MovableCatcher;
|
public readonly Catcher MovableCatcher;
|
||||||
|
private readonly CatchComboDisplay comboDisplay;
|
||||||
|
|
||||||
public Container ExplodingFruitTarget
|
public Container ExplodingFruitTarget
|
||||||
{
|
{
|
||||||
@ -34,12 +36,24 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
public CatcherArea(BeatmapDifficulty difficulty = null)
|
public CatcherArea(BeatmapDifficulty difficulty = null)
|
||||||
{
|
{
|
||||||
Size = new Vector2(CatchPlayfield.WIDTH, CATCHER_SIZE);
|
Size = new Vector2(CatchPlayfield.WIDTH, CATCHER_SIZE);
|
||||||
Child = MovableCatcher = new Catcher(this, difficulty) { X = CatchPlayfield.CENTER_X };
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
comboDisplay = new CatchComboDisplay
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.None,
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.TopLeft,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Margin = new MarginPadding { Bottom = 350f },
|
||||||
|
X = CatchPlayfield.CENTER_X
|
||||||
|
},
|
||||||
|
MovableCatcher = new Catcher(this, difficulty) { X = CatchPlayfield.CENTER_X },
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnResult(DrawableCatchHitObject fruit, JudgementResult result)
|
public void OnNewResult(DrawableCatchHitObject fruit, JudgementResult result)
|
||||||
{
|
{
|
||||||
if (result.Judgement is IgnoreJudgement)
|
if (!result.Type.IsScorable())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
void runAfterLoaded(Action action)
|
void runAfterLoaded(Action action)
|
||||||
@ -86,8 +100,13 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
else
|
else
|
||||||
MovableCatcher.Drop();
|
MovableCatcher.Drop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
comboDisplay.OnNewResult(fruit, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void OnRevertResult(DrawableCatchHitObject fruit, JudgementResult result)
|
||||||
|
=> comboDisplay.OnRevertResult(fruit, result);
|
||||||
|
|
||||||
public void OnReleased(CatchAction action)
|
public void OnReleased(CatchAction action)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -105,6 +124,8 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
if (state?.CatcherX != null)
|
if (state?.CatcherX != null)
|
||||||
MovableCatcher.X = state.CatcherX.Value;
|
MovableCatcher.X = state.CatcherX.Value;
|
||||||
|
|
||||||
|
comboDisplay.X = MovableCatcher.X;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
24
osu.Game.Rulesets.Catch/UI/ICatchComboCounter.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// 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.Framework.Graphics;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.UI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An interface providing a set of methods to update the combo counter.
|
||||||
|
/// </summary>
|
||||||
|
public interface ICatchComboCounter : IDrawable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the counter to animate a transition from the old combo value it had to the current provided one.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This is called regardless of whether the clock is rewinding.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="combo">The new combo value.</param>
|
||||||
|
/// <param name="hitObjectColour">The colour of the object if hit, null on miss.</param>
|
||||||
|
void UpdateCombo(int combo, Color4? hitObjectColour = null);
|
||||||
|
}
|
||||||
|
}
|
@ -16,7 +16,7 @@ using osu.Game.Rulesets.UI.Scrolling;
|
|||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Tests
|
namespace osu.Game.Rulesets.Mania.Tests.Editor
|
||||||
{
|
{
|
||||||
public abstract class ManiaPlacementBlueprintTestScene : PlacementBlueprintTestScene
|
public abstract class ManiaPlacementBlueprintTestScene : PlacementBlueprintTestScene
|
||||||
{
|
{
|
@ -8,7 +8,7 @@ using osu.Game.Rulesets.Mania.UI;
|
|||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Tests
|
namespace osu.Game.Rulesets.Mania.Tests.Editor
|
||||||
{
|
{
|
||||||
public abstract class ManiaSelectionBlueprintTestScene : SelectionBlueprintTestScene
|
public abstract class ManiaSelectionBlueprintTestScene : SelectionBlueprintTestScene
|
||||||
{
|
{
|
@ -8,7 +8,7 @@ using osu.Game.Rulesets.Mania.Configuration;
|
|||||||
using osu.Game.Rulesets.Mania.UI;
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Tests
|
namespace osu.Game.Rulesets.Mania.Tests.Editor
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestSceneEditor : EditorTestScene
|
public class TestSceneEditor : EditorTestScene
|
@ -8,7 +8,7 @@ using osu.Game.Rulesets.Mania.Objects.Drawables;
|
|||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Tests
|
namespace osu.Game.Rulesets.Mania.Tests.Editor
|
||||||
{
|
{
|
||||||
public class TestSceneHoldNotePlacementBlueprint : ManiaPlacementBlueprintTestScene
|
public class TestSceneHoldNotePlacementBlueprint : ManiaPlacementBlueprintTestScene
|
||||||
{
|
{
|
@ -12,7 +12,7 @@ using osu.Game.Rulesets.Mania.Objects.Drawables;
|
|||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Tests
|
namespace osu.Game.Rulesets.Mania.Tests.Editor
|
||||||
{
|
{
|
||||||
public class TestSceneHoldNoteSelectionBlueprint : ManiaSelectionBlueprintTestScene
|
public class TestSceneHoldNoteSelectionBlueprint : ManiaSelectionBlueprintTestScene
|
||||||
{
|
{
|
@ -20,7 +20,7 @@ using osu.Game.Screens.Edit;
|
|||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Tests
|
namespace osu.Game.Rulesets.Mania.Tests.Editor
|
||||||
{
|
{
|
||||||
public class TestSceneManiaBeatSnapGrid : EditorClockTestScene
|
public class TestSceneManiaBeatSnapGrid : EditorClockTestScene
|
||||||
{
|
{
|
@ -23,7 +23,7 @@ using osu.Game.Tests.Visual;
|
|||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Tests
|
namespace osu.Game.Rulesets.Mania.Tests.Editor
|
||||||
{
|
{
|
||||||
public class TestSceneManiaHitObjectComposer : EditorClockTestScene
|
public class TestSceneManiaHitObjectComposer : EditorClockTestScene
|
||||||
{
|
{
|
@ -18,7 +18,7 @@ using osu.Game.Tests.Visual;
|
|||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Tests
|
namespace osu.Game.Rulesets.Mania.Tests.Editor
|
||||||
{
|
{
|
||||||
public class TestSceneNotePlacementBlueprint : ManiaPlacementBlueprintTestScene
|
public class TestSceneNotePlacementBlueprint : ManiaPlacementBlueprintTestScene
|
||||||
{
|
{
|
@ -12,7 +12,7 @@ using osu.Game.Rulesets.UI.Scrolling;
|
|||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Tests
|
namespace osu.Game.Rulesets.Mania.Tests.Editor
|
||||||
{
|
{
|
||||||
public class TestSceneNoteSelectionBlueprint : ManiaSelectionBlueprintTestScene
|
public class TestSceneNoteSelectionBlueprint : ManiaSelectionBlueprintTestScene
|
||||||
{
|
{
|
@ -14,11 +14,13 @@ using osu.Game.Tests.Beatmaps;
|
|||||||
namespace osu.Game.Rulesets.Mania.Tests
|
namespace osu.Game.Rulesets.Mania.Tests
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
|
[Timeout(10000)]
|
||||||
public class ManiaBeatmapConversionTest : BeatmapConversionTest<ManiaConvertMapping, ConvertValue>
|
public class ManiaBeatmapConversionTest : BeatmapConversionTest<ManiaConvertMapping, ConvertValue>
|
||||||
{
|
{
|
||||||
protected override string ResourceAssembly => "osu.Game.Rulesets.Mania";
|
protected override string ResourceAssembly => "osu.Game.Rulesets.Mania";
|
||||||
|
|
||||||
[TestCase("basic")]
|
[TestCase("basic")]
|
||||||
|
[TestCase("zero-length-slider")]
|
||||||
public void Test(string name) => base.Test(name);
|
public void Test(string name) => base.Test(name);
|
||||||
|
|
||||||
protected override IEnumerable<ConvertValue> CreateConvertValue(HitObject hitObject)
|
protected override IEnumerable<ConvertValue> CreateConvertValue(HitObject hitObject)
|
||||||
@ -81,11 +83,17 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
RandomZ = snapshot.RandomZ;
|
RandomZ = snapshot.RandomZ;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void PostProcess()
|
||||||
|
{
|
||||||
|
base.PostProcess();
|
||||||
|
Objects.Sort();
|
||||||
|
}
|
||||||
|
|
||||||
public bool Equals(ManiaConvertMapping other) => other != null && RandomW == other.RandomW && RandomX == other.RandomX && RandomY == other.RandomY && RandomZ == other.RandomZ;
|
public bool Equals(ManiaConvertMapping other) => other != null && RandomW == other.RandomW && RandomX == other.RandomX && RandomY == other.RandomY && RandomZ == other.RandomZ;
|
||||||
public override bool Equals(ConvertMapping<ConvertValue> other) => base.Equals(other) && Equals(other as ManiaConvertMapping);
|
public override bool Equals(ConvertMapping<ConvertValue> other) => base.Equals(other) && Equals(other as ManiaConvertMapping);
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct ConvertValue : IEquatable<ConvertValue>
|
public struct ConvertValue : IEquatable<ConvertValue>, IComparable<ConvertValue>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A sane value to account for osu!stable using ints everwhere.
|
/// A sane value to account for osu!stable using ints everwhere.
|
||||||
@ -100,5 +108,15 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
=> Precision.AlmostEquals(StartTime, other.StartTime, conversion_lenience)
|
=> Precision.AlmostEquals(StartTime, other.StartTime, conversion_lenience)
|
||||||
&& Precision.AlmostEquals(EndTime, other.EndTime, conversion_lenience)
|
&& Precision.AlmostEquals(EndTime, other.EndTime, conversion_lenience)
|
||||||
&& Column == other.Column;
|
&& Column == other.Column;
|
||||||
|
|
||||||
|
public int CompareTo(ConvertValue other)
|
||||||
|
{
|
||||||
|
var result = StartTime.CompareTo(other.StartTime);
|
||||||
|
|
||||||
|
if (result != 0)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
return Column.CompareTo(other.Column);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
{
|
{
|
||||||
protected override string ResourceAssembly => "osu.Game.Rulesets.Mania";
|
protected override string ResourceAssembly => "osu.Game.Rulesets.Mania";
|
||||||
|
|
||||||
[TestCase(2.3683365342338796d, "diffcalc-test")]
|
[TestCase(2.3449735700206298d, "diffcalc-test")]
|
||||||
public void Test(double expected, string name)
|
public void Test(double expected, string name)
|
||||||
=> base.Test(expected, name);
|
=> base.Test(expected, name);
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// 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 System.Linq;
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
@ -13,7 +14,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
|||||||
{
|
{
|
||||||
public class TestSceneHoldNote : ManiaHitObjectTestScene
|
public class TestSceneHoldNote : ManiaHitObjectTestScene
|
||||||
{
|
{
|
||||||
public TestSceneHoldNote()
|
[Test]
|
||||||
|
public void TestHoldNote()
|
||||||
{
|
{
|
||||||
AddToggleStep("toggle hitting", v =>
|
AddToggleStep("toggle hitting", v =>
|
||||||
{
|
{
|
||||||
|
@ -45,9 +45,9 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
});
|
});
|
||||||
|
|
||||||
assertHeadJudgement(HitResult.Miss);
|
assertHeadJudgement(HitResult.Miss);
|
||||||
assertTickJudgement(HitResult.Miss);
|
assertTickJudgement(HitResult.LargeTickMiss);
|
||||||
assertTailJudgement(HitResult.Miss);
|
assertTailJudgement(HitResult.Miss);
|
||||||
assertNoteJudgement(HitResult.Perfect);
|
assertNoteJudgement(HitResult.IgnoreHit);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
});
|
});
|
||||||
|
|
||||||
assertHeadJudgement(HitResult.Miss);
|
assertHeadJudgement(HitResult.Miss);
|
||||||
assertTickJudgement(HitResult.Miss);
|
assertTickJudgement(HitResult.LargeTickMiss);
|
||||||
assertTailJudgement(HitResult.Miss);
|
assertTailJudgement(HitResult.Miss);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,7 +82,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
});
|
});
|
||||||
|
|
||||||
assertHeadJudgement(HitResult.Miss);
|
assertHeadJudgement(HitResult.Miss);
|
||||||
assertTickJudgement(HitResult.Miss);
|
assertTickJudgement(HitResult.LargeTickMiss);
|
||||||
assertTailJudgement(HitResult.Miss);
|
assertTailJudgement(HitResult.Miss);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,7 +102,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
});
|
});
|
||||||
|
|
||||||
assertHeadJudgement(HitResult.Perfect);
|
assertHeadJudgement(HitResult.Perfect);
|
||||||
assertTickJudgement(HitResult.Perfect);
|
assertTickJudgement(HitResult.LargeTickHit);
|
||||||
assertTailJudgement(HitResult.Miss);
|
assertTailJudgement(HitResult.Miss);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,7 +122,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
});
|
});
|
||||||
|
|
||||||
assertHeadJudgement(HitResult.Perfect);
|
assertHeadJudgement(HitResult.Perfect);
|
||||||
assertTickJudgement(HitResult.Perfect);
|
assertTickJudgement(HitResult.LargeTickHit);
|
||||||
assertTailJudgement(HitResult.Perfect);
|
assertTailJudgement(HitResult.Perfect);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,7 +141,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
});
|
});
|
||||||
|
|
||||||
assertHeadJudgement(HitResult.Perfect);
|
assertHeadJudgement(HitResult.Perfect);
|
||||||
assertTickJudgement(HitResult.Miss);
|
assertTickJudgement(HitResult.LargeTickMiss);
|
||||||
assertTailJudgement(HitResult.Miss);
|
assertTailJudgement(HitResult.Miss);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,7 +161,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
});
|
});
|
||||||
|
|
||||||
assertHeadJudgement(HitResult.Perfect);
|
assertHeadJudgement(HitResult.Perfect);
|
||||||
assertTickJudgement(HitResult.Perfect);
|
assertTickJudgement(HitResult.LargeTickHit);
|
||||||
assertTailJudgement(HitResult.Miss);
|
assertTailJudgement(HitResult.Miss);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,7 +181,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
});
|
});
|
||||||
|
|
||||||
assertHeadJudgement(HitResult.Perfect);
|
assertHeadJudgement(HitResult.Perfect);
|
||||||
assertTickJudgement(HitResult.Perfect);
|
assertTickJudgement(HitResult.LargeTickHit);
|
||||||
assertTailJudgement(HitResult.Meh);
|
assertTailJudgement(HitResult.Meh);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,7 +199,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
});
|
});
|
||||||
|
|
||||||
assertHeadJudgement(HitResult.Miss);
|
assertHeadJudgement(HitResult.Miss);
|
||||||
assertTickJudgement(HitResult.Perfect);
|
assertTickJudgement(HitResult.LargeTickHit);
|
||||||
assertTailJudgement(HitResult.Miss);
|
assertTailJudgement(HitResult.Miss);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -217,7 +217,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
});
|
});
|
||||||
|
|
||||||
assertHeadJudgement(HitResult.Miss);
|
assertHeadJudgement(HitResult.Miss);
|
||||||
assertTickJudgement(HitResult.Perfect);
|
assertTickJudgement(HitResult.LargeTickHit);
|
||||||
assertTailJudgement(HitResult.Meh);
|
assertTailJudgement(HitResult.Meh);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -235,7 +235,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
});
|
});
|
||||||
|
|
||||||
assertHeadJudgement(HitResult.Miss);
|
assertHeadJudgement(HitResult.Miss);
|
||||||
assertTickJudgement(HitResult.Miss);
|
assertTickJudgement(HitResult.LargeTickMiss);
|
||||||
assertTailJudgement(HitResult.Meh);
|
assertTailJudgement(HitResult.Meh);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -280,10 +280,10 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
}, beatmap);
|
}, beatmap);
|
||||||
|
|
||||||
AddAssert("first hold note missed", () => judgementResults.Where(j => beatmap.HitObjects[0].NestedHitObjects.Contains(j.HitObject))
|
AddAssert("first hold note missed", () => judgementResults.Where(j => beatmap.HitObjects[0].NestedHitObjects.Contains(j.HitObject))
|
||||||
.All(j => j.Type == HitResult.Miss));
|
.All(j => !j.Type.IsHit()));
|
||||||
|
|
||||||
AddAssert("second hold note missed", () => judgementResults.Where(j => beatmap.HitObjects[1].NestedHitObjects.Contains(j.HitObject))
|
AddAssert("second hold note missed", () => judgementResults.Where(j => beatmap.HitObjects[1].NestedHitObjects.Contains(j.HitObject))
|
||||||
.All(j => j.Type == HitResult.Perfect));
|
.All(j => j.Type.IsHit()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertHeadJudgement(HitResult result)
|
private void assertHeadJudgement(HitResult result)
|
||||||
|
@ -28,25 +28,33 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestSceneNotes : OsuTestScene
|
public class TestSceneNotes : OsuTestScene
|
||||||
{
|
{
|
||||||
[BackgroundDependencyLoader]
|
[Test]
|
||||||
private void load()
|
public void TestVariousNotes()
|
||||||
{
|
{
|
||||||
Child = new FillFlowContainer
|
DrawableNote note1 = null;
|
||||||
|
DrawableNote note2 = null;
|
||||||
|
DrawableHoldNote holdNote1 = null;
|
||||||
|
DrawableHoldNote holdNote2 = null;
|
||||||
|
|
||||||
|
AddStep("create notes", () =>
|
||||||
{
|
{
|
||||||
Clock = new FramedClock(new ManualClock()),
|
Child = new FillFlowContainer
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
AutoSizeAxes = Axes.Both,
|
|
||||||
Direction = FillDirection.Horizontal,
|
|
||||||
Spacing = new Vector2(20),
|
|
||||||
Children = new[]
|
|
||||||
{
|
{
|
||||||
createNoteDisplay(ScrollingDirection.Down, 1, out var note1),
|
Clock = new FramedClock(new ManualClock()),
|
||||||
createNoteDisplay(ScrollingDirection.Up, 2, out var note2),
|
Anchor = Anchor.Centre,
|
||||||
createHoldNoteDisplay(ScrollingDirection.Down, 1, out var holdNote1),
|
Origin = Anchor.Centre,
|
||||||
createHoldNoteDisplay(ScrollingDirection.Up, 2, out var holdNote2),
|
AutoSizeAxes = Axes.Both,
|
||||||
}
|
Direction = FillDirection.Horizontal,
|
||||||
};
|
Spacing = new Vector2(20),
|
||||||
|
Children = new[]
|
||||||
|
{
|
||||||
|
createNoteDisplay(ScrollingDirection.Down, 1, out note1),
|
||||||
|
createNoteDisplay(ScrollingDirection.Up, 2, out note2),
|
||||||
|
createHoldNoteDisplay(ScrollingDirection.Down, 1, out holdNote1),
|
||||||
|
createHoldNoteDisplay(ScrollingDirection.Up, 2, out holdNote2),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
AddAssert("note 1 facing downwards", () => verifyAnchors(note1, Anchor.y2));
|
AddAssert("note 1 facing downwards", () => verifyAnchors(note1, Anchor.y2));
|
||||||
AddAssert("note 2 facing upwards", () => verifyAnchors(note2, Anchor.y0));
|
AddAssert("note 2 facing upwards", () => verifyAnchors(note2, Anchor.y0));
|
||||||
|
@ -21,13 +21,20 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public int TotalColumns => Stages.Sum(g => g.Columns);
|
public int TotalColumns => Stages.Sum(g => g.Columns);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The total number of columns that were present in this <see cref="ManiaBeatmap"/> before any user adjustments.
|
||||||
|
/// </summary>
|
||||||
|
public readonly int OriginalTotalColumns;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new <see cref="ManiaBeatmap"/>.
|
/// Creates a new <see cref="ManiaBeatmap"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="defaultStage">The initial stages.</param>
|
/// <param name="defaultStage">The initial stages.</param>
|
||||||
public ManiaBeatmap(StageDefinition defaultStage)
|
/// <param name="originalTotalColumns">The total number of columns present before any user adjustments. Defaults to the total columns in <paramref name="defaultStage"/>.</param>
|
||||||
|
public ManiaBeatmap(StageDefinition defaultStage, int? originalTotalColumns = null)
|
||||||
{
|
{
|
||||||
Stages.Add(defaultStage);
|
Stages.Add(defaultStage);
|
||||||
|
OriginalTotalColumns = originalTotalColumns ?? defaultStage.Columns;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IEnumerable<BeatmapStatistic> GetStatistics()
|
public override IEnumerable<BeatmapStatistic> GetStatistics()
|
||||||
|
@ -5,7 +5,7 @@ using osu.Game.Rulesets.Mania.Objects;
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Framework.Utils;
|
using System.Threading;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
@ -28,6 +28,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
|||||||
public bool Dual;
|
public bool Dual;
|
||||||
public readonly bool IsForCurrentRuleset;
|
public readonly bool IsForCurrentRuleset;
|
||||||
|
|
||||||
|
private readonly int originalTargetColumns;
|
||||||
|
|
||||||
// Internal for testing purposes
|
// Internal for testing purposes
|
||||||
internal FastRandom Random { get; private set; }
|
internal FastRandom Random { get; private set; }
|
||||||
|
|
||||||
@ -65,23 +67,25 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
|||||||
else
|
else
|
||||||
TargetColumns = Math.Max(4, Math.Min((int)roundedOverallDifficulty + 1, 7));
|
TargetColumns = Math.Max(4, Math.Min((int)roundedOverallDifficulty + 1, 7));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
originalTargetColumns = TargetColumns;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool CanConvert() => Beatmap.HitObjects.All(h => h is IHasXPosition);
|
public override bool CanConvert() => Beatmap.HitObjects.All(h => h is IHasXPosition);
|
||||||
|
|
||||||
protected override Beatmap<ManiaHitObject> ConvertBeatmap(IBeatmap original)
|
protected override Beatmap<ManiaHitObject> ConvertBeatmap(IBeatmap original, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
BeatmapDifficulty difficulty = original.BeatmapInfo.BaseDifficulty;
|
BeatmapDifficulty difficulty = original.BeatmapInfo.BaseDifficulty;
|
||||||
|
|
||||||
int seed = (int)MathF.Round(difficulty.DrainRate + difficulty.CircleSize) * 20 + (int)(difficulty.OverallDifficulty * 41.2) + (int)MathF.Round(difficulty.ApproachRate);
|
int seed = (int)MathF.Round(difficulty.DrainRate + difficulty.CircleSize) * 20 + (int)(difficulty.OverallDifficulty * 41.2) + (int)MathF.Round(difficulty.ApproachRate);
|
||||||
Random = new FastRandom(seed);
|
Random = new FastRandom(seed);
|
||||||
|
|
||||||
return base.ConvertBeatmap(original);
|
return base.ConvertBeatmap(original, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Beatmap<ManiaHitObject> CreateBeatmap()
|
protected override Beatmap<ManiaHitObject> CreateBeatmap()
|
||||||
{
|
{
|
||||||
beatmap = new ManiaBeatmap(new StageDefinition { Columns = TargetColumns });
|
beatmap = new ManiaBeatmap(new StageDefinition { Columns = TargetColumns }, originalTargetColumns);
|
||||||
|
|
||||||
if (Dual)
|
if (Dual)
|
||||||
beatmap.Stages.Add(new StageDefinition { Columns = TargetColumns });
|
beatmap.Stages.Add(new StageDefinition { Columns = TargetColumns });
|
||||||
@ -89,7 +93,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
|||||||
return beatmap;
|
return beatmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IEnumerable<ManiaHitObject> ConvertHitObject(HitObject original, IBeatmap beatmap)
|
protected override IEnumerable<ManiaHitObject> ConvertHitObject(HitObject original, IBeatmap beatmap, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (original is ManiaHitObject maniaOriginal)
|
if (original is ManiaHitObject maniaOriginal)
|
||||||
{
|
{
|
||||||
@ -116,7 +120,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
|||||||
prevNoteTimes.RemoveAt(0);
|
prevNoteTimes.RemoveAt(0);
|
||||||
prevNoteTimes.Add(newNoteTime);
|
prevNoteTimes.Add(newNoteTime);
|
||||||
|
|
||||||
density = (prevNoteTimes[^1] - prevNoteTimes[0]) / prevNoteTimes.Count;
|
if (prevNoteTimes.Count >= 2)
|
||||||
|
density = (prevNoteTimes[^1] - prevNoteTimes[0]) / prevNoteTimes.Count;
|
||||||
}
|
}
|
||||||
|
|
||||||
private double lastTime;
|
private double lastTime;
|
||||||
@ -167,8 +172,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
|||||||
|
|
||||||
var positionData = original as IHasPosition;
|
var positionData = original as IHasPosition;
|
||||||
|
|
||||||
for (double time = original.StartTime; !Precision.DefinitelyBigger(time, generator.EndTime); time += generator.SegmentDuration)
|
for (int i = 0; i <= generator.SpanCount; i++)
|
||||||
{
|
{
|
||||||
|
double time = original.StartTime + generator.SegmentDuration * i;
|
||||||
|
|
||||||
recordNote(time, positionData?.Position ?? Vector2.Zero);
|
recordNote(time, positionData?.Position ?? Vector2.Zero);
|
||||||
computeDensity(time);
|
computeDensity(time);
|
||||||
}
|
}
|
||||||
@ -178,7 +185,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
|||||||
|
|
||||||
case IHasDuration endTimeData:
|
case IHasDuration endTimeData:
|
||||||
{
|
{
|
||||||
conversion = new EndTimeObjectPatternGenerator(Random, original, beatmap, originalBeatmap);
|
conversion = new EndTimeObjectPatternGenerator(Random, original, beatmap, lastPattern, originalBeatmap);
|
||||||
|
|
||||||
recordNote(endTimeData.EndTime, new Vector2(256, 192));
|
recordNote(endTimeData.EndTime, new Vector2(256, 192));
|
||||||
computeDensity(endTimeData.EndTime);
|
computeDensity(endTimeData.EndTime);
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Utils;
|
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mania.MathUtils;
|
using osu.Game.Rulesets.Mania.MathUtils;
|
||||||
@ -12,6 +12,7 @@ using osu.Game.Rulesets.Objects;
|
|||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Beatmaps.Formats;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||||
{
|
{
|
||||||
@ -25,10 +26,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private const float osu_base_scoring_distance = 100;
|
private const float osu_base_scoring_distance = 100;
|
||||||
|
|
||||||
public readonly double EndTime;
|
public readonly int StartTime;
|
||||||
public readonly double SegmentDuration;
|
public readonly int EndTime;
|
||||||
|
public readonly int SegmentDuration;
|
||||||
private readonly int spanCount;
|
public readonly int SpanCount;
|
||||||
|
|
||||||
private PatternType convertType;
|
private PatternType convertType;
|
||||||
|
|
||||||
@ -42,20 +43,26 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
var distanceData = hitObject as IHasDistance;
|
var distanceData = hitObject as IHasDistance;
|
||||||
var repeatsData = hitObject as IHasRepeats;
|
var repeatsData = hitObject as IHasRepeats;
|
||||||
|
|
||||||
spanCount = repeatsData?.SpanCount() ?? 1;
|
Debug.Assert(distanceData != null);
|
||||||
|
|
||||||
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime);
|
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime);
|
||||||
DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(hitObject.StartTime);
|
DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(hitObject.StartTime);
|
||||||
|
|
||||||
// The true distance, accounting for any repeats
|
double beatLength;
|
||||||
double distance = (distanceData?.Distance ?? 0) * spanCount;
|
#pragma warning disable 618
|
||||||
// The velocity of the osu! hit object - calculated as the velocity of a slider
|
if (difficultyPoint is LegacyBeatmapDecoder.LegacyDifficultyControlPoint legacyDifficultyPoint)
|
||||||
double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / timingPoint.BeatLength;
|
#pragma warning restore 618
|
||||||
// The duration of the osu! hit object
|
beatLength = timingPoint.BeatLength * legacyDifficultyPoint.BpmMultiplier;
|
||||||
double osuDuration = distance / osuVelocity;
|
else
|
||||||
|
beatLength = timingPoint.BeatLength / difficultyPoint.SpeedMultiplier;
|
||||||
|
|
||||||
EndTime = hitObject.StartTime + osuDuration;
|
SpanCount = repeatsData?.SpanCount() ?? 1;
|
||||||
SegmentDuration = (EndTime - HitObject.StartTime) / spanCount;
|
StartTime = (int)Math.Round(hitObject.StartTime);
|
||||||
|
|
||||||
|
// This matches stable's calculation.
|
||||||
|
EndTime = (int)Math.Floor(StartTime + distanceData.Distance * beatLength * SpanCount * 0.01 / beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier);
|
||||||
|
|
||||||
|
SegmentDuration = (EndTime - StartTime) / SpanCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IEnumerable<Pattern> Generate()
|
public override IEnumerable<Pattern> Generate()
|
||||||
@ -77,7 +84,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
|
|
||||||
foreach (var obj in originalPattern.HitObjects)
|
foreach (var obj in originalPattern.HitObjects)
|
||||||
{
|
{
|
||||||
if (!Precision.AlmostEquals(EndTime, obj.GetEndTime()))
|
if (EndTime != (int)Math.Round(obj.GetEndTime()))
|
||||||
intermediatePattern.Add(obj);
|
intermediatePattern.Add(obj);
|
||||||
else
|
else
|
||||||
endTimePattern.Add(obj);
|
endTimePattern.Add(obj);
|
||||||
@ -92,35 +99,35 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
if (TotalColumns == 1)
|
if (TotalColumns == 1)
|
||||||
{
|
{
|
||||||
var pattern = new Pattern();
|
var pattern = new Pattern();
|
||||||
addToPattern(pattern, 0, HitObject.StartTime, EndTime);
|
addToPattern(pattern, 0, StartTime, EndTime);
|
||||||
return pattern;
|
return pattern;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (spanCount > 1)
|
if (SpanCount > 1)
|
||||||
{
|
{
|
||||||
if (SegmentDuration <= 90)
|
if (SegmentDuration <= 90)
|
||||||
return generateRandomHoldNotes(HitObject.StartTime, 1);
|
return generateRandomHoldNotes(StartTime, 1);
|
||||||
|
|
||||||
if (SegmentDuration <= 120)
|
if (SegmentDuration <= 120)
|
||||||
{
|
{
|
||||||
convertType |= PatternType.ForceNotStack;
|
convertType |= PatternType.ForceNotStack;
|
||||||
return generateRandomNotes(HitObject.StartTime, spanCount + 1);
|
return generateRandomNotes(StartTime, SpanCount + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SegmentDuration <= 160)
|
if (SegmentDuration <= 160)
|
||||||
return generateStair(HitObject.StartTime);
|
return generateStair(StartTime);
|
||||||
|
|
||||||
if (SegmentDuration <= 200 && ConversionDifficulty > 3)
|
if (SegmentDuration <= 200 && ConversionDifficulty > 3)
|
||||||
return generateRandomMultipleNotes(HitObject.StartTime);
|
return generateRandomMultipleNotes(StartTime);
|
||||||
|
|
||||||
double duration = EndTime - HitObject.StartTime;
|
double duration = EndTime - StartTime;
|
||||||
if (duration >= 4000)
|
if (duration >= 4000)
|
||||||
return generateNRandomNotes(HitObject.StartTime, 0.23, 0, 0);
|
return generateNRandomNotes(StartTime, 0.23, 0, 0);
|
||||||
|
|
||||||
if (SegmentDuration > 400 && spanCount < TotalColumns - 1 - RandomStart)
|
if (SegmentDuration > 400 && SpanCount < TotalColumns - 1 - RandomStart)
|
||||||
return generateTiledHoldNotes(HitObject.StartTime);
|
return generateTiledHoldNotes(StartTime);
|
||||||
|
|
||||||
return generateHoldAndNormalNotes(HitObject.StartTime);
|
return generateHoldAndNormalNotes(StartTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SegmentDuration <= 110)
|
if (SegmentDuration <= 110)
|
||||||
@ -129,37 +136,37 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
convertType |= PatternType.ForceNotStack;
|
convertType |= PatternType.ForceNotStack;
|
||||||
else
|
else
|
||||||
convertType &= ~PatternType.ForceNotStack;
|
convertType &= ~PatternType.ForceNotStack;
|
||||||
return generateRandomNotes(HitObject.StartTime, SegmentDuration < 80 ? 1 : 2);
|
return generateRandomNotes(StartTime, SegmentDuration < 80 ? 1 : 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ConversionDifficulty > 6.5)
|
if (ConversionDifficulty > 6.5)
|
||||||
{
|
{
|
||||||
if (convertType.HasFlag(PatternType.LowProbability))
|
if (convertType.HasFlag(PatternType.LowProbability))
|
||||||
return generateNRandomNotes(HitObject.StartTime, 0.78, 0.3, 0);
|
return generateNRandomNotes(StartTime, 0.78, 0.3, 0);
|
||||||
|
|
||||||
return generateNRandomNotes(HitObject.StartTime, 0.85, 0.36, 0.03);
|
return generateNRandomNotes(StartTime, 0.85, 0.36, 0.03);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ConversionDifficulty > 4)
|
if (ConversionDifficulty > 4)
|
||||||
{
|
{
|
||||||
if (convertType.HasFlag(PatternType.LowProbability))
|
if (convertType.HasFlag(PatternType.LowProbability))
|
||||||
return generateNRandomNotes(HitObject.StartTime, 0.43, 0.08, 0);
|
return generateNRandomNotes(StartTime, 0.43, 0.08, 0);
|
||||||
|
|
||||||
return generateNRandomNotes(HitObject.StartTime, 0.56, 0.18, 0);
|
return generateNRandomNotes(StartTime, 0.56, 0.18, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ConversionDifficulty > 2.5)
|
if (ConversionDifficulty > 2.5)
|
||||||
{
|
{
|
||||||
if (convertType.HasFlag(PatternType.LowProbability))
|
if (convertType.HasFlag(PatternType.LowProbability))
|
||||||
return generateNRandomNotes(HitObject.StartTime, 0.3, 0, 0);
|
return generateNRandomNotes(StartTime, 0.3, 0, 0);
|
||||||
|
|
||||||
return generateNRandomNotes(HitObject.StartTime, 0.37, 0.08, 0);
|
return generateNRandomNotes(StartTime, 0.37, 0.08, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (convertType.HasFlag(PatternType.LowProbability))
|
if (convertType.HasFlag(PatternType.LowProbability))
|
||||||
return generateNRandomNotes(HitObject.StartTime, 0.17, 0, 0);
|
return generateNRandomNotes(StartTime, 0.17, 0, 0);
|
||||||
|
|
||||||
return generateNRandomNotes(HitObject.StartTime, 0.27, 0, 0);
|
return generateNRandomNotes(StartTime, 0.27, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -168,7 +175,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
/// <param name="startTime">Start time of each hold note.</param>
|
/// <param name="startTime">Start time of each hold note.</param>
|
||||||
/// <param name="noteCount">Number of hold notes.</param>
|
/// <param name="noteCount">Number of hold notes.</param>
|
||||||
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
|
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
|
||||||
private Pattern generateRandomHoldNotes(double startTime, int noteCount)
|
private Pattern generateRandomHoldNotes(int startTime, int noteCount)
|
||||||
{
|
{
|
||||||
// - - - -
|
// - - - -
|
||||||
// ■ - ■ ■
|
// ■ - ■ ■
|
||||||
@ -203,7 +210,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
/// <param name="startTime">The start time.</param>
|
/// <param name="startTime">The start time.</param>
|
||||||
/// <param name="noteCount">The number of notes.</param>
|
/// <param name="noteCount">The number of notes.</param>
|
||||||
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
|
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
|
||||||
private Pattern generateRandomNotes(double startTime, int noteCount)
|
private Pattern generateRandomNotes(int startTime, int noteCount)
|
||||||
{
|
{
|
||||||
// - - - -
|
// - - - -
|
||||||
// x - - -
|
// x - - -
|
||||||
@ -235,7 +242,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="startTime">The start time.</param>
|
/// <param name="startTime">The start time.</param>
|
||||||
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
|
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
|
||||||
private Pattern generateStair(double startTime)
|
private Pattern generateStair(int startTime)
|
||||||
{
|
{
|
||||||
// - - - -
|
// - - - -
|
||||||
// x - - -
|
// x - - -
|
||||||
@ -251,7 +258,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
int column = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
|
int column = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
|
||||||
bool increasing = Random.NextDouble() > 0.5;
|
bool increasing = Random.NextDouble() > 0.5;
|
||||||
|
|
||||||
for (int i = 0; i <= spanCount; i++)
|
for (int i = 0; i <= SpanCount; i++)
|
||||||
{
|
{
|
||||||
addToPattern(pattern, column, startTime, startTime);
|
addToPattern(pattern, column, startTime, startTime);
|
||||||
startTime += SegmentDuration;
|
startTime += SegmentDuration;
|
||||||
@ -287,7 +294,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="startTime">The start time.</param>
|
/// <param name="startTime">The start time.</param>
|
||||||
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
|
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
|
||||||
private Pattern generateRandomMultipleNotes(double startTime)
|
private Pattern generateRandomMultipleNotes(int startTime)
|
||||||
{
|
{
|
||||||
// - - - -
|
// - - - -
|
||||||
// x - - -
|
// x - - -
|
||||||
@ -302,7 +309,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
|
|
||||||
int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
|
int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
|
||||||
|
|
||||||
for (int i = 0; i <= spanCount; i++)
|
for (int i = 0; i <= SpanCount; i++)
|
||||||
{
|
{
|
||||||
addToPattern(pattern, nextColumn, startTime, startTime);
|
addToPattern(pattern, nextColumn, startTime, startTime);
|
||||||
|
|
||||||
@ -330,7 +337,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
/// <param name="p3">The probability required for 3 hold notes to be generated.</param>
|
/// <param name="p3">The probability required for 3 hold notes to be generated.</param>
|
||||||
/// <param name="p4">The probability required for 4 hold notes to be generated.</param>
|
/// <param name="p4">The probability required for 4 hold notes to be generated.</param>
|
||||||
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
|
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
|
||||||
private Pattern generateNRandomNotes(double startTime, double p2, double p3, double p4)
|
private Pattern generateNRandomNotes(int startTime, double p2, double p3, double p4)
|
||||||
{
|
{
|
||||||
// - - - -
|
// - - - -
|
||||||
// ■ - ■ ■
|
// ■ - ■ ■
|
||||||
@ -367,7 +374,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
static bool isDoubleSample(HitSampleInfo sample) => sample.Name == HitSampleInfo.HIT_CLAP || sample.Name == HitSampleInfo.HIT_FINISH;
|
static bool isDoubleSample(HitSampleInfo sample) => sample.Name == HitSampleInfo.HIT_CLAP || sample.Name == HitSampleInfo.HIT_FINISH;
|
||||||
|
|
||||||
bool canGenerateTwoNotes = !convertType.HasFlag(PatternType.LowProbability);
|
bool canGenerateTwoNotes = !convertType.HasFlag(PatternType.LowProbability);
|
||||||
canGenerateTwoNotes &= HitObject.Samples.Any(isDoubleSample) || sampleInfoListAt(HitObject.StartTime).Any(isDoubleSample);
|
canGenerateTwoNotes &= HitObject.Samples.Any(isDoubleSample) || sampleInfoListAt(StartTime).Any(isDoubleSample);
|
||||||
|
|
||||||
if (canGenerateTwoNotes)
|
if (canGenerateTwoNotes)
|
||||||
p2 = 1;
|
p2 = 1;
|
||||||
@ -380,7 +387,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="startTime">The first hold note start time.</param>
|
/// <param name="startTime">The first hold note start time.</param>
|
||||||
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
|
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
|
||||||
private Pattern generateTiledHoldNotes(double startTime)
|
private Pattern generateTiledHoldNotes(int startTime)
|
||||||
{
|
{
|
||||||
// - - - -
|
// - - - -
|
||||||
// ■ ■ ■ ■
|
// ■ ■ ■ ■
|
||||||
@ -393,7 +400,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
|
|
||||||
var pattern = new Pattern();
|
var pattern = new Pattern();
|
||||||
|
|
||||||
int columnRepeat = Math.Min(spanCount, TotalColumns);
|
int columnRepeat = Math.Min(SpanCount, TotalColumns);
|
||||||
|
|
||||||
|
// Due to integer rounding, this is not guaranteed to be the same as EndTime (the class-level variable).
|
||||||
|
int endTime = startTime + SegmentDuration * SpanCount;
|
||||||
|
|
||||||
int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
|
int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
|
||||||
if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
|
if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
|
||||||
@ -402,7 +412,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
for (int i = 0; i < columnRepeat; i++)
|
for (int i = 0; i < columnRepeat; i++)
|
||||||
{
|
{
|
||||||
nextColumn = FindAvailableColumn(nextColumn, pattern);
|
nextColumn = FindAvailableColumn(nextColumn, pattern);
|
||||||
addToPattern(pattern, nextColumn, startTime, EndTime);
|
addToPattern(pattern, nextColumn, startTime, endTime);
|
||||||
startTime += SegmentDuration;
|
startTime += SegmentDuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -414,7 +424,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="startTime">The start time of notes.</param>
|
/// <param name="startTime">The start time of notes.</param>
|
||||||
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
|
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
|
||||||
private Pattern generateHoldAndNormalNotes(double startTime)
|
private Pattern generateHoldAndNormalNotes(int startTime)
|
||||||
{
|
{
|
||||||
// - - - -
|
// - - - -
|
||||||
// ■ x x -
|
// ■ x x -
|
||||||
@ -447,9 +457,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
|
|
||||||
var rowPattern = new Pattern();
|
var rowPattern = new Pattern();
|
||||||
|
|
||||||
for (int i = 0; i <= spanCount; i++)
|
for (int i = 0; i <= SpanCount; i++)
|
||||||
{
|
{
|
||||||
if (!(ignoreHead && startTime == HitObject.StartTime))
|
if (!(ignoreHead && startTime == StartTime))
|
||||||
{
|
{
|
||||||
for (int j = 0; j < noteCount; j++)
|
for (int j = 0; j < noteCount; j++)
|
||||||
{
|
{
|
||||||
@ -472,19 +482,18 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="time">The time to retrieve the sample info list from.</param>
|
/// <param name="time">The time to retrieve the sample info list from.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private IList<HitSampleInfo> sampleInfoListAt(double time) => nodeSamplesAt(time)?.First() ?? HitObject.Samples;
|
private IList<HitSampleInfo> sampleInfoListAt(int time) => nodeSamplesAt(time)?.First() ?? HitObject.Samples;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves the list of node samples that occur at time greater than or equal to <paramref name="time"/>.
|
/// Retrieves the list of node samples that occur at time greater than or equal to <paramref name="time"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="time">The time to retrieve node samples at.</param>
|
/// <param name="time">The time to retrieve node samples at.</param>
|
||||||
private List<IList<HitSampleInfo>> nodeSamplesAt(double time)
|
private List<IList<HitSampleInfo>> nodeSamplesAt(int time)
|
||||||
{
|
{
|
||||||
if (!(HitObject is IHasPathWithRepeats curveData))
|
if (!(HitObject is IHasPathWithRepeats curveData))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
// mathematically speaking this should be a whole number always, but floating-point arithmetic is not so kind
|
var index = SegmentDuration == 0 ? 0 : (time - StartTime) / SegmentDuration;
|
||||||
var index = (int)Math.Round(SegmentDuration == 0 ? 0 : (time - HitObject.StartTime) / SegmentDuration, MidpointRounding.AwayFromZero);
|
|
||||||
|
|
||||||
// avoid slicing the list & creating copies, if at all possible.
|
// avoid slicing the list & creating copies, if at all possible.
|
||||||
return index == 0 ? curveData.NodeSamples : curveData.NodeSamples.Skip(index).ToList();
|
return index == 0 ? curveData.NodeSamples : curveData.NodeSamples.Skip(index).ToList();
|
||||||
@ -497,7 +506,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
/// <param name="column">The column to add the note to.</param>
|
/// <param name="column">The column to add the note to.</param>
|
||||||
/// <param name="startTime">The start time of the note.</param>
|
/// <param name="startTime">The start time of the note.</param>
|
||||||
/// <param name="endTime">The end time of the note (set to <paramref name="startTime"/> for a non-hold note).</param>
|
/// <param name="endTime">The end time of the note (set to <paramref name="startTime"/> for a non-hold note).</param>
|
||||||
private void addToPattern(Pattern pattern, int column, double startTime, double endTime)
|
private void addToPattern(Pattern pattern, int column, int startTime, int endTime)
|
||||||
{
|
{
|
||||||
ManiaHitObject newObject;
|
ManiaHitObject newObject;
|
||||||
|
|
||||||
|
@ -14,12 +14,17 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
{
|
{
|
||||||
internal class EndTimeObjectPatternGenerator : PatternGenerator
|
internal class EndTimeObjectPatternGenerator : PatternGenerator
|
||||||
{
|
{
|
||||||
private readonly double endTime;
|
private readonly int endTime;
|
||||||
|
private readonly PatternType convertType;
|
||||||
|
|
||||||
public EndTimeObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, IBeatmap originalBeatmap)
|
public EndTimeObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, IBeatmap originalBeatmap)
|
||||||
: base(random, hitObject, beatmap, new Pattern(), originalBeatmap)
|
: base(random, hitObject, beatmap, previousPattern, originalBeatmap)
|
||||||
{
|
{
|
||||||
endTime = (HitObject as IHasDuration)?.EndTime ?? 0;
|
endTime = (int)((HitObject as IHasDuration)?.EndTime ?? 0);
|
||||||
|
|
||||||
|
convertType = PreviousPattern.ColumnWithObjects == TotalColumns
|
||||||
|
? PatternType.None
|
||||||
|
: PatternType.ForceNotStack;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IEnumerable<Pattern> Generate()
|
public override IEnumerable<Pattern> Generate()
|
||||||
@ -40,18 +45,25 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 8:
|
case 8:
|
||||||
addToPattern(pattern, FindAvailableColumn(GetRandomColumn(), PreviousPattern), generateHold);
|
addToPattern(pattern, getRandomColumn(), generateHold);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if (TotalColumns > 0)
|
addToPattern(pattern, getRandomColumn(0), generateHold);
|
||||||
addToPattern(pattern, GetRandomColumn(), generateHold);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return pattern;
|
return pattern;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int getRandomColumn(int? lowerBound = null)
|
||||||
|
{
|
||||||
|
if ((convertType & PatternType.ForceNotStack) > 0)
|
||||||
|
return FindAvailableColumn(GetRandomColumn(lowerBound), lowerBound, patterns: PreviousPattern);
|
||||||
|
|
||||||
|
return FindAvailableColumn(GetRandomColumn(lowerBound), lowerBound);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructs and adds a note to a pattern.
|
/// Constructs and adds a note to a pattern.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -397,7 +397,11 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
|
|
||||||
case 4:
|
case 4:
|
||||||
centreProbability = 0;
|
centreProbability = 0;
|
||||||
p2 = Math.Min(p2 * 2, 0.2);
|
|
||||||
|
// Stable requires rngValue > x, which is an inverse-probability. Lazer uses true probability (1 - x).
|
||||||
|
// But multiplying this value by 2 (stable) is not the same operation as dividing it by 2 (lazer),
|
||||||
|
// so it needs to be converted to from a probability and then back after the multiplication.
|
||||||
|
p2 = 1 - Math.Max((1 - p2) * 2, 0.8);
|
||||||
p3 = 0;
|
p3 = 0;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -408,11 +412,20 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
|
|
||||||
case 6:
|
case 6:
|
||||||
centreProbability = 0;
|
centreProbability = 0;
|
||||||
p2 = Math.Min(p2 * 2, 0.5);
|
|
||||||
p3 = Math.Min(p3 * 2, 0.15);
|
// Stable requires rngValue > x, which is an inverse-probability. Lazer uses true probability (1 - x).
|
||||||
|
// But multiplying this value by 2 (stable) is not the same operation as dividing it by 2 (lazer),
|
||||||
|
// so it needs to be converted to from a probability and then back after the multiplication.
|
||||||
|
p2 = 1 - Math.Max((1 - p2) * 2, 0.5);
|
||||||
|
p3 = 1 - Math.Max((1 - p3) * 2, 0.85);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The stable values were allowed to exceed 1, which indicate <0% probability.
|
||||||
|
// These values needs to be clamped otherwise GetRandomNoteCount() will throw an exception.
|
||||||
|
p2 = Math.Clamp(p2, 0, 1);
|
||||||
|
p3 = Math.Clamp(p3, 0, 1);
|
||||||
|
|
||||||
double centreVal = Random.NextDouble();
|
double centreVal = Random.NextDouble();
|
||||||
int noteCount = GetRandomNoteCount(p2, p3);
|
int noteCount = GetRandomNoteCount(p2, p3);
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Mania.Configuration
|
|||||||
{
|
{
|
||||||
base.InitialiseDefaults();
|
base.InitialiseDefaults();
|
||||||
|
|
||||||
Set(ManiaRulesetSetting.ScrollTime, 1500.0, DrawableManiaRuleset.MIN_TIME_RANGE, DrawableManiaRuleset.MAX_TIME_RANGE, 1);
|
Set(ManiaRulesetSetting.ScrollTime, 1500.0, DrawableManiaRuleset.MIN_TIME_RANGE, DrawableManiaRuleset.MAX_TIME_RANGE, 5);
|
||||||
Set(ManiaRulesetSetting.ScrollDirection, ManiaScrollingDirection.Down);
|
Set(ManiaRulesetSetting.ScrollDirection, ManiaScrollingDirection.Down);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,5 +8,6 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
|||||||
public class ManiaDifficultyAttributes : DifficultyAttributes
|
public class ManiaDifficultyAttributes : DifficultyAttributes
|
||||||
{
|
{
|
||||||
public double GreatHitWindow;
|
public double GreatHitWindow;
|
||||||
|
public double ScoreMultiplier;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
@ -10,9 +11,12 @@ using osu.Game.Rulesets.Difficulty.Skills;
|
|||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mania.Difficulty.Preprocessing;
|
using osu.Game.Rulesets.Mania.Difficulty.Preprocessing;
|
||||||
using osu.Game.Rulesets.Mania.Difficulty.Skills;
|
using osu.Game.Rulesets.Mania.Difficulty.Skills;
|
||||||
|
using osu.Game.Rulesets.Mania.MathUtils;
|
||||||
using osu.Game.Rulesets.Mania.Mods;
|
using osu.Game.Rulesets.Mania.Mods;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Mania.Scoring;
|
using osu.Game.Rulesets.Mania.Scoring;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Difficulty
|
namespace osu.Game.Rulesets.Mania.Difficulty
|
||||||
@ -22,11 +26,13 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
|||||||
private const double star_scaling_factor = 0.018;
|
private const double star_scaling_factor = 0.018;
|
||||||
|
|
||||||
private readonly bool isForCurrentRuleset;
|
private readonly bool isForCurrentRuleset;
|
||||||
|
private readonly double originalOverallDifficulty;
|
||||||
|
|
||||||
public ManiaDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
|
public ManiaDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
|
||||||
: base(ruleset, beatmap)
|
: base(ruleset, beatmap)
|
||||||
{
|
{
|
||||||
isForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(ruleset.RulesetInfo);
|
isForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(ruleset.RulesetInfo);
|
||||||
|
originalOverallDifficulty = beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
|
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
|
||||||
@ -39,63 +45,33 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
|||||||
|
|
||||||
return new ManiaDifficultyAttributes
|
return new ManiaDifficultyAttributes
|
||||||
{
|
{
|
||||||
StarRating = difficultyValue(skills) * star_scaling_factor,
|
StarRating = skills[0].DifficultyValue() * star_scaling_factor,
|
||||||
Mods = mods,
|
Mods = mods,
|
||||||
// Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future
|
// Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future
|
||||||
GreatHitWindow = (int)(hitWindows.WindowFor(HitResult.Great)) / clockRate,
|
GreatHitWindow = (int)Math.Ceiling(getHitWindow300(mods) / clockRate),
|
||||||
|
ScoreMultiplier = getScoreMultiplier(beatmap, mods),
|
||||||
|
MaxCombo = beatmap.HitObjects.Sum(h => h is HoldNote ? 2 : 1),
|
||||||
Skills = skills
|
Skills = skills
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private double difficultyValue(Skill[] skills)
|
|
||||||
{
|
|
||||||
// Preprocess the strains to find the maximum overall + individual (aggregate) strain from each section
|
|
||||||
var overall = skills.OfType<Overall>().Single();
|
|
||||||
var aggregatePeaks = new List<double>(Enumerable.Repeat(0.0, overall.StrainPeaks.Count));
|
|
||||||
|
|
||||||
foreach (var individual in skills.OfType<Individual>())
|
|
||||||
{
|
|
||||||
for (int i = 0; i < individual.StrainPeaks.Count; i++)
|
|
||||||
{
|
|
||||||
double aggregate = individual.StrainPeaks[i] + overall.StrainPeaks[i];
|
|
||||||
|
|
||||||
if (aggregate > aggregatePeaks[i])
|
|
||||||
aggregatePeaks[i] = aggregate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
aggregatePeaks.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain.
|
|
||||||
|
|
||||||
double difficulty = 0;
|
|
||||||
double weight = 1;
|
|
||||||
|
|
||||||
// Difficulty is the weighted sum of the highest strains from every section.
|
|
||||||
foreach (double strain in aggregatePeaks)
|
|
||||||
{
|
|
||||||
difficulty += strain * weight;
|
|
||||||
weight *= 0.9;
|
|
||||||
}
|
|
||||||
|
|
||||||
return difficulty;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
|
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
|
||||||
{
|
{
|
||||||
for (int i = 1; i < beatmap.HitObjects.Count; i++)
|
var sortedObjects = beatmap.HitObjects.ToArray();
|
||||||
yield return new ManiaDifficultyHitObject(beatmap.HitObjects[i], beatmap.HitObjects[i - 1], clockRate);
|
|
||||||
|
LegacySortHelper<HitObject>.Sort(sortedObjects, Comparer<HitObject>.Create((a, b) => (int)Math.Round(a.StartTime) - (int)Math.Round(b.StartTime)));
|
||||||
|
|
||||||
|
for (int i = 1; i < sortedObjects.Length; i++)
|
||||||
|
yield return new ManiaDifficultyHitObject(sortedObjects[i], sortedObjects[i - 1], clockRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Skill[] CreateSkills(IBeatmap beatmap)
|
// Sorting is done in CreateDifficultyHitObjects, since the full list of hitobjects is required.
|
||||||
|
protected override IEnumerable<DifficultyHitObject> SortObjects(IEnumerable<DifficultyHitObject> input) => input;
|
||||||
|
|
||||||
|
protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[]
|
||||||
{
|
{
|
||||||
int columnCount = ((ManiaBeatmap)beatmap).TotalColumns;
|
new Strain(((ManiaBeatmap)beatmap).TotalColumns)
|
||||||
|
};
|
||||||
var skills = new List<Skill> { new Overall(columnCount) };
|
|
||||||
|
|
||||||
for (int i = 0; i < columnCount; i++)
|
|
||||||
skills.Add(new Individual(i, columnCount));
|
|
||||||
|
|
||||||
return skills.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Mod[] DifficultyAdjustmentMods
|
protected override Mod[] DifficultyAdjustmentMods
|
||||||
{
|
{
|
||||||
@ -120,12 +96,73 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
|||||||
new ManiaModKey3(),
|
new ManiaModKey3(),
|
||||||
new ManiaModKey4(),
|
new ManiaModKey4(),
|
||||||
new ManiaModKey5(),
|
new ManiaModKey5(),
|
||||||
|
new MultiMod(new ManiaModKey5(), new ManiaModDualStages()),
|
||||||
new ManiaModKey6(),
|
new ManiaModKey6(),
|
||||||
|
new MultiMod(new ManiaModKey6(), new ManiaModDualStages()),
|
||||||
new ManiaModKey7(),
|
new ManiaModKey7(),
|
||||||
|
new MultiMod(new ManiaModKey7(), new ManiaModDualStages()),
|
||||||
new ManiaModKey8(),
|
new ManiaModKey8(),
|
||||||
|
new MultiMod(new ManiaModKey8(), new ManiaModDualStages()),
|
||||||
new ManiaModKey9(),
|
new ManiaModKey9(),
|
||||||
|
new MultiMod(new ManiaModKey9(), new ManiaModDualStages()),
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int getHitWindow300(Mod[] mods)
|
||||||
|
{
|
||||||
|
if (isForCurrentRuleset)
|
||||||
|
{
|
||||||
|
double od = Math.Min(10.0, Math.Max(0, 10.0 - originalOverallDifficulty));
|
||||||
|
return applyModAdjustments(34 + 3 * od, mods);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Math.Round(originalOverallDifficulty) > 4)
|
||||||
|
return applyModAdjustments(34, mods);
|
||||||
|
|
||||||
|
return applyModAdjustments(47, mods);
|
||||||
|
|
||||||
|
static int applyModAdjustments(double value, Mod[] mods)
|
||||||
|
{
|
||||||
|
if (mods.Any(m => m is ManiaModHardRock))
|
||||||
|
value /= 1.4;
|
||||||
|
else if (mods.Any(m => m is ManiaModEasy))
|
||||||
|
value *= 1.4;
|
||||||
|
|
||||||
|
if (mods.Any(m => m is ManiaModDoubleTime))
|
||||||
|
value *= 1.5;
|
||||||
|
else if (mods.Any(m => m is ManiaModHalfTime))
|
||||||
|
value *= 0.75;
|
||||||
|
|
||||||
|
return (int)value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private double getScoreMultiplier(IBeatmap beatmap, Mod[] mods)
|
||||||
|
{
|
||||||
|
double scoreMultiplier = 1;
|
||||||
|
|
||||||
|
foreach (var m in mods)
|
||||||
|
{
|
||||||
|
switch (m)
|
||||||
|
{
|
||||||
|
case ManiaModNoFail _:
|
||||||
|
case ManiaModEasy _:
|
||||||
|
case ManiaModHalfTime _:
|
||||||
|
scoreMultiplier *= 0.5;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var maniaBeatmap = (ManiaBeatmap)beatmap;
|
||||||
|
int diff = maniaBeatmap.TotalColumns - maniaBeatmap.OriginalTotalColumns;
|
||||||
|
|
||||||
|
if (diff > 0)
|
||||||
|
scoreMultiplier *= 0.9;
|
||||||
|
else if (diff < 0)
|
||||||
|
scoreMultiplier *= 0.9 + 0.04 * diff;
|
||||||
|
|
||||||
|
return scoreMultiplier;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
using osu.Game.Beatmaps;
|
|
||||||
using osu.Game.Rulesets.Difficulty;
|
using osu.Game.Rulesets.Difficulty;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
@ -29,8 +28,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
|||||||
private int countMeh;
|
private int countMeh;
|
||||||
private int countMiss;
|
private int countMiss;
|
||||||
|
|
||||||
public ManiaPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, ScoreInfo score)
|
public ManiaPerformanceCalculator(Ruleset ruleset, DifficultyAttributes attributes, ScoreInfo score)
|
||||||
: base(ruleset, beatmap, score)
|
: base(ruleset, attributes, score)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,47 +0,0 @@
|
|||||||
// 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.Game.Rulesets.Difficulty.Preprocessing;
|
|
||||||
using osu.Game.Rulesets.Difficulty.Skills;
|
|
||||||
using osu.Game.Rulesets.Mania.Difficulty.Preprocessing;
|
|
||||||
using osu.Game.Rulesets.Objects;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Difficulty.Skills
|
|
||||||
{
|
|
||||||
public class Individual : Skill
|
|
||||||
{
|
|
||||||
protected override double SkillMultiplier => 1;
|
|
||||||
protected override double StrainDecayBase => 0.125;
|
|
||||||
|
|
||||||
private readonly double[] holdEndTimes;
|
|
||||||
|
|
||||||
private readonly int column;
|
|
||||||
|
|
||||||
public Individual(int column, int columnCount)
|
|
||||||
{
|
|
||||||
this.column = column;
|
|
||||||
|
|
||||||
holdEndTimes = new double[columnCount];
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override double StrainValueOf(DifficultyHitObject current)
|
|
||||||
{
|
|
||||||
var maniaCurrent = (ManiaDifficultyHitObject)current;
|
|
||||||
var endTime = maniaCurrent.BaseObject.GetEndTime();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (maniaCurrent.BaseObject.Column != column)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
// We give a slight bonus if something is held meanwhile
|
|
||||||
return holdEndTimes.Any(t => t > endTime) ? 2.5 : 2;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
holdEndTimes[maniaCurrent.BaseObject.Column] = endTime;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,56 +0,0 @@
|
|||||||
// 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.Difficulty.Preprocessing;
|
|
||||||
using osu.Game.Rulesets.Difficulty.Skills;
|
|
||||||
using osu.Game.Rulesets.Mania.Difficulty.Preprocessing;
|
|
||||||
using osu.Game.Rulesets.Objects;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Difficulty.Skills
|
|
||||||
{
|
|
||||||
public class Overall : Skill
|
|
||||||
{
|
|
||||||
protected override double SkillMultiplier => 1;
|
|
||||||
protected override double StrainDecayBase => 0.3;
|
|
||||||
|
|
||||||
private readonly double[] holdEndTimes;
|
|
||||||
|
|
||||||
private readonly int columnCount;
|
|
||||||
|
|
||||||
public Overall(int columnCount)
|
|
||||||
{
|
|
||||||
this.columnCount = columnCount;
|
|
||||||
|
|
||||||
holdEndTimes = new double[columnCount];
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override double StrainValueOf(DifficultyHitObject current)
|
|
||||||
{
|
|
||||||
var maniaCurrent = (ManiaDifficultyHitObject)current;
|
|
||||||
var endTime = maniaCurrent.BaseObject.GetEndTime();
|
|
||||||
|
|
||||||
double holdFactor = 1.0; // Factor in case something else is held
|
|
||||||
double holdAddition = 0; // Addition to the current note in case it's a hold and has to be released awkwardly
|
|
||||||
|
|
||||||
for (int i = 0; i < columnCount; i++)
|
|
||||||
{
|
|
||||||
// If there is at least one other overlapping end or note, then we get an addition, buuuuuut...
|
|
||||||
if (current.BaseObject.StartTime < holdEndTimes[i] && endTime > holdEndTimes[i])
|
|
||||||
holdAddition = 1.0;
|
|
||||||
|
|
||||||
// ... this addition only is valid if there is _no_ other note with the same ending.
|
|
||||||
// Releasing multiple notes at the same time is just as easy as releasing one
|
|
||||||
if (endTime == holdEndTimes[i])
|
|
||||||
holdAddition = 0;
|
|
||||||
|
|
||||||
// We give a slight bonus if something is held meanwhile
|
|
||||||
if (holdEndTimes[i] > endTime)
|
|
||||||
holdFactor = 1.25;
|
|
||||||
}
|
|
||||||
|
|
||||||
holdEndTimes[maniaCurrent.BaseObject.Column] = endTime;
|
|
||||||
|
|
||||||
return (1 + holdAddition) * holdFactor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
80
osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||||
|
using osu.Game.Rulesets.Difficulty.Skills;
|
||||||
|
using osu.Game.Rulesets.Mania.Difficulty.Preprocessing;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Difficulty.Skills
|
||||||
|
{
|
||||||
|
public class Strain : Skill
|
||||||
|
{
|
||||||
|
private const double individual_decay_base = 0.125;
|
||||||
|
private const double overall_decay_base = 0.30;
|
||||||
|
|
||||||
|
protected override double SkillMultiplier => 1;
|
||||||
|
protected override double StrainDecayBase => 1;
|
||||||
|
|
||||||
|
private readonly double[] holdEndTimes;
|
||||||
|
private readonly double[] individualStrains;
|
||||||
|
|
||||||
|
private double individualStrain;
|
||||||
|
private double overallStrain;
|
||||||
|
|
||||||
|
public Strain(int totalColumns)
|
||||||
|
{
|
||||||
|
holdEndTimes = new double[totalColumns];
|
||||||
|
individualStrains = new double[totalColumns];
|
||||||
|
overallStrain = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override double StrainValueOf(DifficultyHitObject current)
|
||||||
|
{
|
||||||
|
var maniaCurrent = (ManiaDifficultyHitObject)current;
|
||||||
|
var endTime = maniaCurrent.BaseObject.GetEndTime();
|
||||||
|
var column = maniaCurrent.BaseObject.Column;
|
||||||
|
|
||||||
|
double holdFactor = 1.0; // Factor to all additional strains in case something else is held
|
||||||
|
double holdAddition = 0; // Addition to the current note in case it's a hold and has to be released awkwardly
|
||||||
|
|
||||||
|
// Fill up the holdEndTimes array
|
||||||
|
for (int i = 0; i < holdEndTimes.Length; ++i)
|
||||||
|
{
|
||||||
|
// If there is at least one other overlapping end or note, then we get an addition, buuuuuut...
|
||||||
|
if (Precision.DefinitelyBigger(holdEndTimes[i], maniaCurrent.BaseObject.StartTime, 1) && Precision.DefinitelyBigger(endTime, holdEndTimes[i], 1))
|
||||||
|
holdAddition = 1.0;
|
||||||
|
|
||||||
|
// ... this addition only is valid if there is _no_ other note with the same ending. Releasing multiple notes at the same time is just as easy as releasing 1
|
||||||
|
if (Precision.AlmostEquals(endTime, holdEndTimes[i], 1))
|
||||||
|
holdAddition = 0;
|
||||||
|
|
||||||
|
// We give a slight bonus to everything if something is held meanwhile
|
||||||
|
if (Precision.DefinitelyBigger(holdEndTimes[i], endTime, 1))
|
||||||
|
holdFactor = 1.25;
|
||||||
|
|
||||||
|
// Decay individual strains
|
||||||
|
individualStrains[i] = applyDecay(individualStrains[i], current.DeltaTime, individual_decay_base);
|
||||||
|
}
|
||||||
|
|
||||||
|
holdEndTimes[column] = endTime;
|
||||||
|
|
||||||
|
// Increase individual strain in own column
|
||||||
|
individualStrains[column] += 2.0 * holdFactor;
|
||||||
|
individualStrain = individualStrains[column];
|
||||||
|
|
||||||
|
overallStrain = applyDecay(overallStrain, current.DeltaTime, overall_decay_base) + (1 + holdAddition) * holdFactor;
|
||||||
|
|
||||||
|
return individualStrain + overallStrain - CurrentStrain;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override double GetPeakStrain(double offset)
|
||||||
|
=> applyDecay(individualStrain, offset - Previous[0].BaseObject.StartTime, individual_decay_base)
|
||||||
|
+ applyDecay(overallStrain, offset - Previous[0].BaseObject.StartTime, overall_decay_base);
|
||||||
|
|
||||||
|
private double applyDecay(double value, double deltaTime, double decayBase)
|
||||||
|
=> value * Math.Pow(decayBase, deltaTime / 1000);
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
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;
|
||||||
@ -14,6 +16,8 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders);
|
||||||
|
|
||||||
public override PlacementBlueprint CreatePlacementBlueprint() => new HoldNotePlacementBlueprint();
|
public override PlacementBlueprint CreatePlacementBlueprint() => new HoldNotePlacementBlueprint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
int minColumn = int.MaxValue;
|
int minColumn = int.MaxValue;
|
||||||
int maxColumn = int.MinValue;
|
int maxColumn = int.MinValue;
|
||||||
|
|
||||||
foreach (var obj in SelectedHitObjects.OfType<ManiaHitObject>())
|
foreach (var obj in EditorBeatmap.SelectedHitObjects.OfType<ManiaHitObject>())
|
||||||
{
|
{
|
||||||
if (obj.Column < minColumn)
|
if (obj.Column < minColumn)
|
||||||
minColumn = obj.Column;
|
minColumn = obj.Column;
|
||||||
@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
|
|
||||||
columnDelta = Math.Clamp(columnDelta, -minColumn, maniaPlayfield.TotalColumns - 1 - maxColumn);
|
columnDelta = Math.Clamp(columnDelta, -minColumn, maniaPlayfield.TotalColumns - 1 - maxColumn);
|
||||||
|
|
||||||
foreach (var obj in SelectedHitObjects.OfType<ManiaHitObject>())
|
foreach (var obj in EditorBeatmap.SelectedHitObjects.OfType<ManiaHitObject>())
|
||||||
obj.Column += columnDelta;
|
obj.Column += columnDelta;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
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;
|
||||||
@ -15,6 +17,8 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles);
|
||||||
|
|
||||||
public override PlacementBlueprint CreatePlacementBlueprint() => new NotePlacementBlueprint();
|
public override PlacementBlueprint CreatePlacementBlueprint() => new NotePlacementBlueprint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,18 +7,6 @@ namespace osu.Game.Rulesets.Mania.Judgements
|
|||||||
{
|
{
|
||||||
public class HoldNoteTickJudgement : ManiaJudgement
|
public class HoldNoteTickJudgement : ManiaJudgement
|
||||||
{
|
{
|
||||||
protected override int NumericResultFor(HitResult result) => result == MaxResult ? 20 : 0;
|
public override HitResult MaxResult => HitResult.LargeTickHit;
|
||||||
|
|
||||||
protected override double HealthIncreaseFor(HitResult result)
|
|
||||||
{
|
|
||||||
switch (result)
|
|
||||||
{
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
case HitResult.Perfect:
|
|
||||||
return 0.01;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,27 +8,33 @@ namespace osu.Game.Rulesets.Mania.Judgements
|
|||||||
{
|
{
|
||||||
public class ManiaJudgement : Judgement
|
public class ManiaJudgement : Judgement
|
||||||
{
|
{
|
||||||
protected override int NumericResultFor(HitResult result)
|
protected override double HealthIncreaseFor(HitResult result)
|
||||||
{
|
{
|
||||||
switch (result)
|
switch (result)
|
||||||
{
|
{
|
||||||
default:
|
case HitResult.LargeTickHit:
|
||||||
return 0;
|
return DEFAULT_MAX_HEALTH_INCREASE * 0.1;
|
||||||
|
|
||||||
|
case HitResult.LargeTickMiss:
|
||||||
|
return -DEFAULT_MAX_HEALTH_INCREASE * 0.1;
|
||||||
|
|
||||||
case HitResult.Meh:
|
case HitResult.Meh:
|
||||||
return 50;
|
return -DEFAULT_MAX_HEALTH_INCREASE * 0.5;
|
||||||
|
|
||||||
case HitResult.Ok:
|
case HitResult.Ok:
|
||||||
return 100;
|
return -DEFAULT_MAX_HEALTH_INCREASE * 0.3;
|
||||||
|
|
||||||
case HitResult.Good:
|
case HitResult.Good:
|
||||||
return 200;
|
return DEFAULT_MAX_HEALTH_INCREASE * 0.1;
|
||||||
|
|
||||||
case HitResult.Great:
|
case HitResult.Great:
|
||||||
return 300;
|
return DEFAULT_MAX_HEALTH_INCREASE * 0.8;
|
||||||
|
|
||||||
case HitResult.Perfect:
|
case HitResult.Perfect:
|
||||||
return 350;
|
return DEFAULT_MAX_HEALTH_INCREASE;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return base.HealthIncreaseFor(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,6 @@ using System.Linq;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Testing;
|
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Mania.Replays;
|
using osu.Game.Rulesets.Mania.Replays;
|
||||||
using osu.Game.Rulesets.Replays.Types;
|
using osu.Game.Rulesets.Replays.Types;
|
||||||
@ -35,7 +34,6 @@ using osu.Game.Screens.Ranking.Statistics;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania
|
namespace osu.Game.Rulesets.Mania
|
||||||
{
|
{
|
||||||
[ExcludeFromDynamicCompile]
|
|
||||||
public class ManiaRuleset : Ruleset, ILegacyRuleset
|
public class ManiaRuleset : Ruleset, ILegacyRuleset
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -51,7 +49,7 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
|
|
||||||
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap, this);
|
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap, this);
|
||||||
|
|
||||||
public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new ManiaPerformanceCalculator(this, beatmap, score);
|
public override PerformanceCalculator CreatePerformanceCalculator(DifficultyAttributes attributes, ScoreInfo score) => new ManiaPerformanceCalculator(this, attributes, score);
|
||||||
|
|
||||||
public const string SHORT_NAME = "mania";
|
public const string SHORT_NAME = "mania";
|
||||||
|
|
||||||
@ -321,6 +319,31 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
return (PlayfieldType)Enum.GetValues(typeof(PlayfieldType)).Cast<int>().OrderByDescending(i => i).First(v => variant >= v);
|
return (PlayfieldType)Enum.GetValues(typeof(PlayfieldType)).Cast<int>().OrderByDescending(i => i).First(v => variant >= v);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override IEnumerable<HitResult> GetValidHitResults()
|
||||||
|
{
|
||||||
|
return new[]
|
||||||
|
{
|
||||||
|
HitResult.Perfect,
|
||||||
|
HitResult.Great,
|
||||||
|
HitResult.Good,
|
||||||
|
HitResult.Ok,
|
||||||
|
HitResult.Meh,
|
||||||
|
|
||||||
|
HitResult.LargeTickHit,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string GetDisplayNameForHitResult(HitResult result)
|
||||||
|
{
|
||||||
|
switch (result)
|
||||||
|
{
|
||||||
|
case HitResult.LargeTickHit:
|
||||||
|
return "hold tick";
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.GetDisplayNameForHitResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => new[]
|
public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => new[]
|
||||||
{
|
{
|
||||||
new StatisticRow
|
new StatisticRow
|
||||||
|
@ -29,12 +29,13 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
new SettingsEnumDropdown<ManiaScrollingDirection>
|
new SettingsEnumDropdown<ManiaScrollingDirection>
|
||||||
{
|
{
|
||||||
LabelText = "Scrolling direction",
|
LabelText = "Scrolling direction",
|
||||||
Bindable = config.GetBindable<ManiaScrollingDirection>(ManiaRulesetSetting.ScrollDirection)
|
Current = config.GetBindable<ManiaScrollingDirection>(ManiaRulesetSetting.ScrollDirection)
|
||||||
},
|
},
|
||||||
new SettingsSlider<double, TimeSlider>
|
new SettingsSlider<double, TimeSlider>
|
||||||
{
|
{
|
||||||
LabelText = "Scroll speed",
|
LabelText = "Scroll speed",
|
||||||
Bindable = config.GetBindable<double>(ManiaRulesetSetting.ScrollTime)
|
Current = config.GetBindable<double>(ManiaRulesetSetting.ScrollTime),
|
||||||
|
KeyboardStep = 5
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
165
osu.Game.Rulesets.Mania/MathUtils/LegacySortHelper.cs
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.Contracts;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.MathUtils
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides access to .NET4.0 unstable sorting methods.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Source: https://referencesource.microsoft.com/#mscorlib/system/collections/generic/arraysorthelper.cs
|
||||||
|
/// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
/// </remarks>
|
||||||
|
internal static class LegacySortHelper<T>
|
||||||
|
{
|
||||||
|
private const int quick_sort_depth_threshold = 32;
|
||||||
|
|
||||||
|
public static void Sort(T[] keys, IComparer<T> comparer)
|
||||||
|
{
|
||||||
|
if (keys == null)
|
||||||
|
throw new ArgumentNullException(nameof(keys));
|
||||||
|
|
||||||
|
if (keys.Length == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
comparer ??= Comparer<T>.Default;
|
||||||
|
depthLimitedQuickSort(keys, 0, keys.Length - 1, comparer, quick_sort_depth_threshold);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void depthLimitedQuickSort(T[] keys, int left, int right, IComparer<T> comparer, int depthLimit)
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (depthLimit == 0)
|
||||||
|
{
|
||||||
|
heapsort(keys, left, right, comparer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int i = left;
|
||||||
|
int j = right;
|
||||||
|
|
||||||
|
// pre-sort the low, middle (pivot), and high values in place.
|
||||||
|
// this improves performance in the face of already sorted data, or
|
||||||
|
// data that is made up of multiple sorted runs appended together.
|
||||||
|
int middle = i + ((j - i) >> 1);
|
||||||
|
swapIfGreater(keys, comparer, i, middle); // swap the low with the mid point
|
||||||
|
swapIfGreater(keys, comparer, i, j); // swap the low with the high
|
||||||
|
swapIfGreater(keys, comparer, middle, j); // swap the middle with the high
|
||||||
|
|
||||||
|
T x = keys[middle];
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
while (comparer.Compare(keys[i], x) < 0) i++;
|
||||||
|
while (comparer.Compare(x, keys[j]) < 0) j--;
|
||||||
|
Contract.Assert(i >= left && j <= right, "(i>=left && j<=right) Sort failed - Is your IComparer bogus?");
|
||||||
|
if (i > j) break;
|
||||||
|
|
||||||
|
if (i < j)
|
||||||
|
{
|
||||||
|
T key = keys[i];
|
||||||
|
keys[i] = keys[j];
|
||||||
|
keys[j] = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
|
j--;
|
||||||
|
} while (i <= j);
|
||||||
|
|
||||||
|
// The next iteration of the while loop is to "recursively" sort the larger half of the array and the
|
||||||
|
// following calls recrusively sort the smaller half. So we subtrack one from depthLimit here so
|
||||||
|
// both sorts see the new value.
|
||||||
|
depthLimit--;
|
||||||
|
|
||||||
|
if (j - left <= right - i)
|
||||||
|
{
|
||||||
|
if (left < j) depthLimitedQuickSort(keys, left, j, comparer, depthLimit);
|
||||||
|
left = i;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (i < right) depthLimitedQuickSort(keys, i, right, comparer, depthLimit);
|
||||||
|
right = j;
|
||||||
|
}
|
||||||
|
} while (left < right);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void heapsort(T[] keys, int lo, int hi, IComparer<T> comparer)
|
||||||
|
{
|
||||||
|
Contract.Requires(keys != null);
|
||||||
|
Contract.Requires(comparer != null);
|
||||||
|
Contract.Requires(lo >= 0);
|
||||||
|
Contract.Requires(hi > lo);
|
||||||
|
Contract.Requires(hi < keys.Length);
|
||||||
|
|
||||||
|
int n = hi - lo + 1;
|
||||||
|
|
||||||
|
for (int i = n / 2; i >= 1; i = i - 1)
|
||||||
|
{
|
||||||
|
downHeap(keys, i, n, lo, comparer);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = n; i > 1; i = i - 1)
|
||||||
|
{
|
||||||
|
swap(keys, lo, lo + i - 1);
|
||||||
|
downHeap(keys, 1, i - 1, lo, comparer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void downHeap(T[] keys, int i, int n, int lo, IComparer<T> comparer)
|
||||||
|
{
|
||||||
|
Contract.Requires(keys != null);
|
||||||
|
Contract.Requires(comparer != null);
|
||||||
|
Contract.Requires(lo >= 0);
|
||||||
|
Contract.Requires(lo < keys.Length);
|
||||||
|
|
||||||
|
T d = keys[lo + i - 1];
|
||||||
|
|
||||||
|
while (i <= n / 2)
|
||||||
|
{
|
||||||
|
var child = 2 * i;
|
||||||
|
|
||||||
|
if (child < n && comparer.Compare(keys[lo + child - 1], keys[lo + child]) < 0)
|
||||||
|
{
|
||||||
|
child++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(comparer.Compare(d, keys[lo + child - 1]) < 0))
|
||||||
|
break;
|
||||||
|
|
||||||
|
keys[lo + i - 1] = keys[lo + child - 1];
|
||||||
|
i = child;
|
||||||
|
}
|
||||||
|
|
||||||
|
keys[lo + i - 1] = d;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void swap(T[] a, int i, int j)
|
||||||
|
{
|
||||||
|
if (i != j)
|
||||||
|
{
|
||||||
|
T t = a[i];
|
||||||
|
a[i] = a[j];
|
||||||
|
a[j] = t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void swapIfGreater(T[] keys, IComparer<T> comparer, int a, int b)
|
||||||
|
{
|
||||||
|
if (a != b)
|
||||||
|
{
|
||||||
|
if (comparer.Compare(keys[a], keys[b]) > 0)
|
||||||
|
{
|
||||||
|
T key = keys[a];
|
||||||
|
keys[a] = keys[b];
|
||||||
|
keys[b] = key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -39,6 +39,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
typeof(ManiaModKey7),
|
typeof(ManiaModKey7),
|
||||||
typeof(ManiaModKey8),
|
typeof(ManiaModKey8),
|
||||||
typeof(ManiaModKey9),
|
typeof(ManiaModKey9),
|
||||||
|
typeof(ManiaModKey10),
|
||||||
}.Except(new[] { GetType() }).ToArray();
|
}.Except(new[] { GetType() }).ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -239,11 +239,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
{
|
{
|
||||||
if (Tail.AllJudged)
|
if (Tail.AllJudged)
|
||||||
{
|
{
|
||||||
ApplyResult(r => r.Type = HitResult.Perfect);
|
ApplyResult(r => r.Type = r.Judgement.MaxResult);
|
||||||
endHold();
|
endHold();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Tail.Result.Type == HitResult.Miss)
|
if (Tail.Judged && !Tail.IsHit)
|
||||||
HasBroken = true;
|
HasBroken = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
if (!userTriggered)
|
if (!userTriggered)
|
||||||
{
|
{
|
||||||
if (!HitObject.HitWindows.CanBeHit(timeOffset))
|
if (!HitObject.HitWindows.CanBeHit(timeOffset))
|
||||||
ApplyResult(r => r.Type = HitResult.Miss);
|
ApplyResult(r => r.Type = r.Judgement.MinResult);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Effects;
|
using osu.Framework.Graphics.Effects;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Game.Rulesets.Scoring;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||||
{
|
{
|
||||||
@ -17,6 +16,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class DrawableHoldNoteTick : DrawableManiaHitObject<HoldNoteTick>
|
public class DrawableHoldNoteTick : DrawableManiaHitObject<HoldNoteTick>
|
||||||
{
|
{
|
||||||
|
public override bool DisplayResult => false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// References the time at which the user started holding the hold note.
|
/// References the time at which the user started holding the hold note.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -73,9 +74,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
var startTime = HoldStartTime?.Invoke();
|
var startTime = HoldStartTime?.Invoke();
|
||||||
|
|
||||||
if (startTime == null || startTime > HitObject.StartTime)
|
if (startTime == null || startTime > HitObject.StartTime)
|
||||||
ApplyResult(r => r.Type = HitResult.Miss);
|
ApplyResult(r => r.Type = r.Judgement.MinResult);
|
||||||
else
|
else
|
||||||
ApplyResult(r => r.Type = HitResult.Perfect);
|
ApplyResult(r => r.Type = r.Judgement.MaxResult);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,6 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
using osu.Game.Rulesets.Mania.UI;
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
using osu.Game.Rulesets.Scoring;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||||
{
|
{
|
||||||
@ -136,7 +135,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Causes this <see cref="DrawableManiaHitObject"/> to get missed, disregarding all conditions in implementations of <see cref="DrawableHitObject.CheckForResult"/>.
|
/// Causes this <see cref="DrawableManiaHitObject"/> to get missed, disregarding all conditions in implementations of <see cref="DrawableHitObject.CheckForResult"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void MissForcefully() => ApplyResult(r => r.Type = HitResult.Miss);
|
public void MissForcefully() => ApplyResult(r => r.Type = r.Judgement.MinResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract class DrawableManiaHitObject<TObject> : DrawableManiaHitObject
|
public abstract class DrawableManiaHitObject<TObject> : DrawableManiaHitObject
|
||||||
|
@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
if (!userTriggered)
|
if (!userTriggered)
|
||||||
{
|
{
|
||||||
if (!HitObject.HitWindows.CanBeHit(timeOffset))
|
if (!HitObject.HitWindows.CanBeHit(timeOffset))
|
||||||
ApplyResult(r => r.Type = HitResult.Miss);
|
ApplyResult(r => r.Type = r.Judgement.MinResult);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,6 +46,9 @@ namespace osu.Game.Rulesets.Mania.Replays
|
|||||||
|
|
||||||
public override Replay Generate()
|
public override Replay Generate()
|
||||||
{
|
{
|
||||||
|
if (Beatmap.HitObjects.Count == 0)
|
||||||
|
return Replay;
|
||||||
|
|
||||||
var pointGroups = generateActionPoints().GroupBy(a => a.Time).OrderBy(g => g.First().Time);
|
var pointGroups = generateActionPoints().GroupBy(a => a.Time).OrderBy(g => g.First().Time);
|
||||||
|
|
||||||
var actions = new List<ManiaAction>();
|
var actions = new List<ManiaAction>();
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
["soft-hitnormal"],
|
["soft-hitnormal"],
|
||||||
["drum-hitnormal"]
|
["drum-hitnormal"]
|
||||||
],
|
],
|
||||||
"Samples": ["drum-hitnormal"]
|
"Samples": ["-hitnormal"]
|
||||||
}, {
|
}, {
|
||||||
"StartTime": 1875.0,
|
"StartTime": 1875.0,
|
||||||
"EndTime": 2750.0,
|
"EndTime": 2750.0,
|
||||||
@ -19,7 +19,7 @@
|
|||||||
["soft-hitnormal"],
|
["soft-hitnormal"],
|
||||||
["drum-hitnormal"]
|
["drum-hitnormal"]
|
||||||
],
|
],
|
||||||
"Samples": ["drum-hitnormal"]
|
"Samples": ["-hitnormal"]
|
||||||
}]
|
}]
|
||||||
}, {
|
}, {
|
||||||
"StartTime": 3750.0,
|
"StartTime": 3750.0,
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"Mappings": [{
|
||||||
|
"RandomW": 3083084786,
|
||||||
|
"RandomX": 273326509,
|
||||||
|
"RandomY": 273553282,
|
||||||
|
"RandomZ": 2659838971,
|
||||||
|
"StartTime": 4836,
|
||||||
|
"Objects": [{
|
||||||
|
"StartTime": 4836,
|
||||||
|
"EndTime": 4836,
|
||||||
|
"Column": 0
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
osu file format v14
|
||||||
|
|
||||||
|
[General]
|
||||||
|
StackLeniency: 0.7
|
||||||
|
Mode: 0
|
||||||
|
|
||||||
|
[Difficulty]
|
||||||
|
HPDrainRate:1
|
||||||
|
CircleSize:4
|
||||||
|
OverallDifficulty:1
|
||||||
|
ApproachRate:9
|
||||||
|
SliderMultiplier:2.5
|
||||||
|
SliderTickRate:0.5
|
||||||
|
|
||||||
|
[TimingPoints]
|
||||||
|
34,431.654676258993,4,1,0,50,1,0
|
||||||
|
4782,-66.6666666666667,4,1,0,20,0,0
|
||||||
|
|
||||||
|
[HitObjects]
|
||||||
|
15,199,4836,22,0,L,1,46.8750017881394
|
@ -10,7 +10,5 @@ namespace osu.Game.Rulesets.Mania.Scoring
|
|||||||
protected override double DefaultAccuracyPortion => 0.95;
|
protected override double DefaultAccuracyPortion => 0.95;
|
||||||
|
|
||||||
protected override double DefaultComboPortion => 0.05;
|
protected override double DefaultComboPortion => 0.05;
|
||||||
|
|
||||||
public override HitWindows CreateHitWindows() => new ManiaHitWindows();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -130,6 +130,9 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
|||||||
|
|
||||||
private Drawable getResult(HitResult result)
|
private Drawable getResult(HitResult result)
|
||||||
{
|
{
|
||||||
|
if (!hitresult_mapping.ContainsKey(result))
|
||||||
|
return null;
|
||||||
|
|
||||||
string filename = this.GetManiaSkinConfig<string>(hitresult_mapping[result])?.Value
|
string filename = this.GetManiaSkinConfig<string>(hitresult_mapping[result])?.Value
|
||||||
?? default_hitresult_skin_filenames[result];
|
?? default_hitresult_skin_filenames[result];
|
||||||
|
|
||||||
|
@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
if (result.IsHit)
|
if (result.IsHit)
|
||||||
hitPolicy.HandleHit(judgedObject);
|
hitPolicy.HandleHit(judgedObject);
|
||||||
|
|
||||||
if (!result.IsHit || !judgedObject.DisplayResult || !DisplayJudgements.Value)
|
if (!result.IsHit || !DisplayJudgements.Value)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
HitObjectArea.Explosions.Add(hitExplosionPool.Get(e => e.Apply(result)));
|
HitObjectArea.Explosions.Add(hitExplosionPool.Get(e => e.Apply(result)));
|
||||||
|
@ -9,7 +9,7 @@ using osu.Game.Rulesets.Osu.Objects;
|
|||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Tests
|
namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||||
{
|
{
|
||||||
public class TestSceneHitCirclePlacementBlueprint : PlacementBlueprintTestScene
|
public class TestSceneHitCirclePlacementBlueprint : PlacementBlueprintTestScene
|
||||||
{
|
{
|
@ -11,7 +11,7 @@ using osu.Game.Rulesets.Osu.Objects.Drawables;
|
|||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Tests
|
namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||||
{
|
{
|
||||||
public class TestSceneHitCircleSelectionBlueprint : SelectionBlueprintTestScene
|
public class TestSceneHitCircleSelectionBlueprint : SelectionBlueprintTestScene
|
||||||
{
|
{
|
@ -0,0 +1,90 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class TestSceneObjectObjectSnap : TestSceneOsuEditor
|
||||||
|
{
|
||||||
|
private OsuPlayfield playfield;
|
||||||
|
|
||||||
|
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(Ruleset.Value, false);
|
||||||
|
|
||||||
|
public override void SetUpSteps()
|
||||||
|
{
|
||||||
|
base.SetUpSteps();
|
||||||
|
AddStep("get playfield", () => playfield = Editor.ChildrenOfType<OsuPlayfield>().First());
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase(true)]
|
||||||
|
[TestCase(false)]
|
||||||
|
public void TestHitCircleSnapsToOtherHitCircle(bool distanceSnapEnabled)
|
||||||
|
{
|
||||||
|
AddStep("move mouse to centre", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre));
|
||||||
|
|
||||||
|
if (!distanceSnapEnabled)
|
||||||
|
AddStep("disable distance snap", () => InputManager.Key(Key.Q));
|
||||||
|
|
||||||
|
AddStep("enter placement mode", () => InputManager.Key(Key.Number2));
|
||||||
|
|
||||||
|
AddStep("place first object", () => InputManager.Click(MouseButton.Left));
|
||||||
|
|
||||||
|
AddStep("move mouse slightly", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * 0.02f, 0)));
|
||||||
|
|
||||||
|
AddStep("place second object", () => InputManager.Click(MouseButton.Left));
|
||||||
|
|
||||||
|
AddAssert("both objects at same location", () =>
|
||||||
|
{
|
||||||
|
var objects = EditorBeatmap.HitObjects;
|
||||||
|
|
||||||
|
var first = (OsuHitObject)objects.First();
|
||||||
|
var second = (OsuHitObject)objects.Last();
|
||||||
|
|
||||||
|
return first.Position == second.Position;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestHitCircleSnapsToSliderEnd()
|
||||||
|
{
|
||||||
|
AddStep("move mouse to centre", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre));
|
||||||
|
|
||||||
|
AddStep("disable distance snap", () => InputManager.Key(Key.Q));
|
||||||
|
|
||||||
|
AddStep("enter slider placement mode", () => InputManager.Key(Key.Number3));
|
||||||
|
|
||||||
|
AddStep("start slider placement", () => InputManager.Click(MouseButton.Left));
|
||||||
|
|
||||||
|
AddStep("move to place end", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * 0.185f, 0)));
|
||||||
|
|
||||||
|
AddStep("end slider placement", () => InputManager.Click(MouseButton.Right));
|
||||||
|
|
||||||
|
AddStep("enter circle placement mode", () => InputManager.Key(Key.Number2));
|
||||||
|
|
||||||
|
AddStep("move mouse slightly", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * 0.20f, 0)));
|
||||||
|
|
||||||
|
AddStep("place second object", () => InputManager.Click(MouseButton.Left));
|
||||||
|
|
||||||
|
AddAssert("circle is at slider's end", () =>
|
||||||
|
{
|
||||||
|
var objects = EditorBeatmap.HitObjects;
|
||||||
|
|
||||||
|
var first = (Slider)objects.First();
|
||||||
|
var second = (OsuHitObject)objects.Last();
|
||||||
|
|
||||||
|
return Precision.AlmostEquals(first.EndPosition, second.Position);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|