mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 14:17:26 +08:00
Merge branch 'master' into kps
This commit is contained in:
commit
da407aa827
@ -21,7 +21,7 @@
|
||||
]
|
||||
},
|
||||
"ppy.localisationanalyser.tools": {
|
||||
"version": "2022.607.0",
|
||||
"version": "2022.809.0",
|
||||
"commands": [
|
||||
"localisation"
|
||||
]
|
||||
|
@ -53,3 +53,7 @@ dotnet_diagnostic.CA2225.severity = none
|
||||
|
||||
# Banned APIs
|
||||
dotnet_diagnostic.RS0030.severity = error
|
||||
|
||||
# Temporarily disable analysing CanBeNull = true in NRT contexts due to mobile issues.
|
||||
# See: https://github.com/ppy/osu/pull/19677
|
||||
dotnet_diagnostic.OSUF001.severity = none
|
@ -4,8 +4,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
@ -50,9 +48,6 @@ namespace osu.Game.Rulesets.Pippidon
|
||||
new KeyBinding(InputKey.X, PippidonAction.Button2),
|
||||
};
|
||||
|
||||
public override Drawable CreateIcon() => new Sprite
|
||||
{
|
||||
Texture = new TextureStore(new TextureLoaderStore(CreateResourceStore()), false).Get("Textures/coin"),
|
||||
};
|
||||
public override Drawable CreateIcon() => new PippidonRulesetIcon(this);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,26 @@
|
||||
// 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.Allocation;
|
||||
using osu.Framework.Graphics.Rendering;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
|
||||
namespace osu.Game.Rulesets.Pippidon
|
||||
{
|
||||
public class PippidonRulesetIcon : Sprite
|
||||
{
|
||||
private readonly Ruleset ruleset;
|
||||
|
||||
public PippidonRulesetIcon(Ruleset ruleset)
|
||||
{
|
||||
this.ruleset = ruleset;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(IRenderer renderer)
|
||||
{
|
||||
Texture = new TextureStore(renderer, new TextureLoaderStore(ruleset.CreateResourceStore()), false).Get("Textures/coin");
|
||||
}
|
||||
}
|
||||
}
|
@ -4,8 +4,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
@ -47,10 +45,6 @@ namespace osu.Game.Rulesets.Pippidon
|
||||
new KeyBinding(InputKey.S, PippidonAction.MoveDown),
|
||||
};
|
||||
|
||||
public override Drawable CreateIcon() => new Sprite
|
||||
{
|
||||
Margin = new MarginPadding { Top = 3 },
|
||||
Texture = new TextureStore(new TextureLoaderStore(CreateResourceStore()), false).Get("Textures/coin"),
|
||||
};
|
||||
public override Drawable CreateIcon() => new PippidonRulesetIcon(this);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,29 @@
|
||||
// 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.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Rendering;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
|
||||
namespace osu.Game.Rulesets.Pippidon
|
||||
{
|
||||
public class PippidonRulesetIcon : Sprite
|
||||
{
|
||||
private readonly Ruleset ruleset;
|
||||
|
||||
public PippidonRulesetIcon(Ruleset ruleset)
|
||||
{
|
||||
this.ruleset = ruleset;
|
||||
|
||||
Margin = new MarginPadding { Top = 3 };
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(IRenderer renderer)
|
||||
{
|
||||
Texture = new TextureStore(renderer, new TextureLoaderStore(ruleset.CreateResourceStore()), false).Get("Textures/coin");
|
||||
}
|
||||
}
|
||||
}
|
@ -51,11 +51,11 @@
|
||||
<Reference Include="Java.Interop" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.722.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.722.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.819.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.819.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Transitive Dependencies">
|
||||
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
||||
<PackageReference Include="Realm" Version="10.14.0" />
|
||||
<PackageReference Include="Realm" Version="10.15.1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -106,9 +106,9 @@ namespace osu.Android
|
||||
|
||||
private class AndroidBatteryInfo : BatteryInfo
|
||||
{
|
||||
public override double ChargeLevel => Battery.ChargeLevel;
|
||||
public override double? ChargeLevel => Battery.ChargeLevel;
|
||||
|
||||
public override bool IsCharging => Battery.PowerSource != BatteryPowerSource.Battery;
|
||||
public override bool OnBattery => Battery.PowerSource == BatteryPowerSource.Battery;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
using DiscordRPC;
|
||||
@ -26,15 +24,15 @@ namespace osu.Desktop
|
||||
{
|
||||
private const string client_id = "367827983903490050";
|
||||
|
||||
private DiscordRpcClient client;
|
||||
private DiscordRpcClient client = null!;
|
||||
|
||||
[Resolved]
|
||||
private IBindable<RulesetInfo> ruleset { get; set; }
|
||||
private IBindable<RulesetInfo> ruleset { get; set; } = null!;
|
||||
|
||||
private IBindable<APIUser> user;
|
||||
private IBindable<APIUser> user = null!;
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; }
|
||||
private IAPIProvider api { get; set; } = null!;
|
||||
|
||||
private readonly IBindable<UserStatus> status = new Bindable<UserStatus>();
|
||||
private readonly IBindable<UserActivity> activity = new Bindable<UserActivity>();
|
||||
@ -130,7 +128,7 @@ namespace osu.Desktop
|
||||
presence.Assets.LargeImageText = string.Empty;
|
||||
else
|
||||
{
|
||||
if (user.Value.RulesetsStatistics != null && user.Value.RulesetsStatistics.TryGetValue(ruleset.Value.ShortName, out UserStatistics statistics))
|
||||
if (user.Value.RulesetsStatistics != null && user.Value.RulesetsStatistics.TryGetValue(ruleset.Value.ShortName, out UserStatistics? statistics))
|
||||
presence.Assets.LargeImageText = $"{user.Value.Username}" + (statistics.GlobalRank > 0 ? $" (rank #{statistics.GlobalRank:N0})" : string.Empty);
|
||||
else
|
||||
presence.Assets.LargeImageText = $"{user.Value.Username}" + (user.Value.Statistics?.GlobalRank > 0 ? $" (rank #{user.Value.Statistics.GlobalRank:N0})" : string.Empty);
|
||||
@ -164,7 +162,7 @@ namespace osu.Desktop
|
||||
});
|
||||
}
|
||||
|
||||
private IBeatmapInfo getBeatmap(UserActivity activity)
|
||||
private IBeatmapInfo? getBeatmap(UserActivity activity)
|
||||
{
|
||||
switch (activity)
|
||||
{
|
||||
@ -183,10 +181,10 @@ namespace osu.Desktop
|
||||
switch (activity)
|
||||
{
|
||||
case UserActivity.InGame game:
|
||||
return game.BeatmapInfo.ToString();
|
||||
return game.BeatmapInfo.ToString() ?? string.Empty;
|
||||
|
||||
case UserActivity.Editing edit:
|
||||
return edit.BeatmapInfo.ToString();
|
||||
return edit.BeatmapInfo.ToString() ?? string.Empty;
|
||||
|
||||
case UserActivity.InLobby lobby:
|
||||
return privacyMode.Value == DiscordRichPresenceMode.Limited ? string.Empty : lobby.Room.Name.Value;
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace osu.Desktop.LegacyIpc
|
||||
{
|
||||
/// <summary>
|
||||
@ -13,7 +11,7 @@ namespace osu.Desktop.LegacyIpc
|
||||
/// </remarks>
|
||||
public class LegacyIpcDifficultyCalculationRequest
|
||||
{
|
||||
public string BeatmapFile { get; set; }
|
||||
public string BeatmapFile { get; set; } = string.Empty;
|
||||
public int RulesetId { get; set; }
|
||||
public int Mods { get; set; }
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace osu.Desktop.LegacyIpc
|
||||
{
|
||||
/// <summary>
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Framework.Platform;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
@ -39,17 +37,20 @@ namespace osu.Desktop.LegacyIpc
|
||||
public new object Value
|
||||
{
|
||||
get => base.Value;
|
||||
set => base.Value = new Data
|
||||
{
|
||||
MessageType = value.GetType().Name,
|
||||
MessageData = value
|
||||
};
|
||||
set => base.Value = new Data(value.GetType().Name, value);
|
||||
}
|
||||
|
||||
public class Data
|
||||
{
|
||||
public string MessageType { get; set; }
|
||||
public object MessageData { get; set; }
|
||||
public string MessageType { get; }
|
||||
|
||||
public object MessageData { get; }
|
||||
|
||||
public Data(string messageType, object messageData)
|
||||
{
|
||||
MessageType = messageType;
|
||||
MessageData = messageData;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,8 @@ using osu.Game.IPC;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Overlays.Settings.Sections;
|
||||
using osu.Game.Overlays.Settings.Sections.Input;
|
||||
using osu.Game.Utils;
|
||||
using SDL2;
|
||||
|
||||
namespace osu.Desktop
|
||||
{
|
||||
@ -166,6 +168,8 @@ namespace osu.Desktop
|
||||
}
|
||||
}
|
||||
|
||||
protected override BatteryInfo CreateBatteryInfo() => new SDL2BatteryInfo();
|
||||
|
||||
private readonly List<string> importableFiles = new List<string>();
|
||||
private ScheduledDelegate? importSchedule;
|
||||
|
||||
@ -206,5 +210,23 @@ namespace osu.Desktop
|
||||
base.Dispose(isDisposing);
|
||||
osuSchemeLinkIPCChannel?.Dispose();
|
||||
}
|
||||
|
||||
private class SDL2BatteryInfo : BatteryInfo
|
||||
{
|
||||
public override double? ChargeLevel
|
||||
{
|
||||
get
|
||||
{
|
||||
SDL.SDL_GetPowerInfo(out _, out int percentage);
|
||||
|
||||
if (percentage == -1)
|
||||
return null;
|
||||
|
||||
return percentage / 100.0;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool OnBattery => SDL.SDL_GetPowerInfo(out _, out _) == SDL.SDL_PowerState.SDL_POWERSTATE_ON_BATTERY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.Versioning;
|
||||
@ -21,9 +19,13 @@ namespace osu.Desktop
|
||||
{
|
||||
public static class Program
|
||||
{
|
||||
#if DEBUG
|
||||
private const string base_game_name = @"osu-development";
|
||||
#else
|
||||
private const string base_game_name = @"osu";
|
||||
#endif
|
||||
|
||||
private static LegacyTcpIpcProvider legacyIpc;
|
||||
private static LegacyTcpIpcProvider? legacyIpc;
|
||||
|
||||
[STAThread]
|
||||
public static void Main(string[] args)
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Security.Principal;
|
||||
using osu.Framework;
|
||||
@ -21,7 +19,7 @@ namespace osu.Desktop.Security
|
||||
public class ElevatedPrivilegesChecker : Component
|
||||
{
|
||||
[Resolved]
|
||||
private INotificationOverlay notifications { get; set; }
|
||||
private INotificationOverlay notifications { get; set; } = null!;
|
||||
|
||||
private bool elevated;
|
||||
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading.Tasks;
|
||||
@ -26,8 +24,8 @@ namespace osu.Desktop.Updater
|
||||
[SupportedOSPlatform("windows")]
|
||||
public class SquirrelUpdateManager : osu.Game.Updater.UpdateManager
|
||||
{
|
||||
private UpdateManager updateManager;
|
||||
private INotificationOverlay notificationOverlay;
|
||||
private UpdateManager? updateManager;
|
||||
private INotificationOverlay notificationOverlay = null!;
|
||||
|
||||
public Task PrepareUpdateAsync() => UpdateManager.RestartAppWhenExited();
|
||||
|
||||
@ -50,12 +48,12 @@ namespace osu.Desktop.Updater
|
||||
|
||||
protected override async Task<bool> PerformUpdateCheck() => await checkForUpdateAsync().ConfigureAwait(false);
|
||||
|
||||
private async Task<bool> 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?
|
||||
bool scheduleRecheck = true;
|
||||
|
||||
const string github_token = null; // TODO: populate.
|
||||
const string? github_token = null; // TODO: populate.
|
||||
|
||||
try
|
||||
{
|
||||
@ -145,7 +143,7 @@ namespace osu.Desktop.Updater
|
||||
private class UpdateCompleteNotification : ProgressCompletionNotification
|
||||
{
|
||||
[Resolved]
|
||||
private OsuGame game { get; set; }
|
||||
private OsuGame game { get; set; } = null!;
|
||||
|
||||
public UpdateCompleteNotification(SquirrelUpdateManager updateManager)
|
||||
{
|
||||
@ -154,7 +152,7 @@ namespace osu.Desktop.Updater
|
||||
Activated = () =>
|
||||
{
|
||||
updateManager.PrepareUpdateAsync()
|
||||
.ContinueWith(_ => updateManager.Schedule(() => game?.AttemptExit()));
|
||||
.ContinueWith(_ => updateManager.Schedule(() => game.AttemptExit()));
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
@ -14,12 +12,12 @@ namespace osu.Desktop.Windows
|
||||
{
|
||||
public class GameplayWinKeyBlocker : Component
|
||||
{
|
||||
private Bindable<bool> disableWinKey;
|
||||
private IBindable<bool> localUserPlaying;
|
||||
private IBindable<bool> isActive;
|
||||
private Bindable<bool> disableWinKey = null!;
|
||||
private IBindable<bool> localUserPlaying = null!;
|
||||
private IBindable<bool> isActive = null!;
|
||||
|
||||
[Resolved]
|
||||
private GameHost host { get; set; }
|
||||
private GameHost host { get; set; } = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ILocalUserPlayInfo localUserInfo, OsuConfigManager config)
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
@ -21,7 +19,7 @@ namespace osu.Desktop.Windows
|
||||
private const int wm_syskeyup = 261;
|
||||
|
||||
//Resharper disable once NotAccessedField.Local
|
||||
private static LowLevelKeyboardProcDelegate keyboardHookDelegate; // keeping a reference alive for the GC
|
||||
private static LowLevelKeyboardProcDelegate? keyboardHookDelegate; // keeping a reference alive for the GC
|
||||
private static IntPtr keyHook;
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
|
@ -2,7 +2,6 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
@ -36,9 +35,9 @@ namespace osu.Game.Rulesets.Catch.Mods
|
||||
|
||||
public override float DefaultFlashlightSize => 350;
|
||||
|
||||
protected override Flashlight CreateFlashlight() => new CatchFlashlight(this, playfield.AsNonNull());
|
||||
protected override Flashlight CreateFlashlight() => new CatchFlashlight(this, playfield);
|
||||
|
||||
private CatchPlayfield? playfield;
|
||||
private CatchPlayfield playfield = null!;
|
||||
|
||||
public override void ApplyToDrawableRuleset(DrawableRuleset<CatchHitObject> drawableRuleset)
|
||||
{
|
||||
|
@ -1,7 +1,6 @@
|
||||
// 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.Diagnostics;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Input.Bindings;
|
||||
@ -19,7 +18,7 @@ namespace osu.Game.Rulesets.Catch.Mods
|
||||
{
|
||||
public override string Description => @"Use the mouse to control the catcher.";
|
||||
|
||||
private DrawableRuleset<CatchHitObject>? drawableRuleset;
|
||||
private DrawableRuleset<CatchHitObject> drawableRuleset = null!;
|
||||
|
||||
public void ApplyToDrawableRuleset(DrawableRuleset<CatchHitObject> drawableRuleset)
|
||||
{
|
||||
@ -28,8 +27,6 @@ namespace osu.Game.Rulesets.Catch.Mods
|
||||
|
||||
public void ApplyToPlayer(Player player)
|
||||
{
|
||||
Debug.Assert(drawableRuleset != null);
|
||||
|
||||
if (!drawableRuleset.HasReplayLoaded.Value)
|
||||
drawableRuleset.Cursor.Add(new MouseInputHelper((CatchPlayfield)drawableRuleset.Playfield));
|
||||
}
|
||||
|
@ -16,11 +16,11 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
{
|
||||
protected override string ResourceAssembly => "osu.Game.Rulesets.Mania";
|
||||
|
||||
[TestCase(2.3449735700206298d, 242, "diffcalc-test")]
|
||||
[TestCase(2.3493769750220914d, 242, "diffcalc-test")]
|
||||
public void Test(double expectedStarRating, int expectedMaxCombo, string name)
|
||||
=> base.Test(expectedStarRating, expectedMaxCombo, name);
|
||||
|
||||
[TestCase(2.7879104989252959d, 242, "diffcalc-test")]
|
||||
[TestCase(2.797245912537965d, 242, "diffcalc-test")]
|
||||
public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxCombo, string name)
|
||||
=> Test(expectedStarRating, expectedMaxCombo, name, new ManiaModDoubleTime());
|
||||
|
||||
|
52
osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneBarLine.cs
Normal file
52
osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneBarLine.cs
Normal file
@ -0,0 +1,52 @@
|
||||
// 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 NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||
{
|
||||
public class TestSceneBarLine : ManiaSkinnableTestScene
|
||||
{
|
||||
[Test]
|
||||
public void TestMinor()
|
||||
{
|
||||
AddStep("Create barlines", () => recreate());
|
||||
}
|
||||
|
||||
private void recreate(Func<IEnumerable<BarLine>>? createBarLines = null)
|
||||
{
|
||||
var stageDefinitions = new List<StageDefinition>
|
||||
{
|
||||
new StageDefinition { Columns = 4 },
|
||||
};
|
||||
|
||||
SetContents(_ => new ManiaPlayfield(stageDefinitions).With(s =>
|
||||
{
|
||||
if (createBarLines != null)
|
||||
{
|
||||
var barLines = createBarLines();
|
||||
|
||||
foreach (var b in barLines)
|
||||
s.Add(b);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 64; i++)
|
||||
{
|
||||
s.Add(new BarLine
|
||||
{
|
||||
StartTime = Time.Current + i * 500,
|
||||
Major = i % 4 == 0,
|
||||
});
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
@ -21,7 +21,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills
|
||||
protected override double SkillMultiplier => 1;
|
||||
protected override double StrainDecayBase => 1;
|
||||
|
||||
private readonly double[] holdEndTimes;
|
||||
private readonly double[] startTimes;
|
||||
private readonly double[] endTimes;
|
||||
private readonly double[] individualStrains;
|
||||
|
||||
private double individualStrain;
|
||||
@ -30,7 +31,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills
|
||||
public Strain(Mod[] mods, int totalColumns)
|
||||
: base(mods)
|
||||
{
|
||||
holdEndTimes = new double[totalColumns];
|
||||
startTimes = new double[totalColumns];
|
||||
endTimes = new double[totalColumns];
|
||||
individualStrains = new double[totalColumns];
|
||||
overallStrain = 1;
|
||||
}
|
||||
@ -38,32 +40,27 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills
|
||||
protected override double StrainValueOf(DifficultyHitObject current)
|
||||
{
|
||||
var maniaCurrent = (ManiaDifficultyHitObject)current;
|
||||
double startTime = maniaCurrent.StartTime;
|
||||
double endTime = maniaCurrent.EndTime;
|
||||
int column = maniaCurrent.BaseObject.Column;
|
||||
double closestEndTime = Math.Abs(endTime - maniaCurrent.LastObject.StartTime); // Lowest value we can assume with the current information
|
||||
|
||||
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
|
||||
bool isOverlapping = false;
|
||||
|
||||
// Fill up the holdEndTimes array
|
||||
for (int i = 0; i < holdEndTimes.Length; ++i)
|
||||
double closestEndTime = Math.Abs(endTime - startTime); // Lowest value we can assume with the current information
|
||||
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
|
||||
|
||||
for (int i = 0; i < endTimes.Length; ++i)
|
||||
{
|
||||
// The current note is overlapped if a previous note or end is overlapping the current note body
|
||||
isOverlapping |= Precision.DefinitelyBigger(holdEndTimes[i], maniaCurrent.StartTime, 1) && Precision.DefinitelyBigger(endTime, holdEndTimes[i], 1);
|
||||
isOverlapping |= Precision.DefinitelyBigger(endTimes[i], startTime, 1) && Precision.DefinitelyBigger(endTime, endTimes[i], 1);
|
||||
|
||||
// We give a slight bonus to everything if something is held meanwhile
|
||||
if (Precision.DefinitelyBigger(holdEndTimes[i], endTime, 1))
|
||||
if (Precision.DefinitelyBigger(endTimes[i], endTime, 1))
|
||||
holdFactor = 1.25;
|
||||
|
||||
closestEndTime = Math.Min(closestEndTime, Math.Abs(endTime - holdEndTimes[i]));
|
||||
|
||||
// Decay individual strains
|
||||
individualStrains[i] = applyDecay(individualStrains[i], current.DeltaTime, individual_decay_base);
|
||||
closestEndTime = Math.Min(closestEndTime, Math.Abs(endTime - endTimes[i]));
|
||||
}
|
||||
|
||||
holdEndTimes[column] = endTime;
|
||||
|
||||
// The hold addition is given if there was an overlap, however it is only valid if there are no other note with a similar ending.
|
||||
// Releasing multiple notes is just as easy as releasing 1. Nerfs the hold addition by half if the closest release is release_threshold away.
|
||||
// holdAddition
|
||||
@ -77,12 +74,22 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills
|
||||
if (isOverlapping)
|
||||
holdAddition = 1 / (1 + Math.Exp(0.5 * (release_threshold - closestEndTime)));
|
||||
|
||||
// Increase individual strain in own column
|
||||
// Decay and increase individualStrains in own column
|
||||
individualStrains[column] = applyDecay(individualStrains[column], startTime - startTimes[column], individual_decay_base);
|
||||
individualStrains[column] += 2.0 * holdFactor;
|
||||
individualStrain = individualStrains[column];
|
||||
|
||||
overallStrain = applyDecay(overallStrain, current.DeltaTime, overall_decay_base) + (1 + holdAddition) * holdFactor;
|
||||
// For notes at the same time (in a chord), the individualStrain should be the hardest individualStrain out of those columns
|
||||
individualStrain = maniaCurrent.DeltaTime <= 1 ? Math.Max(individualStrain, individualStrains[column]) : individualStrains[column];
|
||||
|
||||
// Decay and increase overallStrain
|
||||
overallStrain = applyDecay(overallStrain, current.DeltaTime, overall_decay_base);
|
||||
overallStrain += (1 + holdAddition) * holdFactor;
|
||||
|
||||
// Update startTimes and endTimes arrays
|
||||
startTimes[column] = startTime;
|
||||
endTimes[column] = endTime;
|
||||
|
||||
// By subtracting CurrentStrain, this skill effectively only considers the maximum strain of any one hitobject within each strain section.
|
||||
return individualStrain + overallStrain - CurrentStrain;
|
||||
}
|
||||
|
||||
|
@ -43,6 +43,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Setup
|
||||
private void updateBeatmap()
|
||||
{
|
||||
Beatmap.BeatmapInfo.SpecialStyle = specialStyle.Current.Value;
|
||||
Beatmap.SaveState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
|
||||
public override string Acronym => "CS";
|
||||
|
||||
public override double ScoreMultiplier => 1;
|
||||
public override double ScoreMultiplier => 0.9;
|
||||
|
||||
public override string Description => "No more tricky speed changes!";
|
||||
|
||||
|
@ -1,12 +1,9 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
{
|
||||
@ -16,21 +13,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
/// </summary>
|
||||
public class DrawableBarLine : DrawableManiaHitObject<BarLine>
|
||||
{
|
||||
/// <summary>
|
||||
/// Height of major bar line triangles.
|
||||
/// </summary>
|
||||
private const float triangle_height = 12;
|
||||
|
||||
/// <summary>
|
||||
/// Offset of the major bar line triangles from the sides of the bar line.
|
||||
/// </summary>
|
||||
private const float triangle_offset = 9;
|
||||
|
||||
public DrawableBarLine(BarLine barLine)
|
||||
: base(barLine)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
Height = 2f;
|
||||
Height = barLine.Major ? 1.7f : 1.2f;
|
||||
|
||||
AddInternal(new Box
|
||||
{
|
||||
@ -38,34 +25,33 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = new Color4(255, 204, 33, 255),
|
||||
Alpha = barLine.Major ? 0.5f : 0.2f
|
||||
});
|
||||
|
||||
if (barLine.Major)
|
||||
{
|
||||
AddInternal(new EquilateralTriangle
|
||||
Vector2 size = new Vector2(22, 6);
|
||||
const float line_offset = 4;
|
||||
|
||||
AddInternal(new Circle
|
||||
{
|
||||
Name = "Left triangle",
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.TopCentre,
|
||||
Size = new Vector2(triangle_height),
|
||||
X = -triangle_offset,
|
||||
Rotation = 90
|
||||
Name = "Left line",
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreRight,
|
||||
|
||||
Size = size,
|
||||
X = -line_offset,
|
||||
});
|
||||
|
||||
AddInternal(new EquilateralTriangle
|
||||
AddInternal(new Circle
|
||||
{
|
||||
Name = "Right triangle",
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.TopCentre,
|
||||
Size = new Vector2(triangle_height),
|
||||
X = triangle_offset,
|
||||
Rotation = -90
|
||||
Name = "Right line",
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Size = size,
|
||||
X = line_offset,
|
||||
});
|
||||
}
|
||||
|
||||
if (!barLine.Major)
|
||||
Alpha = 0.2f;
|
||||
}
|
||||
|
||||
protected override void UpdateInitialTransforms()
|
||||
|
@ -9,7 +9,7 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Animations;
|
||||
using osu.Framework.Graphics.OpenGL.Textures;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
|
@ -9,7 +9,6 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Animations;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.OpenGL.Textures;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
|
213
osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs
Normal file
213
osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs
Normal file
@ -0,0 +1,213 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
{
|
||||
public class TestSceneObjectMerging : TestSceneOsuEditor
|
||||
{
|
||||
[Test]
|
||||
public void TestSimpleMerge()
|
||||
{
|
||||
HitCircle? circle1 = null;
|
||||
HitCircle? circle2 = null;
|
||||
|
||||
AddStep("select first two circles", () =>
|
||||
{
|
||||
circle1 = (HitCircle)EditorBeatmap.HitObjects.First(h => h is HitCircle);
|
||||
circle2 = (HitCircle)EditorBeatmap.HitObjects.First(h => h is HitCircle && h != circle1);
|
||||
EditorClock.Seek(circle1.StartTime);
|
||||
EditorBeatmap.SelectedHitObjects.Add(circle1);
|
||||
EditorBeatmap.SelectedHitObjects.Add(circle2);
|
||||
});
|
||||
|
||||
mergeSelection();
|
||||
|
||||
AddAssert("slider created", () => circle1 is not null && circle2 is not null && sliderCreatedFor(
|
||||
(pos: circle1.Position, pathType: PathType.Linear),
|
||||
(pos: circle2.Position, pathType: null)));
|
||||
|
||||
AddStep("undo", () => Editor.Undo());
|
||||
AddAssert("merged objects restored", () => circle1 is not null && circle2 is not null && objectsRestored(circle1, circle2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMergeCircleSlider()
|
||||
{
|
||||
HitCircle? circle1 = null;
|
||||
Slider? slider = null;
|
||||
HitCircle? circle2 = null;
|
||||
|
||||
AddStep("select a circle, slider, circle", () =>
|
||||
{
|
||||
circle1 = (HitCircle)EditorBeatmap.HitObjects.First(h => h is HitCircle);
|
||||
slider = (Slider)EditorBeatmap.HitObjects.First(h => h is Slider && h.StartTime > circle1.StartTime);
|
||||
circle2 = (HitCircle)EditorBeatmap.HitObjects.First(h => h is HitCircle && h.StartTime > slider.StartTime);
|
||||
EditorClock.Seek(circle1.StartTime);
|
||||
EditorBeatmap.SelectedHitObjects.Add(circle1);
|
||||
EditorBeatmap.SelectedHitObjects.Add(slider);
|
||||
EditorBeatmap.SelectedHitObjects.Add(circle2);
|
||||
});
|
||||
|
||||
mergeSelection();
|
||||
|
||||
AddAssert("slider created", () =>
|
||||
{
|
||||
if (circle1 is null || circle2 is null || slider is null)
|
||||
return false;
|
||||
|
||||
var controlPoints = slider.Path.ControlPoints;
|
||||
(Vector2, PathType?)[] args = new (Vector2, PathType?)[controlPoints.Count + 2];
|
||||
args[0] = (circle1.Position, PathType.Linear);
|
||||
|
||||
for (int i = 0; i < controlPoints.Count; i++)
|
||||
{
|
||||
args[i + 1] = (controlPoints[i].Position + slider.Position, i == controlPoints.Count - 1 ? PathType.Linear : controlPoints[i].Type);
|
||||
}
|
||||
|
||||
args[^1] = (circle2.Position, null);
|
||||
return sliderCreatedFor(args);
|
||||
});
|
||||
|
||||
AddStep("undo", () => Editor.Undo());
|
||||
AddAssert("merged objects restored", () => circle1 is not null && circle2 is not null && slider is not null && objectsRestored(circle1, slider, circle2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMergeSliderSlider()
|
||||
{
|
||||
Slider? slider1 = null;
|
||||
SliderPath? slider1Path = null;
|
||||
Slider? slider2 = null;
|
||||
|
||||
AddStep("select two sliders", () =>
|
||||
{
|
||||
slider1 = (Slider)EditorBeatmap.HitObjects.First(h => h is Slider);
|
||||
slider1Path = new SliderPath(slider1.Path.ControlPoints.Select(p => new PathControlPoint(p.Position, p.Type)).ToArray(), slider1.Path.ExpectedDistance.Value);
|
||||
slider2 = (Slider)EditorBeatmap.HitObjects.First(h => h is Slider && h.StartTime > slider1.StartTime);
|
||||
EditorClock.Seek(slider1.StartTime);
|
||||
EditorBeatmap.SelectedHitObjects.Add(slider1);
|
||||
EditorBeatmap.SelectedHitObjects.Add(slider2);
|
||||
});
|
||||
|
||||
mergeSelection();
|
||||
|
||||
AddAssert("slider created", () =>
|
||||
{
|
||||
if (slider1 is null || slider2 is null || slider1Path is null)
|
||||
return false;
|
||||
|
||||
var controlPoints1 = slider1Path.ControlPoints;
|
||||
var controlPoints2 = slider2.Path.ControlPoints;
|
||||
(Vector2, PathType?)[] args = new (Vector2, PathType?)[controlPoints1.Count + controlPoints2.Count - 1];
|
||||
|
||||
for (int i = 0; i < controlPoints1.Count - 1; i++)
|
||||
{
|
||||
args[i] = (controlPoints1[i].Position + slider1.Position, controlPoints1[i].Type);
|
||||
}
|
||||
|
||||
for (int i = 0; i < controlPoints2.Count; i++)
|
||||
{
|
||||
args[i + controlPoints1.Count - 1] = (controlPoints2[i].Position + controlPoints1[^1].Position + slider1.Position, controlPoints2[i].Type);
|
||||
}
|
||||
|
||||
return sliderCreatedFor(args);
|
||||
});
|
||||
|
||||
AddAssert("merged slider matches first slider", () =>
|
||||
{
|
||||
var mergedSlider = (Slider)EditorBeatmap.SelectedHitObjects.First();
|
||||
return slider1 is not null && mergedSlider.HeadCircle.Samples.SequenceEqual(slider1.HeadCircle.Samples)
|
||||
&& mergedSlider.TailCircle.Samples.SequenceEqual(slider1.TailCircle.Samples)
|
||||
&& mergedSlider.Samples.SequenceEqual(slider1.Samples)
|
||||
&& mergedSlider.SampleControlPoint.IsRedundant(slider1.SampleControlPoint);
|
||||
});
|
||||
|
||||
AddAssert("slider end is at same completion for last slider", () =>
|
||||
{
|
||||
if (slider1Path is null || slider2 is null)
|
||||
return false;
|
||||
|
||||
var mergedSlider = (Slider)EditorBeatmap.SelectedHitObjects.First();
|
||||
return Precision.AlmostEquals(mergedSlider.Path.Distance, slider1Path.CalculatedDistance + slider2.Path.Distance);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNonMerge()
|
||||
{
|
||||
HitCircle? circle1 = null;
|
||||
HitCircle? circle2 = null;
|
||||
Spinner? spinner = null;
|
||||
|
||||
AddStep("select first two circles and spinner", () =>
|
||||
{
|
||||
circle1 = (HitCircle)EditorBeatmap.HitObjects.First(h => h is HitCircle);
|
||||
circle2 = (HitCircle)EditorBeatmap.HitObjects.First(h => h is HitCircle && h != circle1);
|
||||
spinner = (Spinner)EditorBeatmap.HitObjects.First(h => h is Spinner);
|
||||
EditorClock.Seek(spinner.StartTime);
|
||||
EditorBeatmap.SelectedHitObjects.Add(circle1);
|
||||
EditorBeatmap.SelectedHitObjects.Add(circle2);
|
||||
EditorBeatmap.SelectedHitObjects.Add(spinner);
|
||||
});
|
||||
|
||||
mergeSelection();
|
||||
|
||||
AddAssert("slider created", () => circle1 is not null && circle2 is not null && sliderCreatedFor(
|
||||
(pos: circle1.Position, pathType: PathType.Linear),
|
||||
(pos: circle2.Position, pathType: null)));
|
||||
|
||||
AddAssert("spinner not merged", () => EditorBeatmap.HitObjects.Contains(spinner));
|
||||
}
|
||||
|
||||
private void mergeSelection()
|
||||
{
|
||||
AddStep("merge selection", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.LControl);
|
||||
InputManager.PressKey(Key.LShift);
|
||||
InputManager.Key(Key.M);
|
||||
InputManager.ReleaseKey(Key.LShift);
|
||||
InputManager.ReleaseKey(Key.LControl);
|
||||
});
|
||||
}
|
||||
|
||||
private bool sliderCreatedFor(params (Vector2 pos, PathType? pathType)[] expectedControlPoints)
|
||||
{
|
||||
if (EditorBeatmap.SelectedHitObjects.Count != 1)
|
||||
return false;
|
||||
|
||||
var mergedSlider = (Slider)EditorBeatmap.SelectedHitObjects.First();
|
||||
int i = 0;
|
||||
|
||||
foreach ((Vector2 pos, PathType? pathType) in expectedControlPoints)
|
||||
{
|
||||
var controlPoint = mergedSlider.Path.ControlPoints[i++];
|
||||
|
||||
if (!Precision.AlmostEquals(controlPoint.Position + mergedSlider.Position, pos) || controlPoint.Type != pathType)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool objectsRestored(params HitObject[] objects)
|
||||
{
|
||||
foreach (var hitObject in objects)
|
||||
{
|
||||
if (EditorBeatmap.HitObjects.Contains(hitObject))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@ -6,7 +6,8 @@ using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics.OpenGL.Textures;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics.Rendering;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Testing;
|
||||
@ -19,6 +20,9 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
[HeadlessTest]
|
||||
public class LegacyMainCirclePieceTest : OsuTestScene
|
||||
{
|
||||
[Resolved]
|
||||
private IRenderer renderer { get; set; } = null!;
|
||||
|
||||
private static readonly object?[][] texture_priority_cases =
|
||||
{
|
||||
// default priority lookup
|
||||
@ -76,7 +80,12 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
skin.Setup(s => s.GetTexture(It.IsAny<string>())).CallBase();
|
||||
|
||||
skin.Setup(s => s.GetTexture(It.IsIn(textureFilenames), It.IsAny<WrapMode>(), It.IsAny<WrapMode>()))
|
||||
.Returns((string componentName, WrapMode _, WrapMode _) => new Texture(1, 1) { AssetName = componentName });
|
||||
.Returns((string componentName, WrapMode _, WrapMode _) =>
|
||||
{
|
||||
var tex = renderer.CreateTexture(1, 1);
|
||||
tex.AssetName = componentName;
|
||||
return tex;
|
||||
});
|
||||
|
||||
Child = new DependencyProvidingContainer
|
||||
{
|
||||
@ -84,7 +93,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
Child = piece = new TestLegacyMainCirclePiece(priorityLookup),
|
||||
};
|
||||
|
||||
var sprites = this.ChildrenOfType<Sprite>().Where(s => s.Texture.AssetName != null).DistinctBy(s => s.Texture.AssetName).ToArray();
|
||||
var sprites = this.ChildrenOfType<Sprite>().Where(s => !string.IsNullOrEmpty(s.Texture.AssetName)).DistinctBy(s => s.Texture.AssetName).ToArray();
|
||||
Debug.Assert(sprites.Length <= 2);
|
||||
});
|
||||
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
|
@ -1,10 +1,9 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
@ -35,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
|
||||
private void runSpmTest(Mod mod)
|
||||
{
|
||||
SpinnerSpmCalculator spmCalculator = null;
|
||||
SpinnerSpmCalculator? spmCalculator = null;
|
||||
|
||||
CreateModTest(new ModTestData
|
||||
{
|
||||
@ -61,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
return spmCalculator != null;
|
||||
});
|
||||
|
||||
AddUntilStep("SPM is correct", () => Precision.AlmostEquals(spmCalculator.Result.Value, 477, 5));
|
||||
AddUntilStep("SPM is correct", () => Precision.AlmostEquals(spmCalculator.AsNonNull().Result.Value, 477, 5));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
@ -24,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
{
|
||||
Mod = mod,
|
||||
PassCondition = () => Player.ScoreProcessor.JudgedHits >= 2 &&
|
||||
Precision.AlmostEquals(Player.GameplayClockContainer.GameplayClock.Rate, mod.SpeedChange.Value)
|
||||
Precision.AlmostEquals(Player.GameplayClockContainer.Rate, mod.SpeedChange.Value)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
@ -162,7 +160,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
|
||||
private class TestOsuModHidden : OsuModHidden
|
||||
{
|
||||
public new HitObject FirstObject => base.FirstObject;
|
||||
public new HitObject? FirstObject => base.FirstObject;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
@ -33,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
[Test]
|
||||
public void TestModCopy()
|
||||
{
|
||||
OsuModMuted muted = null;
|
||||
OsuModMuted muted = null!;
|
||||
|
||||
AddStep("create inversed mod", () => muted = new OsuModMuted
|
||||
{
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@ -30,8 +28,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
[Test]
|
||||
public void TestSpinnerAutoCompleted()
|
||||
{
|
||||
DrawableSpinner spinner = null;
|
||||
JudgementResult lastResult = null;
|
||||
DrawableSpinner? spinner = null;
|
||||
JudgementResult? lastResult = null;
|
||||
|
||||
CreateModTest(new ModTestData
|
||||
{
|
||||
@ -63,11 +61,11 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
[TestCase(null)]
|
||||
[TestCase(typeof(OsuModDoubleTime))]
|
||||
[TestCase(typeof(OsuModHalfTime))]
|
||||
public void TestSpinRateUnaffectedByMods(Type additionalModType)
|
||||
public void TestSpinRateUnaffectedByMods(Type? additionalModType)
|
||||
{
|
||||
var mods = new List<Mod> { new OsuModSpunOut() };
|
||||
if (additionalModType != null)
|
||||
mods.Add((Mod)Activator.CreateInstance(additionalModType));
|
||||
mods.Add((Mod)Activator.CreateInstance(additionalModType)!);
|
||||
|
||||
CreateModTest(new ModTestData
|
||||
{
|
||||
@ -96,7 +94,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
[Test]
|
||||
public void TestSpinnerGetsNoBonusScore()
|
||||
{
|
||||
DrawableSpinner spinner = null;
|
||||
DrawableSpinner? spinner = null;
|
||||
List<JudgementResult> results = new List<JudgementResult>();
|
||||
|
||||
CreateModTest(new ModTestData
|
||||
|
@ -17,18 +17,18 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
|
||||
|
||||
[TestCase(6.6369583000323935d, 206, "diffcalc-test")]
|
||||
[TestCase(1.4476531024675374d, 45, "zero-length-sliders")]
|
||||
[TestCase(6.7115569159190587d, 206, "diffcalc-test")]
|
||||
[TestCase(1.4391311903612753d, 45, "zero-length-sliders")]
|
||||
public void Test(double expectedStarRating, int expectedMaxCombo, string name)
|
||||
=> base.Test(expectedStarRating, expectedMaxCombo, name);
|
||||
|
||||
[TestCase(8.8816128335486386d, 206, "diffcalc-test")]
|
||||
[TestCase(1.7540389962596916d, 45, "zero-length-sliders")]
|
||||
[TestCase(8.9757300665532966d, 206, "diffcalc-test")]
|
||||
[TestCase(1.7437232654020756d, 45, "zero-length-sliders")]
|
||||
public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxCombo, string name)
|
||||
=> Test(expectedStarRating, expectedMaxCombo, name, new OsuModDoubleTime());
|
||||
|
||||
[TestCase(6.6369583000323935d, 239, "diffcalc-test")]
|
||||
[TestCase(1.4476531024675374d, 54, "zero-length-sliders")]
|
||||
[TestCase(6.7115569159190587d, 239, "diffcalc-test")]
|
||||
[TestCase(1.4391311903612753d, 54, "zero-length-sliders")]
|
||||
public void TestClassicMod(double expectedStarRating, int expectedMaxCombo, string name)
|
||||
=> Test(expectedStarRating, expectedMaxCombo, name, new OsuModClassic());
|
||||
|
||||
|
@ -11,7 +11,7 @@ using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.OpenGL.Textures;
|
||||
using osu.Framework.Graphics.Rendering;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Testing.Input;
|
||||
using osu.Game.Audio;
|
||||
@ -25,6 +25,9 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
public class TestSceneCursorTrail : OsuTestScene
|
||||
{
|
||||
[Resolved]
|
||||
private IRenderer renderer { get; set; }
|
||||
|
||||
[Test]
|
||||
public void TestSmoothCursorTrail()
|
||||
{
|
||||
@ -44,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
createTest(() =>
|
||||
{
|
||||
var skinContainer = new LegacySkinContainer(false);
|
||||
var skinContainer = new LegacySkinContainer(renderer, false);
|
||||
var legacyCursorTrail = new LegacyCursorTrail(skinContainer);
|
||||
|
||||
skinContainer.Child = legacyCursorTrail;
|
||||
@ -58,7 +61,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
createTest(() =>
|
||||
{
|
||||
var skinContainer = new LegacySkinContainer(true);
|
||||
var skinContainer = new LegacySkinContainer(renderer, true);
|
||||
var legacyCursorTrail = new LegacyCursorTrail(skinContainer);
|
||||
|
||||
skinContainer.Child = legacyCursorTrail;
|
||||
@ -82,10 +85,12 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
[Cached(typeof(ISkinSource))]
|
||||
private class LegacySkinContainer : Container, ISkinSource
|
||||
{
|
||||
private readonly IRenderer renderer;
|
||||
private readonly bool disjoint;
|
||||
|
||||
public LegacySkinContainer(bool disjoint)
|
||||
public LegacySkinContainer(IRenderer renderer, bool disjoint)
|
||||
{
|
||||
this.renderer = renderer;
|
||||
this.disjoint = disjoint;
|
||||
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
@ -98,14 +103,14 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
switch (componentName)
|
||||
{
|
||||
case "cursortrail":
|
||||
var tex = new Texture(Texture.WhitePixel.TextureGL);
|
||||
var tex = new Texture(renderer.WhitePixel);
|
||||
|
||||
if (disjoint)
|
||||
tex.ScaleAdjust = 1 / 25f;
|
||||
return tex;
|
||||
|
||||
case "cursormiddle":
|
||||
return disjoint ? null : Texture.WhitePixel;
|
||||
return disjoint ? null : renderer.WhitePixel;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@ -10,7 +10,6 @@ using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.OpenGL.Textures;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Input;
|
||||
|
@ -12,7 +12,6 @@ using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.OpenGL.Textures;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Timing;
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@ -12,7 +10,6 @@ using osu.Framework.Audio;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Replays;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
@ -36,16 +33,16 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
private const double spinner_duration = 6000;
|
||||
|
||||
[Resolved]
|
||||
private AudioManager audioManager { get; set; }
|
||||
private AudioManager audioManager { get; set; } = null!;
|
||||
|
||||
protected override bool Autoplay => true;
|
||||
|
||||
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new ScoreExposedPlayer();
|
||||
|
||||
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
|
||||
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard = null)
|
||||
=> new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
|
||||
|
||||
private DrawableSpinner drawableSpinner;
|
||||
private DrawableSpinner drawableSpinner = null!;
|
||||
private SpriteIcon spinnerSymbol => drawableSpinner.ChildrenOfType<SpriteIcon>().Single();
|
||||
|
||||
[SetUpSteps]
|
||||
@ -67,12 +64,12 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
trackerRotationTolerance = Math.Abs(drawableSpinner.RotationTracker.Rotation * 0.1f);
|
||||
});
|
||||
AddAssert("is disc rotation not almost 0", () => !Precision.AlmostEquals(drawableSpinner.RotationTracker.Rotation, 0, 100));
|
||||
AddAssert("is disc rotation absolute not almost 0", () => !Precision.AlmostEquals(drawableSpinner.Result.RateAdjustedRotation, 0, 100));
|
||||
AddAssert("is disc rotation not almost 0", () => drawableSpinner.RotationTracker.Rotation, () => Is.Not.EqualTo(0).Within(100));
|
||||
AddAssert("is disc rotation absolute not almost 0", () => drawableSpinner.Result.RateAdjustedRotation, () => Is.Not.EqualTo(0).Within(100));
|
||||
|
||||
addSeekStep(0);
|
||||
AddAssert("is disc rotation almost 0", () => Precision.AlmostEquals(drawableSpinner.RotationTracker.Rotation, 0, trackerRotationTolerance));
|
||||
AddAssert("is disc rotation absolute almost 0", () => Precision.AlmostEquals(drawableSpinner.Result.RateAdjustedRotation, 0, 100));
|
||||
AddAssert("is disc rotation almost 0", () => drawableSpinner.RotationTracker.Rotation, () => Is.EqualTo(0).Within(trackerRotationTolerance));
|
||||
AddAssert("is disc rotation absolute almost 0", () => drawableSpinner.Result.RateAdjustedRotation, () => Is.EqualTo(0).Within(100));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -100,20 +97,20 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
// we want to make sure that the rotation at time 2500 is in the same direction as at time 5000, but about half-way in.
|
||||
// due to the exponential damping applied we're allowing a larger margin of error of about 10%
|
||||
// (5% relative to the final rotation value, but we're half-way through the spin).
|
||||
() => Precision.AlmostEquals(drawableSpinner.RotationTracker.Rotation, finalTrackerRotation / 2, trackerRotationTolerance));
|
||||
() => drawableSpinner.RotationTracker.Rotation, () => Is.EqualTo(finalTrackerRotation / 2).Within(trackerRotationTolerance));
|
||||
AddAssert("symbol rotation rewound",
|
||||
() => Precision.AlmostEquals(spinnerSymbol.Rotation, finalSpinnerSymbolRotation / 2, spinnerSymbolRotationTolerance));
|
||||
() => spinnerSymbol.Rotation, () => Is.EqualTo(finalSpinnerSymbolRotation / 2).Within(spinnerSymbolRotationTolerance));
|
||||
AddAssert("is cumulative rotation rewound",
|
||||
// cumulative rotation is not damped, so we're treating it as the "ground truth" and allowing a comparatively smaller margin of error.
|
||||
() => Precision.AlmostEquals(drawableSpinner.Result.RateAdjustedRotation, finalCumulativeTrackerRotation / 2, 100));
|
||||
() => drawableSpinner.Result.RateAdjustedRotation, () => Is.EqualTo(finalCumulativeTrackerRotation / 2).Within(100));
|
||||
|
||||
addSeekStep(spinner_start_time + 5000);
|
||||
AddAssert("is disc rotation almost same",
|
||||
() => Precision.AlmostEquals(drawableSpinner.RotationTracker.Rotation, finalTrackerRotation, trackerRotationTolerance));
|
||||
() => drawableSpinner.RotationTracker.Rotation, () => Is.EqualTo(finalTrackerRotation).Within(trackerRotationTolerance));
|
||||
AddAssert("is symbol rotation almost same",
|
||||
() => Precision.AlmostEquals(spinnerSymbol.Rotation, finalSpinnerSymbolRotation, spinnerSymbolRotationTolerance));
|
||||
() => spinnerSymbol.Rotation, () => Is.EqualTo(finalSpinnerSymbolRotation).Within(spinnerSymbolRotationTolerance));
|
||||
AddAssert("is cumulative rotation almost same",
|
||||
() => Precision.AlmostEquals(drawableSpinner.Result.RateAdjustedRotation, finalCumulativeTrackerRotation, 100));
|
||||
() => drawableSpinner.Result.RateAdjustedRotation, () => Is.EqualTo(finalCumulativeTrackerRotation).Within(100));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -177,10 +174,10 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
AddStep("retrieve spm", () => estimatedSpm = drawableSpinner.SpinsPerMinute.Value);
|
||||
|
||||
addSeekStep(2000);
|
||||
AddAssert("spm still valid", () => Precision.AlmostEquals(drawableSpinner.SpinsPerMinute.Value, estimatedSpm, 1.0));
|
||||
AddAssert("spm still valid", () => drawableSpinner.SpinsPerMinute.Value, () => Is.EqualTo(estimatedSpm).Within(1.0));
|
||||
|
||||
addSeekStep(1000);
|
||||
AddAssert("spm still valid", () => Precision.AlmostEquals(drawableSpinner.SpinsPerMinute.Value, estimatedSpm, 1.0));
|
||||
AddAssert("spm still valid", () => drawableSpinner.SpinsPerMinute.Value, () => Is.EqualTo(estimatedSpm).Within(1.0));
|
||||
}
|
||||
|
||||
[TestCase(0.5)]
|
||||
@ -202,14 +199,14 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
AddStep("adjust track rate", () => ((MasterGameplayClockContainer)Player.GameplayClockContainer).UserPlaybackRate.Value = rate);
|
||||
|
||||
addSeekStep(1000);
|
||||
AddAssert("progress almost same", () => Precision.AlmostEquals(expectedProgress, drawableSpinner.Progress, 0.05));
|
||||
AddAssert("spm almost same", () => Precision.AlmostEquals(expectedSpm, drawableSpinner.SpinsPerMinute.Value, 2.0));
|
||||
AddAssert("progress almost same", () => drawableSpinner.Progress, () => Is.EqualTo(expectedProgress).Within(0.05));
|
||||
AddAssert("spm almost same", () => drawableSpinner.SpinsPerMinute.Value, () => Is.EqualTo(expectedSpm).Within(2.0));
|
||||
}
|
||||
|
||||
private void addSeekStep(double time)
|
||||
{
|
||||
AddStep($"seek to {time}", () => Player.GameplayClockContainer.Seek(time));
|
||||
AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100));
|
||||
AddUntilStep("wait for seek to finish", () => Player.DrawableRuleset.FrameStableClock.CurrentTime, () => Is.EqualTo(time).Within(100));
|
||||
}
|
||||
|
||||
private void transformReplay(Func<Replay, Replay> replayTransformation) => AddStep("set replay", () =>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<Import Project="..\osu.TestProject.props" />
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||
<PackageReference Include="Moq" Version="4.18.1" />
|
||||
<PackageReference Include="Moq" Version="4.18.2" />
|
||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||
|
@ -13,8 +13,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
||||
public static class AimEvaluator
|
||||
{
|
||||
private const double wide_angle_multiplier = 1.5;
|
||||
private const double acute_angle_multiplier = 2.0;
|
||||
private const double slider_multiplier = 1.5;
|
||||
private const double acute_angle_multiplier = 1.95;
|
||||
private const double slider_multiplier = 1.35;
|
||||
private const double velocity_change_multiplier = 0.75;
|
||||
|
||||
/// <summary>
|
||||
@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
||||
velocityChangeBonus *= Math.Pow(Math.Min(osuCurrObj.StrainTime, osuLastObj.StrainTime) / Math.Max(osuCurrObj.StrainTime, osuLastObj.StrainTime), 2);
|
||||
}
|
||||
|
||||
if (osuLastObj.TravelTime != 0)
|
||||
if (osuLastObj.BaseObject is Slider)
|
||||
{
|
||||
// Reward sliders based on velocity.
|
||||
sliderBonus = osuLastObj.TravelDistance / osuLastObj.TravelTime;
|
||||
|
@ -15,11 +15,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
||||
private const double max_opacity_bonus = 0.4;
|
||||
private const double hidden_bonus = 0.2;
|
||||
|
||||
private const double min_velocity = 0.5;
|
||||
private const double slider_multiplier = 1.3;
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates the difficulty of memorising and hitting an object, based on:
|
||||
/// <list type="bullet">
|
||||
/// <item><description>distance between the previous and current object,</description></item>
|
||||
/// <item><description>distance between a number of previous objects and the current object,</description></item>
|
||||
/// <item><description>the visual opacity of the current object,</description></item>
|
||||
/// <item><description>length and speed of the current object (for sliders),</description></item>
|
||||
/// <item><description>and whether the hidden mod is enabled.</description></item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
@ -73,6 +77,26 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
||||
if (hidden)
|
||||
result *= 1.0 + hidden_bonus;
|
||||
|
||||
double sliderBonus = 0.0;
|
||||
|
||||
if (osuCurrent.BaseObject is Slider osuSlider)
|
||||
{
|
||||
// Invert the scaling factor to determine the true travel distance independent of circle size.
|
||||
double pixelTravelDistance = osuSlider.LazyTravelDistance / scalingFactor;
|
||||
|
||||
// Reward sliders based on velocity.
|
||||
sliderBonus = Math.Pow(Math.Max(0.0, pixelTravelDistance / osuCurrent.TravelTime - min_velocity), 0.5);
|
||||
|
||||
// Longer sliders require more memorisation.
|
||||
sliderBonus *= pixelTravelDistance;
|
||||
|
||||
// Nerf sliders with repeats, as less memorisation is required.
|
||||
if (osuSlider.RepeatCount > 0)
|
||||
sliderBonus /= (osuSlider.RepeatCount + 1);
|
||||
}
|
||||
|
||||
result += sliderBonus * slider_multiplier;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@ -46,7 +46,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1;
|
||||
|
||||
if (mods.Any(h => h is OsuModRelax))
|
||||
{
|
||||
aimRating *= 0.9;
|
||||
speedRating = 0.0;
|
||||
flashlightRating *= 0.7;
|
||||
}
|
||||
|
||||
double baseAimPerformance = Math.Pow(5 * Math.Max(1, aimRating / 0.0675) - 4, 3) / 100000;
|
||||
double baseSpeedPerformance = Math.Pow(5 * Math.Max(1, speedRating / 0.0675) - 4, 3) / 100000;
|
||||
@ -62,7 +66,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
Math.Pow(baseFlashlightPerformance, 1.1), 1.0 / 1.1
|
||||
);
|
||||
|
||||
double starRating = basePerformance > 0.00001 ? Math.Cbrt(1.12) * 0.027 * (Math.Cbrt(100000 / Math.Pow(2, 1 / 1.1) * basePerformance) + 4) : 0;
|
||||
double starRating = basePerformance > 0.00001 ? Math.Cbrt(OsuPerformanceCalculator.PERFORMANCE_BASE_MULTIPLIER) * 0.027 * (Math.Cbrt(100000 / Math.Pow(2, 1 / 1.1) * basePerformance) + 4) : 0;
|
||||
|
||||
double preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450) / clockRate;
|
||||
double drainRate = beatmap.Difficulty.DrainRate;
|
||||
|
@ -15,6 +15,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
{
|
||||
public class OsuPerformanceCalculator : PerformanceCalculator
|
||||
{
|
||||
public const double PERFORMANCE_BASE_MULTIPLIER = 1.14; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things.
|
||||
|
||||
private double accuracy;
|
||||
private int scoreMaxCombo;
|
||||
private int countGreat;
|
||||
@ -41,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss);
|
||||
effectiveMissCount = calculateEffectiveMissCount(osuAttributes);
|
||||
|
||||
double multiplier = 1.12; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things.
|
||||
double multiplier = PERFORMANCE_BASE_MULTIPLIER;
|
||||
|
||||
if (score.Mods.Any(m => m is OsuModNoFail))
|
||||
multiplier *= Math.Max(0.90, 1.0 - 0.02 * effectiveMissCount);
|
||||
@ -51,10 +53,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
|
||||
if (score.Mods.Any(h => h is OsuModRelax))
|
||||
{
|
||||
// As we're adding Oks and Mehs to an approximated number of combo breaks the result can be higher than total hits in specific scenarios (which breaks some calculations) so we need to clamp it.
|
||||
effectiveMissCount = Math.Min(effectiveMissCount + countOk + countMeh, totalHits);
|
||||
// https://www.desmos.com/calculator/bc9eybdthb
|
||||
// we use OD13.3 as maximum since it's the value at which great hitwidow becomes 0
|
||||
// this is well beyond currently maximum achievable OD which is 12.17 (DTx2 + DA with OD11)
|
||||
double okMultiplier = Math.Max(0.0, osuAttributes.OverallDifficulty > 0.0 ? 1 - Math.Pow(osuAttributes.OverallDifficulty / 13.33, 1.8) : 1.0);
|
||||
double mehMultiplier = Math.Max(0.0, osuAttributes.OverallDifficulty > 0.0 ? 1 - Math.Pow(osuAttributes.OverallDifficulty / 13.33, 5) : 1.0);
|
||||
|
||||
multiplier *= 0.6;
|
||||
// As we're adding Oks and Mehs to an approximated number of combo breaks the result can be higher than total hits in specific scenarios (which breaks some calculations) so we need to clamp it.
|
||||
effectiveMissCount = Math.Min(effectiveMissCount + countOk * okMultiplier + countMeh * mehMultiplier, totalHits);
|
||||
}
|
||||
|
||||
double aimValue = computeAimValue(score, osuAttributes);
|
||||
@ -103,7 +109,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
if (attributes.ApproachRate > 10.33)
|
||||
approachRateFactor = 0.3 * (attributes.ApproachRate - 10.33);
|
||||
else if (attributes.ApproachRate < 8.0)
|
||||
approachRateFactor = 0.1 * (8.0 - attributes.ApproachRate);
|
||||
approachRateFactor = 0.05 * (8.0 - attributes.ApproachRate);
|
||||
|
||||
if (score.Mods.Any(h => h is OsuModRelax))
|
||||
approachRateFactor = 0.0;
|
||||
|
||||
aimValue *= 1.0 + approachRateFactor * lengthBonus; // Buff for longer maps with high AR.
|
||||
|
||||
@ -134,6 +143,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
|
||||
private double computeSpeedValue(ScoreInfo score, OsuDifficultyAttributes attributes)
|
||||
{
|
||||
if (score.Mods.Any(h => h is OsuModRelax))
|
||||
return 0.0;
|
||||
|
||||
double speedValue = Math.Pow(5.0 * Math.Max(1.0, attributes.SpeedDifficulty / 0.0675) - 4.0, 3.0) / 100000.0;
|
||||
|
||||
double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) +
|
||||
@ -174,7 +186,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
speedValue *= (0.95 + Math.Pow(attributes.OverallDifficulty, 2) / 750) * Math.Pow((accuracy + relevantAccuracy) / 2.0, (14.5 - Math.Max(attributes.OverallDifficulty, 8)) / 2);
|
||||
|
||||
// Scale the speed value with # of 50s to punish doubletapping.
|
||||
speedValue *= Math.Pow(0.98, countMeh < totalHits / 500.0 ? 0 : countMeh - totalHits / 500.0);
|
||||
speedValue *= Math.Pow(0.99, countMeh < totalHits / 500.0 ? 0 : countMeh - totalHits / 500.0);
|
||||
|
||||
return speedValue;
|
||||
}
|
||||
@ -258,14 +270,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
comboBasedMissCount = fullComboThreshold / Math.Max(1.0, scoreMaxCombo);
|
||||
}
|
||||
|
||||
// Clamp miss count since it's derived from combo and can be higher than total hits and that breaks some calculations
|
||||
comboBasedMissCount = Math.Min(comboBasedMissCount, totalHits);
|
||||
// Clamp miss count to maximum amount of possible breaks
|
||||
comboBasedMissCount = Math.Min(comboBasedMissCount, countOk + countMeh + countMiss);
|
||||
|
||||
return Math.Max(countMiss, comboBasedMissCount);
|
||||
}
|
||||
|
||||
private double getComboScalingFactor(OsuDifficultyAttributes attributes) => attributes.MaxCombo <= 0 ? 1.0 : Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(attributes.MaxCombo, 0.8), 1.0);
|
||||
private int totalHits => countGreat + countOk + countMeh + countMiss;
|
||||
private int totalSuccessfulHits => countGreat + countOk + countMeh;
|
||||
}
|
||||
}
|
||||
|
@ -15,10 +15,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
{
|
||||
public class OsuDifficultyHitObject : DifficultyHitObject
|
||||
{
|
||||
private const int normalised_radius = 50; // Change radius to 50 to make 100 the diameter. Easier for mental maths.
|
||||
/// <summary>
|
||||
/// A distance by which all distances should be scaled in order to assume a uniform circle size.
|
||||
/// </summary>
|
||||
public const int NORMALISED_RADIUS = 50; // Change radius to 50 to make 100 the diameter. Easier for mental maths.
|
||||
|
||||
private const int min_delta_time = 25;
|
||||
private const float maximum_slider_radius = normalised_radius * 2.4f;
|
||||
private const float assumed_slider_radius = normalised_radius * 1.8f;
|
||||
private const float maximum_slider_radius = NORMALISED_RADIUS * 2.4f;
|
||||
private const float assumed_slider_radius = NORMALISED_RADIUS * 1.8f;
|
||||
|
||||
protected new OsuHitObject BaseObject => (OsuHitObject)base.BaseObject;
|
||||
|
||||
@ -64,7 +68,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
public double TravelDistance { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The time taken to travel through <see cref="TravelDistance"/>, with a minimum value of 25ms for a non-zero distance.
|
||||
/// The time taken to travel through <see cref="TravelDistance"/>, with a minimum value of 25ms for <see cref="Slider"/> objects.
|
||||
/// </summary>
|
||||
public double TravelTime { get; private set; }
|
||||
|
||||
@ -123,7 +127,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
if (BaseObject is Slider currentSlider)
|
||||
{
|
||||
computeSliderCursorPosition(currentSlider);
|
||||
TravelDistance = currentSlider.LazyTravelDistance;
|
||||
// Bonus for repeat sliders until a better per nested object strain system can be achieved.
|
||||
TravelDistance = currentSlider.LazyTravelDistance * (float)Math.Pow(1 + currentSlider.RepeatCount / 2.5, 1.0 / 2.5);
|
||||
TravelTime = Math.Max(currentSlider.LazyTravelTime / clockRate, min_delta_time);
|
||||
}
|
||||
|
||||
@ -132,7 +137,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
return;
|
||||
|
||||
// We will scale distances by this factor, so we can assume a uniform CircleSize among beatmaps.
|
||||
float scalingFactor = normalised_radius / (float)BaseObject.Radius;
|
||||
float scalingFactor = NORMALISED_RADIUS / (float)BaseObject.Radius;
|
||||
|
||||
if (BaseObject.Radius < 30)
|
||||
{
|
||||
@ -206,7 +211,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
|
||||
slider.LazyEndPosition = slider.StackedPosition + slider.Path.PositionAt(endTimeMin); // temporary lazy end position until a real result can be derived.
|
||||
var currCursorPosition = slider.StackedPosition;
|
||||
double scalingFactor = normalised_radius / slider.Radius; // lazySliderDistance is coded to be sensitive to scaling, this makes the maths easier with the thresholds being used.
|
||||
double scalingFactor = NORMALISED_RADIUS / slider.Radius; // lazySliderDistance is coded to be sensitive to scaling, this makes the maths easier with the thresholds being used.
|
||||
|
||||
for (int i = 1; i < slider.NestedHitObjects.Count; i++)
|
||||
{
|
||||
@ -234,7 +239,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
else if (currMovementObj is SliderRepeat)
|
||||
{
|
||||
// For a slider repeat, assume a tighter movement threshold to better assess repeat sliders.
|
||||
requiredMovement = normalised_radius;
|
||||
requiredMovement = NORMALISED_RADIUS;
|
||||
}
|
||||
|
||||
if (currMovementLength > requiredMovement)
|
||||
@ -248,8 +253,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
if (i == slider.NestedHitObjects.Count - 1)
|
||||
slider.LazyEndPosition = currCursorPosition;
|
||||
}
|
||||
|
||||
slider.LazyTravelDistance *= (float)Math.Pow(1 + slider.RepeatCount / 2.5, 1.0 / 2.5); // Bonus for repeat sliders until a better per nested object strain system can be achieved.
|
||||
}
|
||||
|
||||
private Vector2 getEndCursorPosition(OsuHitObject hitObject)
|
||||
|
@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
|
||||
private double currentStrain;
|
||||
|
||||
private double skillMultiplier => 23.25;
|
||||
private double skillMultiplier => 23.55;
|
||||
private double strainDecayBase => 0.15;
|
||||
|
||||
private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000);
|
||||
|
@ -251,13 +251,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
|
||||
private void convertToStream()
|
||||
{
|
||||
if (editorBeatmap == null || changeHandler == null || beatDivisor == null)
|
||||
if (editorBeatmap == null || beatDivisor == null)
|
||||
return;
|
||||
|
||||
var timingPoint = editorBeatmap.ControlPointInfo.TimingPointAt(HitObject.StartTime);
|
||||
double streamSpacing = timingPoint.BeatLength / beatDivisor.Value;
|
||||
|
||||
changeHandler.BeginChange();
|
||||
changeHandler?.BeginChange();
|
||||
|
||||
int i = 0;
|
||||
double time = HitObject.StartTime;
|
||||
@ -292,7 +292,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
|
||||
editorBeatmap.Remove(HitObject);
|
||||
|
||||
changeHandler.EndChange();
|
||||
changeHandler?.EndChange();
|
||||
}
|
||||
|
||||
public override MenuItem[] ContextMenuItems => new MenuItem[]
|
||||
|
@ -6,8 +6,11 @@ using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
@ -15,6 +18,7 @@ using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Game.Screens.Edit.Compose.Components;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
@ -53,6 +57,17 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
referencePathTypes = null;
|
||||
}
|
||||
|
||||
protected override bool OnKeyDown(KeyDownEvent e)
|
||||
{
|
||||
if (e.Key == Key.M && e.ControlPressed && e.ShiftPressed)
|
||||
{
|
||||
mergeSelection();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool HandleMovement(MoveSelectionEvent<HitObject> moveEvent)
|
||||
{
|
||||
var hitObjects = selectedMovableObjects;
|
||||
@ -320,7 +335,109 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
/// All osu! hitobjects which can be moved/rotated/scaled.
|
||||
/// </summary>
|
||||
private OsuHitObject[] selectedMovableObjects => SelectedItems.OfType<OsuHitObject>()
|
||||
.Where(h => !(h is Spinner))
|
||||
.Where(h => h is not Spinner)
|
||||
.ToArray();
|
||||
|
||||
/// <summary>
|
||||
/// All osu! hitobjects which can be merged.
|
||||
/// </summary>
|
||||
private OsuHitObject[] selectedMergeableObjects => SelectedItems.OfType<OsuHitObject>()
|
||||
.Where(h => h is HitCircle or Slider)
|
||||
.OrderBy(h => h.StartTime)
|
||||
.ToArray();
|
||||
|
||||
private void mergeSelection()
|
||||
{
|
||||
var mergeableObjects = selectedMergeableObjects;
|
||||
|
||||
if (mergeableObjects.Length < 2)
|
||||
return;
|
||||
|
||||
ChangeHandler?.BeginChange();
|
||||
|
||||
// Have an initial slider object.
|
||||
var firstHitObject = mergeableObjects[0];
|
||||
var mergedHitObject = firstHitObject as Slider ?? new Slider
|
||||
{
|
||||
StartTime = firstHitObject.StartTime,
|
||||
Position = firstHitObject.Position,
|
||||
NewCombo = firstHitObject.NewCombo,
|
||||
SampleControlPoint = firstHitObject.SampleControlPoint,
|
||||
};
|
||||
|
||||
if (mergedHitObject.Path.ControlPoints.Count == 0)
|
||||
{
|
||||
mergedHitObject.Path.ControlPoints.Add(new PathControlPoint(Vector2.Zero, PathType.Linear));
|
||||
}
|
||||
|
||||
// Merge all the selected hit objects into one slider path.
|
||||
bool lastCircle = firstHitObject is HitCircle;
|
||||
|
||||
foreach (var selectedMergeableObject in mergeableObjects.Skip(1))
|
||||
{
|
||||
if (selectedMergeableObject is IHasPath hasPath)
|
||||
{
|
||||
var offset = lastCircle ? selectedMergeableObject.Position - mergedHitObject.Position : mergedHitObject.Path.ControlPoints[^1].Position;
|
||||
float distanceToLastControlPoint = Vector2.Distance(mergedHitObject.Path.ControlPoints[^1].Position, offset);
|
||||
|
||||
// Calculate the distance required to travel to the expected distance of the merging slider.
|
||||
mergedHitObject.Path.ExpectedDistance.Value = mergedHitObject.Path.CalculatedDistance + distanceToLastControlPoint + hasPath.Path.Distance;
|
||||
|
||||
// Remove the last control point if it sits exactly on the start of the next control point.
|
||||
if (Precision.AlmostEquals(distanceToLastControlPoint, 0))
|
||||
{
|
||||
mergedHitObject.Path.ControlPoints.RemoveAt(mergedHitObject.Path.ControlPoints.Count - 1);
|
||||
}
|
||||
|
||||
mergedHitObject.Path.ControlPoints.AddRange(hasPath.Path.ControlPoints.Select(o => new PathControlPoint(o.Position + offset, o.Type)));
|
||||
lastCircle = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Turn the last control point into a linear type if this is the first merging circle in a sequence, so the subsequent control points can be inherited path type.
|
||||
if (!lastCircle)
|
||||
{
|
||||
mergedHitObject.Path.ControlPoints.Last().Type = PathType.Linear;
|
||||
}
|
||||
|
||||
mergedHitObject.Path.ControlPoints.Add(new PathControlPoint(selectedMergeableObject.Position - mergedHitObject.Position));
|
||||
mergedHitObject.Path.ExpectedDistance.Value = null;
|
||||
lastCircle = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure only the merged hit object is in the beatmap.
|
||||
if (firstHitObject is Slider)
|
||||
{
|
||||
foreach (var selectedMergeableObject in mergeableObjects.Skip(1))
|
||||
{
|
||||
EditorBeatmap.Remove(selectedMergeableObject);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var selectedMergeableObject in mergeableObjects)
|
||||
{
|
||||
EditorBeatmap.Remove(selectedMergeableObject);
|
||||
}
|
||||
|
||||
EditorBeatmap.Add(mergedHitObject);
|
||||
}
|
||||
|
||||
// Make sure the merged hitobject is selected.
|
||||
SelectedItems.Clear();
|
||||
SelectedItems.Add(mergedHitObject);
|
||||
|
||||
ChangeHandler?.EndChange();
|
||||
}
|
||||
|
||||
protected override IEnumerable<MenuItem> GetContextMenuItemsForSelection(IEnumerable<SelectionBlueprint<HitObject>> selection)
|
||||
{
|
||||
foreach (var item in base.GetContextMenuItemsForSelection(selection))
|
||||
yield return item;
|
||||
|
||||
if (selectedMergeableObjects.Length > 1)
|
||||
yield return new OsuMenuItem("Merge selection", MenuItemType.Destructive, mergeSelection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -49,6 +49,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Setup
|
||||
private void updateBeatmap()
|
||||
{
|
||||
Beatmap.BeatmapInfo.StackLeniency = stackLeniency.Current.Value;
|
||||
Beatmap.SaveState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
/// <summary>
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
/// <summary>
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@ -23,18 +21,18 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public override IconUsage? Icon => OsuIcon.ModAutopilot;
|
||||
public override ModType Type => ModType.Automation;
|
||||
public override string Description => @"Automatic cursor movement - just follow the rhythm.";
|
||||
public override double ScoreMultiplier => 1;
|
||||
public override double ScoreMultiplier => 0.1;
|
||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail), typeof(ModAutoplay), typeof(OsuModMagnetised), typeof(OsuModRepel) };
|
||||
|
||||
public bool PerformFail() => false;
|
||||
|
||||
public bool RestartOnFail => false;
|
||||
|
||||
private OsuInputManager inputManager;
|
||||
private OsuInputManager inputManager = null!;
|
||||
|
||||
private IFrameStableClock gameplayClock;
|
||||
private IFrameStableClock gameplayClock = null!;
|
||||
|
||||
private List<OsuReplayFrame> replayFrames;
|
||||
private List<OsuReplayFrame> replayFrames = null!;
|
||||
|
||||
private int currentFrame;
|
||||
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
@ -33,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
|
||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModFlashlight) };
|
||||
|
||||
private DrawableOsuBlinds blinds;
|
||||
private DrawableOsuBlinds blinds = null!;
|
||||
|
||||
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
||||
{
|
||||
@ -55,9 +53,12 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
/// <summary>
|
||||
/// Black background boxes behind blind panel textures.
|
||||
/// </summary>
|
||||
private Box blackBoxLeft, blackBoxRight;
|
||||
private Box blackBoxLeft = null!, blackBoxRight = null!;
|
||||
|
||||
private Drawable panelLeft, panelRight, bgPanelLeft, bgPanelRight;
|
||||
private Drawable panelLeft = null!;
|
||||
private Drawable panelRight = null!;
|
||||
private Drawable bgPanelLeft = null!;
|
||||
private Drawable bgPanelRight = null!;
|
||||
|
||||
private readonly Beatmap<OsuHitObject> beatmap;
|
||||
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Game.Rulesets.Mods;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Configuration;
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Game.Rulesets.Mods;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Game.Rulesets.Mods;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
@ -53,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
|
||||
public override float DefaultFlashlightSize => 180;
|
||||
|
||||
private OsuFlashlight flashlight;
|
||||
private OsuFlashlight flashlight = null!;
|
||||
|
||||
protected override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(this);
|
||||
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Configuration;
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Game.Rulesets.Mods;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
@ -25,10 +23,10 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public override IconUsage? Icon => FontAwesome.Solid.Magnet;
|
||||
public override ModType Type => ModType.Fun;
|
||||
public override string Description => "No need to chase the circles – your cursor is a magnet!";
|
||||
public override double ScoreMultiplier => 1;
|
||||
public override double ScoreMultiplier => 0.5;
|
||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModRelax), typeof(OsuModRepel) };
|
||||
|
||||
private IFrameStableClock gameplayClock;
|
||||
private IFrameStableClock gameplayClock = null!;
|
||||
|
||||
[SettingSource("Attraction strength", "How strong the pull is.", 0)]
|
||||
public BindableFloat AttractionStrength { get; } = new BindableFloat(0.5f)
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Configuration;
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
@ -21,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public override string Description => "Where's the cursor?";
|
||||
|
||||
private PeriodTracker spinnerPeriods;
|
||||
private PeriodTracker spinnerPeriods = null!;
|
||||
|
||||
[SettingSource(
|
||||
"Hidden at combo",
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
@ -31,9 +29,9 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
private bool isDownState;
|
||||
private bool wasLeft;
|
||||
|
||||
private OsuInputManager osuInputManager;
|
||||
private OsuInputManager osuInputManager = null!;
|
||||
|
||||
private ReplayState<OsuAction> state;
|
||||
private ReplayState<OsuAction> state = null!;
|
||||
private double lastStateChangeTime;
|
||||
|
||||
private bool hasReplay;
|
||||
@ -134,7 +132,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
wasLeft = !wasLeft;
|
||||
}
|
||||
|
||||
state?.Apply(osuInputManager.CurrentState, osuInputManager);
|
||||
state.Apply(osuInputManager.CurrentState, osuInputManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@ -96,11 +94,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
|
||||
#region Private Fields
|
||||
|
||||
private ControlPointInfo controlPointInfo;
|
||||
|
||||
private List<OsuHitObject> originalHitObjects;
|
||||
|
||||
private Random rng;
|
||||
private ControlPointInfo controlPointInfo = null!;
|
||||
|
||||
#endregion
|
||||
|
||||
@ -171,16 +165,17 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public override void ApplyToBeatmap(IBeatmap beatmap)
|
||||
{
|
||||
Seed.Value ??= RNG.Next();
|
||||
rng = new Random(Seed.Value.Value);
|
||||
|
||||
var rng = new Random(Seed.Value.Value);
|
||||
|
||||
var osuBeatmap = (OsuBeatmap)beatmap;
|
||||
|
||||
if (osuBeatmap.HitObjects.Count == 0) return;
|
||||
|
||||
controlPointInfo = osuBeatmap.ControlPointInfo;
|
||||
originalHitObjects = osuBeatmap.HitObjects.OrderBy(x => x.StartTime).ToList();
|
||||
|
||||
var hitObjects = generateBeats(osuBeatmap)
|
||||
var originalHitObjects = osuBeatmap.HitObjects.OrderBy(x => x.StartTime).ToList();
|
||||
var hitObjects = generateBeats(osuBeatmap, originalHitObjects)
|
||||
.Select(beat =>
|
||||
{
|
||||
var newCircle = new HitCircle();
|
||||
@ -189,18 +184,18 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
return (OsuHitObject)newCircle;
|
||||
}).ToList();
|
||||
|
||||
addHitSamples(hitObjects);
|
||||
addHitSamples(hitObjects, originalHitObjects);
|
||||
|
||||
fixComboInfo(hitObjects);
|
||||
fixComboInfo(hitObjects, originalHitObjects);
|
||||
|
||||
randomizeCirclePos(hitObjects);
|
||||
randomizeCirclePos(hitObjects, rng);
|
||||
|
||||
osuBeatmap.HitObjects = hitObjects;
|
||||
|
||||
base.ApplyToBeatmap(beatmap);
|
||||
}
|
||||
|
||||
private IEnumerable<double> generateBeats(IBeatmap beatmap)
|
||||
private IEnumerable<double> generateBeats(IBeatmap beatmap, IReadOnlyCollection<OsuHitObject> originalHitObjects)
|
||||
{
|
||||
double startTime = originalHitObjects.First().StartTime;
|
||||
double endTime = originalHitObjects.Last().GetEndTime();
|
||||
@ -213,7 +208,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
// Remove beats before startTime
|
||||
.Where(beat => almostBigger(beat, startTime))
|
||||
// Remove beats during breaks
|
||||
.Where(beat => !isInsideBreakPeriod(beatmap.Breaks, beat))
|
||||
.Where(beat => !isInsideBreakPeriod(originalHitObjects, beatmap.Breaks, beat))
|
||||
.ToList();
|
||||
|
||||
// Remove beats that are too close to the next one (e.g. due to timing point changes)
|
||||
@ -228,7 +223,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
return beats;
|
||||
}
|
||||
|
||||
private void addHitSamples(IEnumerable<OsuHitObject> hitObjects)
|
||||
private void addHitSamples(IEnumerable<OsuHitObject> hitObjects, List<OsuHitObject> originalHitObjects)
|
||||
{
|
||||
foreach (var obj in hitObjects)
|
||||
{
|
||||
@ -240,7 +235,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
}
|
||||
}
|
||||
|
||||
private void fixComboInfo(List<OsuHitObject> hitObjects)
|
||||
private void fixComboInfo(List<OsuHitObject> hitObjects, List<OsuHitObject> originalHitObjects)
|
||||
{
|
||||
// Copy combo indices from an original object at the same time or from the closest preceding object
|
||||
// (Objects lying between two combos are assumed to belong to the preceding combo)
|
||||
@ -274,7 +269,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
}
|
||||
}
|
||||
|
||||
private void randomizeCirclePos(IReadOnlyList<OsuHitObject> hitObjects)
|
||||
private void randomizeCirclePos(IReadOnlyList<OsuHitObject> hitObjects, Random rng)
|
||||
{
|
||||
if (hitObjects.Count == 0) return;
|
||||
|
||||
@ -355,9 +350,10 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
/// The given time is also considered to be inside a break if it is earlier than the
|
||||
/// start time of the first original hit object after the break.
|
||||
/// </remarks>
|
||||
/// <param name="originalHitObjects">Hit objects order by time.</param>
|
||||
/// <param name="breaks">The breaks of the beatmap.</param>
|
||||
/// <param name="time">The time to be checked.</param>=
|
||||
private bool isInsideBreakPeriod(IEnumerable<BreakPeriod> breaks, double time)
|
||||
private bool isInsideBreakPeriod(IReadOnlyCollection<OsuHitObject> originalHitObjects, IEnumerable<BreakPeriod> breaks, double time)
|
||||
{
|
||||
return breaks.Any(breakPeriod =>
|
||||
{
|
||||
@ -405,7 +401,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
/// <param name="hitObjects">The list of hit objects in a beatmap, ordered by StartTime</param>
|
||||
/// <param name="time">The point in time to get samples for</param>
|
||||
/// <returns>Hit samples</returns>
|
||||
private IList<HitSampleInfo> getSamplesAtTime(IEnumerable<OsuHitObject> hitObjects, double time)
|
||||
private IList<HitSampleInfo>? getSamplesAtTime(IEnumerable<OsuHitObject> hitObjects, double time)
|
||||
{
|
||||
// Get a hit object that
|
||||
// either has StartTime equal to the target time
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Game.Rulesets.Mods;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
@ -59,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
}
|
||||
}
|
||||
|
||||
private void applyCirclePieceState(DrawableOsuHitObject hitObject, IDrawable hitCircle = null)
|
||||
private void applyCirclePieceState(DrawableOsuHitObject hitObject, IDrawable? hitCircle = null)
|
||||
{
|
||||
var h = hitObject.HitObject;
|
||||
using (hitObject.BeginAbsoluteSequence(h.StartTime - h.TimePreempt))
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
|
@ -1,11 +1,11 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
@ -24,8 +24,15 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public override double ScoreMultiplier => 1;
|
||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform), typeof(OsuModMagnetised), typeof(OsuModRepel) };
|
||||
|
||||
private const int wiggle_duration = 90; // (ms) Higher = fewer wiggles
|
||||
private const int wiggle_strength = 10; // Higher = stronger wiggles
|
||||
private const int wiggle_duration = 100; // (ms) Higher = fewer wiggles
|
||||
|
||||
[SettingSource("Strength", "Multiplier applied to the wiggling strength.")]
|
||||
public BindableDouble Strength { get; } = new BindableDouble(1)
|
||||
{
|
||||
MinValue = 0.1f,
|
||||
MaxValue = 2f,
|
||||
Precision = 0.1f
|
||||
};
|
||||
|
||||
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) => drawableOnApplyCustomUpdateState(hitObject, state);
|
||||
|
||||
@ -49,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
void wiggle()
|
||||
{
|
||||
float nextAngle = (float)(objRand.NextDouble() * 2 * Math.PI);
|
||||
float nextDist = (float)(objRand.NextDouble() * wiggle_strength);
|
||||
float nextDist = (float)(objRand.NextDouble() * Strength.Value * 7);
|
||||
drawable.MoveTo(new Vector2((float)(nextDist * Math.Cos(nextAngle) + origin.X), (float)(nextDist * Math.Sin(nextAngle) + origin.Y)), wiggle_duration);
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
||||
{
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
connectionPool = new DrawablePool<FollowPointConnection>(1, 200),
|
||||
connectionPool = new DrawablePool<FollowPointConnection>(10, 200),
|
||||
pointPool = new DrawablePool<FollowPoint>(50, 1000)
|
||||
};
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.Diagnostics;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
@ -32,37 +30,42 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
};
|
||||
}
|
||||
|
||||
protected override void OnTrackingChanged(ValueChangedEvent<bool> tracking)
|
||||
protected override void OnSliderPress()
|
||||
{
|
||||
Debug.Assert(ParentObject != null);
|
||||
|
||||
const float duration = 300f;
|
||||
|
||||
if (ParentObject.Judged)
|
||||
return;
|
||||
if (Precision.AlmostEquals(0, Alpha))
|
||||
this.ScaleTo(1);
|
||||
|
||||
if (tracking.NewValue)
|
||||
{
|
||||
if (Precision.AlmostEquals(0, Alpha))
|
||||
this.ScaleTo(1);
|
||||
this.ScaleTo(DrawableSliderBall.FOLLOW_AREA, duration, Easing.OutQuint)
|
||||
.FadeIn(duration, Easing.OutQuint);
|
||||
}
|
||||
|
||||
this.ScaleTo(DrawableSliderBall.FOLLOW_AREA, duration, Easing.OutQuint)
|
||||
.FadeTo(1f, duration, Easing.OutQuint);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 1.2f, duration / 2, Easing.OutQuint)
|
||||
.FadeTo(0, duration / 2, Easing.OutQuint);
|
||||
}
|
||||
protected override void OnSliderRelease()
|
||||
{
|
||||
const float duration = 150;
|
||||
|
||||
this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 1.2f, duration, Easing.OutQuint)
|
||||
.FadeTo(0, duration, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override void OnSliderEnd()
|
||||
{
|
||||
const float fade_duration = 300;
|
||||
const float duration = 300;
|
||||
|
||||
// intentionally pile on an extra FadeOut to make it happen much faster
|
||||
this.ScaleTo(1, fade_duration, Easing.OutQuint);
|
||||
this.FadeOut(fade_duration / 2, Easing.OutQuint);
|
||||
this.ScaleTo(1, duration, Easing.OutQuint)
|
||||
.FadeOut(duration / 2, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override void OnSliderTick()
|
||||
{
|
||||
this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 1.08f, 40, Easing.OutQuint)
|
||||
.Then()
|
||||
.ScaleTo(DrawableSliderBall.FOLLOW_AREA, 200f, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override void OnSliderBreak()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
private bool rotationTransferred;
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
private GameplayClock gameplayClock { get; set; }
|
||||
private IGameplayClock gameplayClock { get; set; }
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
|
@ -1,8 +1,8 @@
|
||||
// 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.Diagnostics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
@ -23,7 +23,17 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
((DrawableSlider?)ParentObject)?.Tracking.BindValueChanged(OnTrackingChanged, true);
|
||||
((DrawableSlider?)ParentObject)?.Tracking.BindValueChanged(tracking =>
|
||||
{
|
||||
Debug.Assert(ParentObject != null);
|
||||
if (ParentObject.Judged)
|
||||
return;
|
||||
|
||||
if (tracking.NewValue)
|
||||
OnSliderPress();
|
||||
else
|
||||
OnSliderRelease();
|
||||
}, true);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@ -48,13 +58,46 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
|
||||
private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state)
|
||||
{
|
||||
// Gets called by slider ticks, tails, etc., leading to duplicated
|
||||
// animations which may negatively affect performance
|
||||
if (drawableObject is not DrawableSlider)
|
||||
return;
|
||||
Debug.Assert(ParentObject != null);
|
||||
|
||||
using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime))
|
||||
OnSliderEnd();
|
||||
switch (state)
|
||||
{
|
||||
case ArmedState.Hit:
|
||||
switch (drawableObject)
|
||||
{
|
||||
case DrawableSliderTail:
|
||||
// Use ParentObject instead of drawableObject because slider tail's
|
||||
// HitStateUpdateTime is ~36ms before the actual slider end (aka slider
|
||||
// tail leniency)
|
||||
using (BeginAbsoluteSequence(ParentObject.HitStateUpdateTime))
|
||||
OnSliderEnd();
|
||||
break;
|
||||
|
||||
case DrawableSliderTick:
|
||||
case DrawableSliderRepeat:
|
||||
using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime))
|
||||
OnSliderTick();
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case ArmedState.Miss:
|
||||
switch (drawableObject)
|
||||
{
|
||||
case DrawableSliderTail:
|
||||
case DrawableSliderTick:
|
||||
case DrawableSliderRepeat:
|
||||
// Despite above comment, ok to use drawableObject.HitStateUpdateTime
|
||||
// here, since on stable, the break anim plays right when the tail is
|
||||
// missed, not when the slider ends
|
||||
using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime))
|
||||
OnSliderBreak();
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
@ -68,8 +111,14 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void OnTrackingChanged(ValueChangedEvent<bool> tracking);
|
||||
protected abstract void OnSliderPress();
|
||||
|
||||
protected abstract void OnSliderRelease();
|
||||
|
||||
protected abstract void OnSliderEnd();
|
||||
|
||||
protected abstract void OnSliderTick();
|
||||
|
||||
protected abstract void OnSliderBreak();
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
@ -21,29 +20,20 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
InternalChild = animationContent;
|
||||
}
|
||||
|
||||
protected override void OnTrackingChanged(ValueChangedEvent<bool> tracking)
|
||||
protected override void OnSliderPress()
|
||||
{
|
||||
Debug.Assert(ParentObject != null);
|
||||
|
||||
if (ParentObject.Judged)
|
||||
return;
|
||||
|
||||
double remainingTime = Math.Max(0, ParentObject.HitStateUpdateTime - Time.Current);
|
||||
|
||||
// Note that the scale adjust here is 2 instead of DrawableSliderBall.FOLLOW_AREA to match legacy behaviour.
|
||||
// This means the actual tracking area for gameplay purposes is larger than the sprite (but skins may be accounting for this).
|
||||
if (tracking.NewValue)
|
||||
{
|
||||
// TODO: Follow circle should bounce on each slider tick.
|
||||
this.ScaleTo(0.5f).ScaleTo(2f, Math.Min(180f, remainingTime), Easing.Out)
|
||||
.FadeTo(0).FadeTo(1f, Math.Min(60f, remainingTime));
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Should animate only at the next slider tick if we want to match stable perfectly.
|
||||
this.ScaleTo(4f, 100)
|
||||
.FadeTo(0f, 100);
|
||||
}
|
||||
this.ScaleTo(0.5f).ScaleTo(2f, Math.Min(180f, remainingTime), Easing.Out)
|
||||
.FadeTo(0).FadeTo(1f, Math.Min(60f, remainingTime));
|
||||
}
|
||||
|
||||
protected override void OnSliderRelease()
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnSliderEnd()
|
||||
@ -51,5 +41,17 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
this.ScaleTo(1.6f, 200, Easing.Out)
|
||||
.FadeOut(200, Easing.In);
|
||||
}
|
||||
|
||||
protected override void OnSliderTick()
|
||||
{
|
||||
this.ScaleTo(2.2f)
|
||||
.ScaleTo(2f, 200);
|
||||
}
|
||||
|
||||
protected override void OnSliderBreak()
|
||||
{
|
||||
this.ScaleTo(4f, 100)
|
||||
.FadeTo(0f, 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,9 +9,9 @@ using System.Runtime.InteropServices;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Batches;
|
||||
using osu.Framework.Graphics.OpenGL.Vertices;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Graphics.Rendering;
|
||||
using osu.Framework.Graphics.Rendering.Vertices;
|
||||
using osu.Framework.Graphics.Shaders;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Input;
|
||||
@ -68,8 +68,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ShaderManager shaders)
|
||||
private void load(IRenderer renderer, ShaderManager shaders)
|
||||
{
|
||||
texture ??= renderer.WhitePixel;
|
||||
shader = shaders.Load(@"CursorTrail", FragmentShaderDescriptor.TEXTURE);
|
||||
}
|
||||
|
||||
@ -79,7 +80,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
resetTime();
|
||||
}
|
||||
|
||||
private Texture texture = Texture.WhitePixel;
|
||||
private Texture texture;
|
||||
|
||||
public Texture Texture
|
||||
{
|
||||
@ -222,7 +223,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
private Vector2 size;
|
||||
private Vector2 originPosition;
|
||||
|
||||
private readonly QuadBatch<TexturedTrailVertex> vertexBatch = new QuadBatch<TexturedTrailVertex>(max_sprites, 1);
|
||||
private IVertexBatch<TexturedTrailVertex> vertexBatch;
|
||||
|
||||
public TrailDrawNode(CursorTrail source)
|
||||
: base(source)
|
||||
@ -254,15 +255,17 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
Source.parts.CopyTo(parts, 0);
|
||||
}
|
||||
|
||||
public override void Draw(Action<TexturedVertex2D> vertexAction)
|
||||
public override void Draw(IRenderer renderer)
|
||||
{
|
||||
base.Draw(vertexAction);
|
||||
base.Draw(renderer);
|
||||
|
||||
vertexBatch ??= renderer.CreateQuadBatch<TexturedTrailVertex>(max_sprites, 1);
|
||||
|
||||
shader.Bind();
|
||||
shader.GetUniform<float>("g_FadeClock").UpdateValue(ref time);
|
||||
shader.GetUniform<float>("g_FadeExponent").UpdateValue(ref fadeExponent);
|
||||
|
||||
texture.TextureGL.Bind();
|
||||
texture.Bind();
|
||||
|
||||
RectangleF textureRect = texture.GetTextureRect();
|
||||
|
||||
@ -319,7 +322,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
vertexBatch.Dispose();
|
||||
vertexBatch?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,9 +11,11 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Pooling;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||
using osu.Game.Rulesets.Osu.Configuration;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
@ -112,21 +114,36 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(OsuRulesetConfigManager config)
|
||||
private void load(OsuRulesetConfigManager config, IBeatmap beatmap)
|
||||
{
|
||||
config?.BindWith(OsuRulesetSetting.PlayfieldBorderStyle, playfieldBorder.PlayfieldBorderStyle);
|
||||
|
||||
RegisterPool<HitCircle, DrawableHitCircle>(10, 100);
|
||||
var osuBeatmap = (OsuBeatmap)beatmap;
|
||||
|
||||
RegisterPool<Slider, DrawableSlider>(10, 100);
|
||||
RegisterPool<SliderHeadCircle, DrawableSliderHead>(10, 100);
|
||||
RegisterPool<SliderTailCircle, DrawableSliderTail>(10, 100);
|
||||
RegisterPool<SliderTick, DrawableSliderTick>(10, 100);
|
||||
RegisterPool<SliderRepeat, DrawableSliderRepeat>(5, 50);
|
||||
RegisterPool<HitCircle, DrawableHitCircle>(20, 100);
|
||||
|
||||
// handle edge cases where a beatmap has a slider with many repeats.
|
||||
int maxRepeatsOnOneSlider = 0;
|
||||
int maxTicksOnOneSlider = 0;
|
||||
|
||||
if (osuBeatmap != null)
|
||||
{
|
||||
foreach (var slider in osuBeatmap.HitObjects.OfType<Slider>())
|
||||
{
|
||||
maxRepeatsOnOneSlider = Math.Max(maxRepeatsOnOneSlider, slider.RepeatCount);
|
||||
maxTicksOnOneSlider = Math.Max(maxTicksOnOneSlider, slider.NestedHitObjects.OfType<SliderTick>().Count());
|
||||
}
|
||||
}
|
||||
|
||||
RegisterPool<Slider, DrawableSlider>(20, 100);
|
||||
RegisterPool<SliderHeadCircle, DrawableSliderHead>(20, 100);
|
||||
RegisterPool<SliderTailCircle, DrawableSliderTail>(20, 100);
|
||||
RegisterPool<SliderTick, DrawableSliderTick>(Math.Max(maxTicksOnOneSlider, 20), Math.Max(maxTicksOnOneSlider, 200));
|
||||
RegisterPool<SliderRepeat, DrawableSliderRepeat>(Math.Max(maxRepeatsOnOneSlider, 20), Math.Max(maxRepeatsOnOneSlider, 200));
|
||||
|
||||
RegisterPool<Spinner, DrawableSpinner>(2, 20);
|
||||
RegisterPool<SpinnerTick, DrawableSpinnerTick>(10, 100);
|
||||
RegisterPool<SpinnerBonusTick, DrawableSpinnerBonusTick>(10, 100);
|
||||
RegisterPool<SpinnerTick, DrawableSpinnerTick>(10, 200);
|
||||
RegisterPool<SpinnerBonusTick, DrawableSpinnerBonusTick>(10, 200);
|
||||
}
|
||||
|
||||
protected override HitObjectLifetimeEntry CreateLifetimeEntry(HitObject hitObject) => new OsuHitObjectLifetimeEntry(hitObject);
|
||||
@ -173,7 +190,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
private readonly Action<DrawableOsuJudgement> onLoaded;
|
||||
|
||||
public DrawableJudgementPool(HitResult result, Action<DrawableOsuJudgement> onLoaded)
|
||||
: base(10)
|
||||
: base(20)
|
||||
{
|
||||
this.result = result;
|
||||
this.onLoaded = onLoaded;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user