Merge remote-tracking branch 'refs/remotes/origin/master' into nightcore-beats
@ -176,8 +176,8 @@ dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
|
||||
#Style - C# 8 features
|
||||
csharp_prefer_static_local_function = true:warning
|
||||
csharp_prefer_simple_using_statement = true:silent
|
||||
csharp_style_prefer_index_operator = false:none
|
||||
csharp_style_prefer_range_operator = false:none
|
||||
csharp_style_prefer_index_operator = true:warning
|
||||
csharp_style_prefer_range_operator = true:warning
|
||||
csharp_style_prefer_switch_expression = false:none
|
||||
|
||||
#Supressing roslyn built-in analyzers
|
||||
|
58
CodeAnalysis/osu.ruleset
Normal file
@ -0,0 +1,58 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RuleSet Name="osu! Rule Set" Description=" " ToolsVersion="16.0">
|
||||
<Rules AnalyzerId="Microsoft.CodeQuality.Analyzers" RuleNamespace="Microsoft.CodeQuality.Analyzers">
|
||||
<Rule Id="CA1016" Action="None" />
|
||||
<Rule Id="CA1028" Action="None" />
|
||||
<Rule Id="CA1031" Action="None" />
|
||||
<Rule Id="CA1034" Action="None" />
|
||||
<Rule Id="CA1036" Action="None" />
|
||||
<Rule Id="CA1040" Action="None" />
|
||||
<Rule Id="CA1044" Action="None" />
|
||||
<Rule Id="CA1051" Action="None" />
|
||||
<Rule Id="CA1054" Action="None" />
|
||||
<Rule Id="CA1056" Action="None" />
|
||||
<Rule Id="CA1062" Action="None" />
|
||||
<Rule Id="CA1063" Action="None" />
|
||||
<Rule Id="CA1067" Action="None" />
|
||||
<Rule Id="CA1707" Action="None" />
|
||||
<Rule Id="CA1710" Action="None" />
|
||||
<Rule Id="CA1714" Action="None" />
|
||||
<Rule Id="CA1716" Action="None" />
|
||||
<Rule Id="CA1717" Action="None" />
|
||||
<Rule Id="CA1720" Action="None" />
|
||||
<Rule Id="CA1721" Action="None" />
|
||||
<Rule Id="CA1724" Action="None" />
|
||||
<Rule Id="CA1801" Action="None" />
|
||||
<Rule Id="CA1806" Action="None" />
|
||||
<Rule Id="CA1812" Action="None" />
|
||||
<Rule Id="CA1814" Action="None" />
|
||||
<Rule Id="CA1815" Action="None" />
|
||||
<Rule Id="CA1819" Action="None" />
|
||||
<Rule Id="CA1822" Action="None" />
|
||||
<Rule Id="CA1823" Action="None" />
|
||||
<Rule Id="CA2007" Action="None" />
|
||||
<Rule Id="CA2214" Action="None" />
|
||||
<Rule Id="CA2227" Action="None" />
|
||||
</Rules>
|
||||
<Rules AnalyzerId="Microsoft.CodeQuality.CSharp.Analyzers" RuleNamespace="Microsoft.CodeQuality.CSharp.Analyzers">
|
||||
<Rule Id="CA1001" Action="None" />
|
||||
<Rule Id="CA1032" Action="None" />
|
||||
</Rules>
|
||||
<Rules AnalyzerId="Microsoft.NetCore.Analyzers" RuleNamespace="Microsoft.NetCore.Analyzers">
|
||||
<Rule Id="CA1303" Action="None" />
|
||||
<Rule Id="CA1304" Action="None" />
|
||||
<Rule Id="CA1305" Action="None" />
|
||||
<Rule Id="CA1307" Action="None" />
|
||||
<Rule Id="CA1308" Action="None" />
|
||||
<Rule Id="CA1816" Action="None" />
|
||||
<Rule Id="CA1826" Action="None" />
|
||||
<Rule Id="CA2000" Action="None" />
|
||||
<Rule Id="CA2008" Action="None" />
|
||||
<Rule Id="CA2213" Action="None" />
|
||||
<Rule Id="CA2235" Action="None" />
|
||||
</Rules>
|
||||
<Rules AnalyzerId="Microsoft.NetCore.CSharp.Analyzers" RuleNamespace="Microsoft.NetCore.CSharp.Analyzers">
|
||||
<Rule Id="CA1309" Action="Warning" />
|
||||
<Rule Id="CA2201" Action="Warning" />
|
||||
</Rules>
|
||||
</RuleSet>
|
@ -18,7 +18,11 @@
|
||||
<ItemGroup Label="Code Analysis">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="2.9.8" PrivateAssets="All" />
|
||||
<AdditionalFiles Include="$(MSBuildThisFileDirectory)CodeAnalysis\BannedSymbols.txt" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Code Analysis">
|
||||
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)CodeAnalysis\osu.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="Documentation">
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
||||
|
@ -1,5 +1,5 @@
|
||||
#addin "nuget:?package=CodeFileSanity&version=0.0.33"
|
||||
#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2019.2.1"
|
||||
#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2019.3.0"
|
||||
#tool "nuget:?package=NVika.MSBuild&version=1.0.1"
|
||||
var nVikaToolPath = GetFiles("./tools/NVika.MSBuild.*/tools/NVika.exe").First();
|
||||
|
||||
|
@ -1,4 +1,9 @@
|
||||
{
|
||||
"sdk": {
|
||||
"allowPrerelease": false,
|
||||
"rollForward": "minor",
|
||||
"version": "3.1.100"
|
||||
},
|
||||
"msbuild-sdks": {
|
||||
"Microsoft.Build.Traversal": "2.0.24"
|
||||
}
|
||||
|
120
osu.Desktop/DiscordRichPresence.cs
Normal file
@ -0,0 +1,120 @@
|
||||
// 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 DiscordRPC;
|
||||
using DiscordRPC.Message;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Users;
|
||||
using LogLevel = osu.Framework.Logging.LogLevel;
|
||||
using User = osu.Game.Users.User;
|
||||
|
||||
namespace osu.Desktop
|
||||
{
|
||||
internal class DiscordRichPresence : Component
|
||||
{
|
||||
private const string client_id = "367827983903490050";
|
||||
|
||||
private DiscordRpcClient client;
|
||||
|
||||
[Resolved]
|
||||
private IBindable<RulesetInfo> ruleset { get; set; }
|
||||
|
||||
private Bindable<User> user;
|
||||
|
||||
private readonly IBindable<UserStatus> status = new Bindable<UserStatus>();
|
||||
private readonly IBindable<UserActivity> activity = new Bindable<UserActivity>();
|
||||
|
||||
private readonly RichPresence presence = new RichPresence
|
||||
{
|
||||
Assets = new Assets { LargeImageKey = "osu_logo_lazer", }
|
||||
};
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(IAPIProvider provider)
|
||||
{
|
||||
client = new DiscordRpcClient(client_id)
|
||||
{
|
||||
SkipIdenticalPresence = false // handles better on discord IPC loss, see updateStatus call in onReady.
|
||||
};
|
||||
|
||||
client.OnReady += onReady;
|
||||
client.OnError += (_, e) => Logger.Log($"An error occurred with Discord RPC Client: {e.Code} {e.Message}", LoggingTarget.Network, LogLevel.Error);
|
||||
client.OnConnectionFailed += (_, e) => Logger.Log($"An connection occurred with Discord RPC Client: {e.Type}", LoggingTarget.Network, LogLevel.Error);
|
||||
|
||||
(user = provider.LocalUser.GetBoundCopy()).BindValueChanged(u =>
|
||||
{
|
||||
status.UnbindBindings();
|
||||
status.BindTo(u.NewValue.Status);
|
||||
|
||||
activity.UnbindBindings();
|
||||
activity.BindTo(u.NewValue.Activity);
|
||||
}, true);
|
||||
|
||||
ruleset.BindValueChanged(_ => updateStatus());
|
||||
status.BindValueChanged(_ => updateStatus());
|
||||
activity.BindValueChanged(_ => updateStatus());
|
||||
|
||||
client.Initialize();
|
||||
}
|
||||
|
||||
private void onReady(object _, ReadyMessage __)
|
||||
{
|
||||
Logger.Log("Discord RPC Client ready.", LoggingTarget.Network, LogLevel.Debug);
|
||||
updateStatus();
|
||||
}
|
||||
|
||||
private void updateStatus()
|
||||
{
|
||||
if (status.Value is UserStatusOffline)
|
||||
{
|
||||
client.ClearPresence();
|
||||
return;
|
||||
}
|
||||
|
||||
if (status.Value is UserStatusOnline && activity.Value != null)
|
||||
{
|
||||
presence.State = activity.Value.Status;
|
||||
presence.Details = getDetails(activity.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
presence.State = "Idle";
|
||||
presence.Details = string.Empty;
|
||||
}
|
||||
|
||||
// update user information
|
||||
presence.Assets.LargeImageText = $"{user.Value.Username}" + (user.Value.Statistics?.Ranks.Global > 0 ? $" (rank #{user.Value.Statistics.Ranks.Global:N0})" : string.Empty);
|
||||
|
||||
// update ruleset
|
||||
presence.Assets.SmallImageKey = ruleset.Value.ID <= 3 ? $"mode_{ruleset.Value.ID}" : "mode_custom";
|
||||
presence.Assets.SmallImageText = ruleset.Value.Name;
|
||||
|
||||
client.SetPresence(presence);
|
||||
}
|
||||
|
||||
private string getDetails(UserActivity activity)
|
||||
{
|
||||
switch (activity)
|
||||
{
|
||||
case UserActivity.SoloGame solo:
|
||||
return solo.Beatmap.ToString();
|
||||
|
||||
case UserActivity.Editing edit:
|
||||
return edit.Beatmap.ToString();
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
client.Dispose();
|
||||
base.Dispose(isDisposing);
|
||||
}
|
||||
}
|
||||
}
|
@ -60,6 +60,8 @@ namespace osu.Desktop
|
||||
else
|
||||
Add(new SimpleUpdateManager());
|
||||
}
|
||||
|
||||
LoadComponentAsync(new DiscordRichPresence(), Add);
|
||||
}
|
||||
|
||||
protected override void ScreenChanged(IScreen lastScreen, IScreen newScreen)
|
||||
|
@ -29,6 +29,7 @@
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.2.6" />
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="4.7.0" />
|
||||
<PackageReference Include="DiscordRichPresence" Version="1.0.121" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Resources">
|
||||
<EmbeddedResource Include="lazer.ico" />
|
||||
|
@ -16,14 +16,20 @@ using osu.Game.Rulesets.Replays.Types;
|
||||
using osu.Game.Beatmaps.Legacy;
|
||||
using osu.Game.Rulesets.Catch.Beatmaps;
|
||||
using osu.Game.Rulesets.Catch.Difficulty;
|
||||
using osu.Game.Rulesets.Catch.Scoring;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
using System;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch
|
||||
{
|
||||
public class CatchRuleset : Ruleset
|
||||
{
|
||||
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => new DrawableCatchRuleset(this, beatmap, mods);
|
||||
|
||||
public override ScoreProcessor CreateScoreProcessor(IBeatmap beatmap) => new CatchScoreProcessor(beatmap);
|
||||
|
||||
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap);
|
||||
public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new CatchBeatmapProcessor(beatmap);
|
||||
|
||||
@ -51,7 +57,9 @@ namespace osu.Game.Rulesets.Catch
|
||||
else if (mods.HasFlag(LegacyMods.SuddenDeath))
|
||||
yield return new CatchModSuddenDeath();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.Autoplay))
|
||||
if (mods.HasFlag(LegacyMods.Cinema))
|
||||
yield return new CatchModCinema();
|
||||
else if (mods.HasFlag(LegacyMods.Autoplay))
|
||||
yield return new CatchModAutoplay();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.Easy))
|
||||
@ -101,7 +109,7 @@ namespace osu.Game.Rulesets.Catch
|
||||
case ModType.Automation:
|
||||
return new Mod[]
|
||||
{
|
||||
new MultiMod(new CatchModAutoplay(), new ModCinema()),
|
||||
new MultiMod(new CatchModAutoplay(), new CatchModCinema()),
|
||||
new CatchModRelax(),
|
||||
};
|
||||
|
||||
@ -112,7 +120,7 @@ namespace osu.Game.Rulesets.Catch
|
||||
};
|
||||
|
||||
default:
|
||||
return new Mod[] { };
|
||||
return Array.Empty<Mod>();
|
||||
}
|
||||
}
|
||||
|
||||
|
21
osu.Game.Rulesets.Catch/Mods/CatchModCinema.cs
Normal file
@ -0,0 +1,21 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Catch.Replays;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Mods
|
||||
{
|
||||
public class CatchModCinema : ModCinema<CatchHitObject>
|
||||
{
|
||||
public override Score CreateReplayScore(IBeatmap beatmap) => new Score
|
||||
{
|
||||
ScoreInfo = new ScoreInfo { User = new User { Username = "osu!salad!" } },
|
||||
Replay = new CatchAutoGenerator(beatmap).Generate(),
|
||||
};
|
||||
}
|
||||
}
|
@ -10,10 +10,8 @@ using osu.Game.Replays;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Catch.Objects.Drawable;
|
||||
using osu.Game.Rulesets.Catch.Replays;
|
||||
using osu.Game.Rulesets.Catch.Scoring;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
|
||||
@ -32,8 +30,6 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
TimeRange.Value = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450);
|
||||
}
|
||||
|
||||
public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(Beatmap);
|
||||
|
||||
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay);
|
||||
|
||||
protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty, CreateDrawableRepresentation);
|
||||
|
@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
||||
prevNoteTimes.RemoveAt(0);
|
||||
prevNoteTimes.Add(newNoteTime);
|
||||
|
||||
density = (prevNoteTimes[prevNoteTimes.Count - 1] - prevNoteTimes[0]) / prevNoteTimes.Count;
|
||||
density = (prevNoteTimes[^1] - prevNoteTimes[0]) / prevNoteTimes.Count;
|
||||
}
|
||||
|
||||
private double lastTime;
|
||||
|
@ -25,6 +25,8 @@ using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Configuration;
|
||||
using osu.Game.Rulesets.Mania.Difficulty;
|
||||
using osu.Game.Rulesets.Mania.Edit;
|
||||
using osu.Game.Rulesets.Mania.Scoring;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania
|
||||
@ -32,7 +34,11 @@ namespace osu.Game.Rulesets.Mania
|
||||
public class ManiaRuleset : Ruleset
|
||||
{
|
||||
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => new DrawableManiaRuleset(this, beatmap, mods);
|
||||
|
||||
public override ScoreProcessor CreateScoreProcessor(IBeatmap beatmap) => new ManiaScoreProcessor(beatmap);
|
||||
|
||||
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap);
|
||||
|
||||
public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new ManiaPerformanceCalculator(this, beatmap, score);
|
||||
|
||||
public const string SHORT_NAME = "mania";
|
||||
@ -51,7 +57,9 @@ namespace osu.Game.Rulesets.Mania
|
||||
else if (mods.HasFlag(LegacyMods.SuddenDeath))
|
||||
yield return new ManiaModSuddenDeath();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.Autoplay))
|
||||
if (mods.HasFlag(LegacyMods.Cinema))
|
||||
yield return new ManiaModCinema();
|
||||
else if (mods.HasFlag(LegacyMods.Autoplay))
|
||||
yield return new ManiaModAutoplay();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.Easy))
|
||||
@ -148,7 +156,7 @@ namespace osu.Game.Rulesets.Mania
|
||||
case ModType.Automation:
|
||||
return new Mod[]
|
||||
{
|
||||
new MultiMod(new ManiaModAutoplay(), new ModCinema()),
|
||||
new MultiMod(new ManiaModAutoplay(), new ManiaModCinema()),
|
||||
};
|
||||
|
||||
case ModType.Fun:
|
||||
@ -158,7 +166,7 @@ namespace osu.Game.Rulesets.Mania
|
||||
};
|
||||
|
||||
default:
|
||||
return new Mod[] { };
|
||||
return Array.Empty<Mod>();
|
||||
}
|
||||
}
|
||||
|
||||
@ -268,7 +276,7 @@ namespace osu.Game.Rulesets.Mania
|
||||
return stage1Bindings.Concat(stage2Bindings);
|
||||
}
|
||||
|
||||
return new KeyBinding[0];
|
||||
return Array.Empty<KeyBinding>();
|
||||
}
|
||||
|
||||
public override string GetVariantName(int variant)
|
||||
|
22
osu.Game.Rulesets.Mania/Mods/ManiaModCinema.cs
Normal file
@ -0,0 +1,22 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.Replays;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Mods
|
||||
{
|
||||
public class ManiaModCinema : ModCinema<ManiaHitObject>
|
||||
{
|
||||
public override Score CreateReplayScore(IBeatmap beatmap) => new Score
|
||||
{
|
||||
ScoreInfo = new ScoreInfo { User = new User { Username = "osu!topus!" } },
|
||||
Replay = new ManiaAutoGenerator((ManiaBeatmap)beatmap).Generate(),
|
||||
};
|
||||
}
|
||||
}
|
@ -14,11 +14,9 @@ using osu.Game.Rulesets.Mania.Configuration;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Mania.Replays;
|
||||
using osu.Game.Rulesets.Mania.Scoring;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osuTK;
|
||||
@ -67,8 +65,6 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
|
||||
protected override Playfield CreatePlayfield() => new ManiaPlayfield(Beatmap.Stages);
|
||||
|
||||
public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(Beatmap);
|
||||
|
||||
public override int Variant => (int)(Beatmap.Stages.Count == 1 ? PlayfieldType.Single : PlayfieldType.Dual) + Beatmap.TotalColumns;
|
||||
|
||||
protected override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo, Variant);
|
||||
|
After Width: | Height: | Size: 4.4 KiB |
BIN
osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-0.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-1.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-2.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-3.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-4.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-5.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-6.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-7.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-8.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-9.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit0.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit100.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit300.png
Normal file
After Width: | Height: | Size: 33 KiB |
BIN
osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit50.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hitcircle.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 6.9 KiB |
2
osu.Game.Rulesets.Osu.Tests/Resources/old-skin/skin.ini
Normal file
@ -0,0 +1,2 @@
|
||||
[General]
|
||||
Version: 1.0
|
@ -19,9 +19,10 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
private Skin metricsSkin;
|
||||
private Skin defaultSkin;
|
||||
private Skin specialSkin;
|
||||
private Skin oldSkin;
|
||||
|
||||
protected SkinnableTestScene()
|
||||
: base(2, 2)
|
||||
: base(2, 3)
|
||||
{
|
||||
}
|
||||
|
||||
@ -33,6 +34,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
metricsSkin = new TestLegacySkin(new SkinInfo(), new NamespacedResourceStore<byte[]>(dllStore, "Resources/metrics_skin"), audio, true);
|
||||
defaultSkin = skinManager.GetSkin(DefaultLegacySkin.Info);
|
||||
specialSkin = new TestLegacySkin(new SkinInfo(), new NamespacedResourceStore<byte[]>(dllStore, "Resources/special_skin"), audio, true);
|
||||
oldSkin = new TestLegacySkin(new SkinInfo(), new NamespacedResourceStore<byte[]>(dllStore, "Resources/old_skin"), audio, true);
|
||||
}
|
||||
|
||||
public void SetContents(Func<Drawable> creationFunction)
|
||||
@ -41,6 +43,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
Cell(1).Child = createProvider(metricsSkin, creationFunction);
|
||||
Cell(2).Child = createProvider(defaultSkin, creationFunction);
|
||||
Cell(3).Child = createProvider(specialSkin, creationFunction);
|
||||
Cell(4).Child = createProvider(oldSkin, creationFunction);
|
||||
}
|
||||
|
||||
private Drawable createProvider(Skin skin, Func<Drawable> creationFunction)
|
||||
|
@ -286,11 +286,11 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
|
||||
private bool assertGreatJudge() => judgementResults.Last().Type == HitResult.Great;
|
||||
|
||||
private bool assertHeadMissTailTracked() => judgementResults[judgementResults.Count - 2].Type == HitResult.Great && judgementResults.First().Type == HitResult.Miss;
|
||||
private bool assertHeadMissTailTracked() => judgementResults[^2].Type == HitResult.Great && judgementResults.First().Type == HitResult.Miss;
|
||||
|
||||
private bool assertMidSliderJudgements() => judgementResults[judgementResults.Count - 2].Type == HitResult.Great;
|
||||
private bool assertMidSliderJudgements() => judgementResults[^2].Type == HitResult.Great;
|
||||
|
||||
private bool assertMidSliderJudgementFail() => judgementResults[judgementResults.Count - 2].Type == HitResult.Miss;
|
||||
private bool assertMidSliderJudgementFail() => judgementResults[^2].Type == HitResult.Miss;
|
||||
|
||||
private ScoreAccessibleReplayPlayer currentPlayer;
|
||||
|
||||
|
@ -70,6 +70,21 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
AddAssert("is rotation absolute almost same", () => Precision.AlmostEquals(drawableSpinner.Disc.RotationAbsolute, estimatedRotation, 100));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSpinPerMinuteOnRewind()
|
||||
{
|
||||
double estimatedSpm = 0;
|
||||
|
||||
addSeekStep(2500);
|
||||
AddStep("retrieve spm", () => estimatedSpm = drawableSpinner.SpmCounter.SpinsPerMinute);
|
||||
|
||||
addSeekStep(5000);
|
||||
AddAssert("spm still valid", () => Precision.AlmostEquals(drawableSpinner.SpmCounter.SpinsPerMinute, estimatedSpm, 1.0));
|
||||
|
||||
addSeekStep(2500);
|
||||
AddAssert("spm still valid", () => Precision.AlmostEquals(drawableSpinner.SpmCounter.SpinsPerMinute, estimatedSpm, 1.0));
|
||||
}
|
||||
|
||||
private void addSeekStep(double time)
|
||||
{
|
||||
AddStep($"seek to {time}", () => track.Seek(time));
|
||||
@ -84,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
new Spinner
|
||||
{
|
||||
Position = new Vector2(256, 192),
|
||||
EndTime = 5000,
|
||||
EndTime = 6000,
|
||||
},
|
||||
// placeholder object to avoid hitting the results screen
|
||||
new HitObject
|
||||
|
@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
protected override bool OnDoubleClick(DoubleClickEvent e)
|
||||
{
|
||||
// Todo: This should all not occur on double click, but rather if the previous control point is hovered.
|
||||
segmentStart = HitObject.Path.ControlPoints[HitObject.Path.ControlPoints.Count - 1];
|
||||
segmentStart = HitObject.Path.ControlPoints[^1];
|
||||
segmentStart.Type.Value = PathType.Linear;
|
||||
|
||||
currentSegmentLength = 1;
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using JetBrains.Annotations;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Screens.Edit.Compose.Components;
|
||||
|
||||
@ -10,7 +11,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
public class OsuDistanceSnapGrid : CircularDistanceSnapGrid
|
||||
{
|
||||
public OsuDistanceSnapGrid(OsuHitObject hitObject, [CanBeNull] OsuHitObject nextHitObject = null)
|
||||
: base(hitObject.StackedPosition, hitObject.StartTime, nextHitObject?.StartTime)
|
||||
: base(hitObject.StackedEndPosition, hitObject.GetEndTime(), nextHitObject?.StartTime)
|
||||
{
|
||||
Masking = true;
|
||||
}
|
||||
|
@ -92,7 +92,24 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
return null;
|
||||
|
||||
OsuHitObject sourceObject = EditorBeatmap.HitObjects[sourceIndex];
|
||||
OsuHitObject targetObject = sourceIndex + targetOffset < EditorBeatmap.HitObjects.Count ? EditorBeatmap.HitObjects[sourceIndex + targetOffset] : null;
|
||||
|
||||
int targetIndex = sourceIndex + targetOffset;
|
||||
OsuHitObject targetObject = null;
|
||||
|
||||
// Keep advancing the target object while its start time falls before the end time of the source object
|
||||
while (true)
|
||||
{
|
||||
if (targetIndex >= EditorBeatmap.HitObjects.Count)
|
||||
break;
|
||||
|
||||
if (EditorBeatmap.HitObjects[targetIndex].StartTime >= sourceObject.GetEndTime())
|
||||
{
|
||||
targetObject = EditorBeatmap.HitObjects[targetIndex];
|
||||
break;
|
||||
}
|
||||
|
||||
targetIndex++;
|
||||
}
|
||||
|
||||
return new OsuDistanceSnapGrid(sourceObject, targetObject);
|
||||
}
|
||||
|
25
osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs
Normal file
@ -0,0 +1,25 @@
|
||||
// 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.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Replays;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public class OsuModCinema : ModCinema<OsuHitObject>
|
||||
{
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).Append(typeof(OsuModSpunOut)).ToArray();
|
||||
|
||||
public override Score CreateReplayScore(IBeatmap beatmap) => new Score
|
||||
{
|
||||
ScoreInfo = new ScoreInfo { User = new User { Username = "Autoplay" } },
|
||||
Replay = new OsuAutoGenerator(beatmap).Generate()
|
||||
};
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
@ -54,13 +55,8 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
break;
|
||||
|
||||
case DrawableSlider slider:
|
||||
slider.AccentColour.BindValueChanged(_ =>
|
||||
{
|
||||
//will trigger on skin change.
|
||||
slider.Body.AccentColour = slider.AccentColour.Value.Opacity(0);
|
||||
slider.Body.BorderColour = slider.AccentColour.Value;
|
||||
}, true);
|
||||
|
||||
slider.Body.OnSkinChanged += () => applySliderState(slider);
|
||||
applySliderState(slider);
|
||||
break;
|
||||
|
||||
case DrawableSpinner spinner:
|
||||
@ -69,5 +65,11 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void applySliderState(DrawableSlider slider)
|
||||
{
|
||||
((PlaySliderBody)slider.Body.Drawable).AccentColour = slider.AccentColour.Value.Opacity(0);
|
||||
((PlaySliderBody)slider.Body.Drawable).BorderColour = slider.AccentColour.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.MathUtils;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osuTK;
|
||||
using osu.Game.Skinning;
|
||||
@ -98,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
public void UpdateSnakingPosition(Vector2 start, Vector2 end)
|
||||
{
|
||||
bool isRepeatAtEnd = repeatPoint.RepeatIndex % 2 == 0;
|
||||
List<Vector2> curve = drawableSlider.Body.CurrentCurve;
|
||||
List<Vector2> curve = ((PlaySliderBody)drawableSlider.Body.Drawable).CurrentCurve;
|
||||
|
||||
Position = isRepeatAtEnd ? end : start;
|
||||
|
||||
|
@ -11,7 +11,6 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.Configuration;
|
||||
using osu.Game.Rulesets.Osu.Skinning;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osuTK.Graphics;
|
||||
@ -24,8 +23,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
public DrawableSliderHead HeadCircle => headContainer.Child;
|
||||
public DrawableSliderTail TailCircle => tailContainer.Child;
|
||||
|
||||
public readonly SnakingSliderBody Body;
|
||||
public readonly SliderBall Ball;
|
||||
public readonly SkinnableDrawable Body;
|
||||
|
||||
private PlaySliderBody sliderBody => Body.Drawable as PlaySliderBody;
|
||||
|
||||
private readonly Container<DrawableSliderHead> headContainer;
|
||||
private readonly Container<DrawableSliderTail> tailContainer;
|
||||
@ -37,10 +38,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
private readonly IBindable<Vector2> positionBindable = new Bindable<Vector2>();
|
||||
private readonly IBindable<int> stackHeightBindable = new Bindable<int>();
|
||||
private readonly IBindable<float> scaleBindable = new Bindable<float>();
|
||||
private readonly IBindable<int> pathVersion = new Bindable<int>();
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
private OsuRulesetConfigManager config { get; set; }
|
||||
|
||||
public DrawableSlider(Slider s)
|
||||
: base(s)
|
||||
@ -51,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
Body = new SnakingSliderBody(s),
|
||||
Body = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderBody), _ => new DefaultSliderBody(), confineMode: ConfineMode.NoScaling),
|
||||
tickContainer = new Container<DrawableSliderTick> { RelativeSizeAxes = Axes.Both },
|
||||
repeatContainer = new Container<DrawableRepeatPoint> { RelativeSizeAxes = Axes.Both },
|
||||
Ball = new SliderBall(s, this)
|
||||
@ -70,28 +67,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
config?.BindWith(OsuRulesetSetting.SnakingInSliders, Body.SnakingIn);
|
||||
config?.BindWith(OsuRulesetSetting.SnakingOutSliders, Body.SnakingOut);
|
||||
|
||||
positionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
|
||||
stackHeightBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
|
||||
scaleBindable.BindValueChanged(scale =>
|
||||
{
|
||||
updatePathRadius();
|
||||
Ball.Scale = new Vector2(scale.NewValue);
|
||||
});
|
||||
scaleBindable.BindValueChanged(scale => Ball.Scale = new Vector2(scale.NewValue));
|
||||
|
||||
positionBindable.BindTo(HitObject.PositionBindable);
|
||||
stackHeightBindable.BindTo(HitObject.StackHeightBindable);
|
||||
scaleBindable.BindTo(HitObject.ScaleBindable);
|
||||
pathVersion.BindTo(slider.Path.Version);
|
||||
|
||||
pathVersion.BindValueChanged(_ => Body.Refresh());
|
||||
|
||||
AccentColour.BindValueChanged(colour =>
|
||||
{
|
||||
Body.AccentColour = colour.NewValue;
|
||||
|
||||
foreach (var drawableHitObject in NestedHitObjects)
|
||||
drawableHitObject.AccentColour.Value = colour.NewValue;
|
||||
}, true);
|
||||
@ -169,16 +154,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
double completionProgress = Math.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1);
|
||||
|
||||
Ball.UpdateProgress(completionProgress);
|
||||
Body.UpdateProgress(completionProgress);
|
||||
sliderBody?.UpdateProgress(completionProgress);
|
||||
|
||||
foreach (DrawableHitObject hitObject in NestedHitObjects)
|
||||
{
|
||||
if (hitObject is ITrackSnaking s) s.UpdateSnakingPosition(slider.Path.PositionAt(Body.SnakedStart ?? 0), slider.Path.PositionAt(Body.SnakedEnd ?? 0));
|
||||
if (hitObject is ITrackSnaking s) s.UpdateSnakingPosition(slider.Path.PositionAt(sliderBody?.SnakedStart ?? 0), slider.Path.PositionAt(sliderBody?.SnakedEnd ?? 0));
|
||||
if (hitObject is IRequireTracking t) t.Tracking = Ball.Tracking;
|
||||
}
|
||||
|
||||
Size = Body.Size;
|
||||
OriginPosition = Body.PathOffset;
|
||||
Size = sliderBody?.Size ?? Vector2.Zero;
|
||||
OriginPosition = sliderBody?.PathOffset ?? Vector2.Zero;
|
||||
|
||||
if (DrawSize != Vector2.Zero)
|
||||
{
|
||||
@ -192,28 +177,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
public override void OnKilled()
|
||||
{
|
||||
base.OnKilled();
|
||||
Body.RecyclePath();
|
||||
sliderBody?.RecyclePath();
|
||||
}
|
||||
|
||||
private float sliderPathRadius;
|
||||
|
||||
protected override void ApplySkin(ISkinSource skin, bool allowFallback)
|
||||
{
|
||||
base.ApplySkin(skin, allowFallback);
|
||||
|
||||
Body.BorderSize = skin.GetConfig<OsuSkinConfiguration, float>(OsuSkinConfiguration.SliderBorderSize)?.Value ?? SliderBody.DEFAULT_BORDER_SIZE;
|
||||
sliderPathRadius = skin.GetConfig<OsuSkinConfiguration, float>(OsuSkinConfiguration.SliderPathRadius)?.Value ?? OsuHitObject.OBJECT_RADIUS;
|
||||
updatePathRadius();
|
||||
|
||||
Body.AccentColour = skin.GetConfig<OsuSkinColour, Color4>(OsuSkinColour.SliderTrackOverride)?.Value ?? AccentColour.Value;
|
||||
Body.BorderColour = skin.GetConfig<OsuSkinColour, Color4>(OsuSkinColour.SliderBorder)?.Value ?? Color4.White;
|
||||
|
||||
bool allowBallTint = skin.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.AllowSliderBallTint)?.Value ?? false;
|
||||
Ball.Colour = allowBallTint ? AccentColour.Value : Color4.White;
|
||||
}
|
||||
|
||||
private void updatePathRadius() => Body.PathRadius = slider.Scale * sliderPathRadius;
|
||||
|
||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||
{
|
||||
if (userTriggered || Time.Current < slider.EndTime)
|
||||
@ -264,6 +238,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
public Drawable ProxiedLayer => HeadCircle.ProxiedLayer;
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Body.ReceivePositionalInputAt(screenSpacePos);
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => sliderBody?.ReceivePositionalInputAt(screenSpacePos) ?? base.ReceivePositionalInputAt(screenSpacePos);
|
||||
|
||||
private class DefaultSliderBody : PlaySliderBody
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
public readonly SpinnerDisc Disc;
|
||||
public readonly SpinnerTicks Ticks;
|
||||
private readonly SpinnerSpmCounter spmCounter;
|
||||
public readonly SpinnerSpmCounter SpmCounter;
|
||||
|
||||
private readonly Container mainContainer;
|
||||
|
||||
@ -110,7 +110,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
},
|
||||
}
|
||||
},
|
||||
spmCounter = new SpinnerSpmCounter
|
||||
SpmCounter = new SpinnerSpmCounter
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
@ -177,8 +177,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
protected override void Update()
|
||||
{
|
||||
Disc.Tracking = OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false;
|
||||
if (!spmCounter.IsPresent && Disc.Tracking)
|
||||
spmCounter.FadeIn(HitObject.TimeFadeIn);
|
||||
if (!SpmCounter.IsPresent && Disc.Tracking)
|
||||
SpmCounter.FadeIn(HitObject.TimeFadeIn);
|
||||
|
||||
base.Update();
|
||||
}
|
||||
@ -189,7 +189,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
circle.Rotation = Disc.Rotation;
|
||||
Ticks.Rotation = Disc.Rotation;
|
||||
spmCounter.SetRotation(Disc.RotationAbsolute);
|
||||
SpmCounter.SetRotation(Disc.RotationAbsolute);
|
||||
|
||||
float relativeCircleScale = Spinner.Scale * circle.DrawHeight / mainContainer.DrawHeight;
|
||||
Disc.ScaleTo(relativeCircleScale + (1 - relativeCircleScale) * Progress, 200, Easing.OutQuint);
|
||||
|
@ -0,0 +1,70 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics.Lines;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
{
|
||||
public abstract class DrawableSliderPath : SmoothPath
|
||||
{
|
||||
protected const float BORDER_PORTION = 0.128f;
|
||||
protected const float GRADIENT_PORTION = 1 - BORDER_PORTION;
|
||||
|
||||
private const float border_max_size = 8f;
|
||||
private const float border_min_size = 0f;
|
||||
|
||||
private Color4 borderColour = Color4.White;
|
||||
|
||||
public Color4 BorderColour
|
||||
{
|
||||
get => borderColour;
|
||||
set
|
||||
{
|
||||
if (borderColour == value)
|
||||
return;
|
||||
|
||||
borderColour = value;
|
||||
|
||||
InvalidateTexture();
|
||||
}
|
||||
}
|
||||
|
||||
private Color4 accentColour = Color4.White;
|
||||
|
||||
public Color4 AccentColour
|
||||
{
|
||||
get => accentColour;
|
||||
set
|
||||
{
|
||||
if (accentColour == value)
|
||||
return;
|
||||
|
||||
accentColour = value;
|
||||
|
||||
InvalidateTexture();
|
||||
}
|
||||
}
|
||||
|
||||
private float borderSize = 1;
|
||||
|
||||
public float BorderSize
|
||||
{
|
||||
get => borderSize;
|
||||
set
|
||||
{
|
||||
if (borderSize == value)
|
||||
return;
|
||||
|
||||
if (value < border_min_size || value > border_max_size)
|
||||
return;
|
||||
|
||||
borderSize = value;
|
||||
|
||||
InvalidateTexture();
|
||||
}
|
||||
}
|
||||
|
||||
protected float CalculatedBorderPortion => BorderSize * BORDER_PORTION;
|
||||
}
|
||||
}
|
@ -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 osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Configuration;
|
||||
using osu.Game.Rulesets.Osu.Skinning;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
{
|
||||
public abstract class PlaySliderBody : SnakingSliderBody
|
||||
{
|
||||
private IBindable<float> scaleBindable;
|
||||
private IBindable<int> pathVersion;
|
||||
private IBindable<Color4> accentColour;
|
||||
|
||||
[Resolved]
|
||||
private DrawableHitObject drawableObject { get; set; }
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
private OsuRulesetConfigManager config { get; set; }
|
||||
|
||||
private Slider slider;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ISkinSource skin)
|
||||
{
|
||||
slider = (Slider)drawableObject.HitObject;
|
||||
|
||||
scaleBindable = slider.ScaleBindable.GetBoundCopy();
|
||||
scaleBindable.BindValueChanged(scale => PathRadius = OsuHitObject.OBJECT_RADIUS * scale.NewValue, true);
|
||||
|
||||
pathVersion = slider.Path.Version.GetBoundCopy();
|
||||
pathVersion.BindValueChanged(_ => Refresh());
|
||||
|
||||
accentColour = drawableObject.AccentColour.GetBoundCopy();
|
||||
accentColour.BindValueChanged(accent => updateAccentColour(skin, accent.NewValue), true);
|
||||
|
||||
config?.BindWith(OsuRulesetSetting.SnakingInSliders, SnakingIn);
|
||||
config?.BindWith(OsuRulesetSetting.SnakingOutSliders, SnakingOut);
|
||||
|
||||
BorderSize = skin.GetConfig<OsuSkinConfiguration, float>(OsuSkinConfiguration.SliderBorderSize)?.Value ?? 1;
|
||||
BorderColour = skin.GetConfig<OsuSkinColour, Color4>(OsuSkinColour.SliderBorder)?.Value ?? Color4.White;
|
||||
}
|
||||
|
||||
private void updateAccentColour(ISkinSource skin, Color4 defaultAccentColour)
|
||||
=> AccentColour = skin.GetConfig<OsuSkinColour, Color4>(OsuSkinColour.SliderTrackOverride)?.Value ?? defaultAccentColour;
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Lines;
|
||||
using osuTK;
|
||||
@ -12,9 +13,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
{
|
||||
public abstract class SliderBody : CompositeDrawable
|
||||
{
|
||||
public const float DEFAULT_BORDER_SIZE = 1;
|
||||
|
||||
private SliderPath path;
|
||||
private DrawableSliderPath path;
|
||||
|
||||
protected Path Path => path;
|
||||
|
||||
@ -80,19 +79,19 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialises a new <see cref="SliderPath"/>, releasing all resources retained by the old one.
|
||||
/// Initialises a new <see cref="DrawableSliderPath"/>, releasing all resources retained by the old one.
|
||||
/// </summary>
|
||||
public virtual void RecyclePath()
|
||||
{
|
||||
InternalChild = path = new SliderPath
|
||||
InternalChild = path = CreateSliderPath().With(p =>
|
||||
{
|
||||
Position = path?.Position ?? Vector2.Zero,
|
||||
PathRadius = path?.PathRadius ?? 10,
|
||||
AccentColour = path?.AccentColour ?? Color4.White,
|
||||
BorderColour = path?.BorderColour ?? Color4.White,
|
||||
BorderSize = path?.BorderSize ?? DEFAULT_BORDER_SIZE,
|
||||
Vertices = path?.Vertices ?? Array.Empty<Vector2>()
|
||||
};
|
||||
p.Position = path?.Position ?? Vector2.Zero;
|
||||
p.PathRadius = path?.PathRadius ?? 10;
|
||||
p.AccentColour = path?.AccentColour ?? Color4.White;
|
||||
p.BorderColour = path?.BorderColour ?? Color4.White;
|
||||
p.BorderSize = path?.BorderSize ?? 1;
|
||||
p.Vertices = path?.Vertices ?? Array.Empty<Vector2>();
|
||||
});
|
||||
}
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => path.ReceivePositionalInputAt(screenSpacePos);
|
||||
@ -103,77 +102,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
/// <param name="vertices">The vertices</param>
|
||||
protected void SetVertices(IReadOnlyList<Vector2> vertices) => path.Vertices = vertices;
|
||||
|
||||
private class SliderPath : SmoothPath
|
||||
protected virtual DrawableSliderPath CreateSliderPath() => new DefaultDrawableSliderPath();
|
||||
|
||||
private class DefaultDrawableSliderPath : DrawableSliderPath
|
||||
{
|
||||
private const float border_max_size = 8f;
|
||||
private const float border_min_size = 0f;
|
||||
|
||||
private const float border_portion = 0.128f;
|
||||
private const float gradient_portion = 1 - border_portion;
|
||||
|
||||
private const float opacity_at_centre = 0.3f;
|
||||
private const float opacity_at_edge = 0.8f;
|
||||
|
||||
private Color4 borderColour = Color4.White;
|
||||
|
||||
public Color4 BorderColour
|
||||
{
|
||||
get => borderColour;
|
||||
set
|
||||
{
|
||||
if (borderColour == value)
|
||||
return;
|
||||
|
||||
borderColour = value;
|
||||
|
||||
InvalidateTexture();
|
||||
}
|
||||
}
|
||||
|
||||
private Color4 accentColour = Color4.White;
|
||||
|
||||
public Color4 AccentColour
|
||||
{
|
||||
get => accentColour;
|
||||
set
|
||||
{
|
||||
if (accentColour == value)
|
||||
return;
|
||||
|
||||
accentColour = value;
|
||||
|
||||
InvalidateTexture();
|
||||
}
|
||||
}
|
||||
|
||||
private float borderSize = DEFAULT_BORDER_SIZE;
|
||||
|
||||
public float BorderSize
|
||||
{
|
||||
get => borderSize;
|
||||
set
|
||||
{
|
||||
if (borderSize == value)
|
||||
return;
|
||||
|
||||
if (value < border_min_size || value > border_max_size)
|
||||
return;
|
||||
|
||||
borderSize = value;
|
||||
|
||||
InvalidateTexture();
|
||||
}
|
||||
}
|
||||
|
||||
private float calculatedBorderPortion => BorderSize * border_portion;
|
||||
|
||||
protected override Color4 ColourAt(float position)
|
||||
{
|
||||
if (calculatedBorderPortion != 0f && position <= calculatedBorderPortion)
|
||||
if (CalculatedBorderPortion != 0f && position <= CalculatedBorderPortion)
|
||||
return BorderColour;
|
||||
|
||||
position -= calculatedBorderPortion;
|
||||
return new Color4(AccentColour.R, AccentColour.G, AccentColour.B, (opacity_at_edge - (opacity_at_edge - opacity_at_centre) * position / gradient_portion) * AccentColour.A);
|
||||
position -= CalculatedBorderPortion;
|
||||
return new Color4(AccentColour.R, AccentColour.G, AccentColour.B, (opacity_at_edge - (opacity_at_edge - opacity_at_centre) * position / GRADIENT_PORTION) * AccentColour.A);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osuTK;
|
||||
|
||||
@ -50,16 +51,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
/// </summary>
|
||||
private Vector2 snakedPathOffset;
|
||||
|
||||
private readonly Slider slider;
|
||||
|
||||
public SnakingSliderBody(Slider slider)
|
||||
{
|
||||
this.slider = slider;
|
||||
}
|
||||
private Slider slider;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
private void load(DrawableHitObject drawableObject)
|
||||
{
|
||||
slider = (Slider)drawableObject.HitObject;
|
||||
|
||||
Refresh();
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.MathUtils;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
|
||||
@ -62,6 +63,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
|
||||
public void SetRotation(float currentRotation)
|
||||
{
|
||||
// Never calculate SPM by same time of record to avoid 0 / 0 = NaN or X / 0 = Infinity result.
|
||||
if (Precision.AlmostEquals(0, Time.Elapsed))
|
||||
return;
|
||||
|
||||
// If we've gone back in time, it's fine to work with a fresh set of records for now
|
||||
if (records.Count > 0 && Time.Current < records.Last().Time)
|
||||
records.Clear();
|
||||
@ -71,6 +76,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
var record = records.Peek();
|
||||
while (records.Count > 0 && Time.Current - records.Peek().Time > spm_count_duration)
|
||||
record = records.Dequeue();
|
||||
|
||||
SpinsPerMinute = (currentRotation - record.Rotation) / (Time.Current - record.Time) * 1000 * 60 / 360;
|
||||
}
|
||||
|
||||
|
@ -23,16 +23,23 @@ using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||
using osu.Game.Rulesets.Osu.Configuration;
|
||||
using osu.Game.Rulesets.Osu.Difficulty;
|
||||
using osu.Game.Rulesets.Osu.Scoring;
|
||||
using osu.Game.Rulesets.Osu.Skinning;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Skinning;
|
||||
using System;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu
|
||||
{
|
||||
public class OsuRuleset : Ruleset
|
||||
{
|
||||
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => new DrawableOsuRuleset(this, beatmap, mods);
|
||||
|
||||
public override ScoreProcessor CreateScoreProcessor(IBeatmap beatmap) => new OsuScoreProcessor(beatmap);
|
||||
|
||||
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap);
|
||||
|
||||
public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new OsuBeatmapProcessor(beatmap);
|
||||
|
||||
public const string SHORT_NAME = "osu";
|
||||
@ -60,7 +67,9 @@ namespace osu.Game.Rulesets.Osu
|
||||
if (mods.HasFlag(LegacyMods.Autopilot))
|
||||
yield return new OsuModAutopilot();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.Autoplay))
|
||||
if (mods.HasFlag(LegacyMods.Cinema))
|
||||
yield return new OsuModCinema();
|
||||
else if (mods.HasFlag(LegacyMods.Autoplay))
|
||||
yield return new OsuModAutoplay();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.Easy))
|
||||
@ -126,7 +135,7 @@ namespace osu.Game.Rulesets.Osu
|
||||
case ModType.Automation:
|
||||
return new Mod[]
|
||||
{
|
||||
new MultiMod(new OsuModAutoplay(), new ModCinema()),
|
||||
new MultiMod(new OsuModAutoplay(), new OsuModCinema()),
|
||||
new OsuModRelax(),
|
||||
new OsuModAutopilot(),
|
||||
};
|
||||
@ -149,7 +158,7 @@ namespace osu.Game.Rulesets.Osu
|
||||
};
|
||||
|
||||
default:
|
||||
return new Mod[] { };
|
||||
return Array.Empty<Mod>();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,7 @@ namespace osu.Game.Rulesets.Osu
|
||||
ReverseArrow,
|
||||
HitCircleText,
|
||||
SliderFollowCircle,
|
||||
SliderBall
|
||||
SliderBall,
|
||||
SliderBody,
|
||||
}
|
||||
}
|
||||
|
@ -156,9 +156,9 @@ namespace osu.Game.Rulesets.Osu.Replays
|
||||
// TODO: Shouldn't the spinner always spin in the same direction?
|
||||
if (h is Spinner)
|
||||
{
|
||||
calcSpinnerStartPosAndDirection(((OsuReplayFrame)Frames[Frames.Count - 1]).Position, out startPosition, out spinnerDirection);
|
||||
calcSpinnerStartPosAndDirection(((OsuReplayFrame)Frames[^1]).Position, out startPosition, out spinnerDirection);
|
||||
|
||||
Vector2 spinCentreOffset = SPINNER_CENTRE - ((OsuReplayFrame)Frames[Frames.Count - 1]).Position;
|
||||
Vector2 spinCentreOffset = SPINNER_CENTRE - ((OsuReplayFrame)Frames[^1]).Position;
|
||||
|
||||
if (spinCentreOffset.Length > SPIN_RADIUS)
|
||||
{
|
||||
@ -230,7 +230,7 @@ namespace osu.Game.Rulesets.Osu.Replays
|
||||
|
||||
private void moveToHitObject(OsuHitObject h, Vector2 targetPos, Easing easing)
|
||||
{
|
||||
OsuReplayFrame lastFrame = (OsuReplayFrame)Frames[Frames.Count - 1];
|
||||
OsuReplayFrame lastFrame = (OsuReplayFrame)Frames[^1];
|
||||
|
||||
// Wait until Auto could "see and react" to the next note.
|
||||
double waitTime = h.StartTime - Math.Max(0.0, h.TimePreempt - reactionTime);
|
||||
@ -363,7 +363,7 @@ namespace osu.Game.Rulesets.Osu.Replays
|
||||
}
|
||||
|
||||
// We only want to let go of our button if we are at the end of the current replay. Otherwise something is still going on after us so we need to keep the button pressed!
|
||||
if (Frames[Frames.Count - 1].Time <= endFrame.Time)
|
||||
if (Frames[^1].Time <= endFrame.Time)
|
||||
AddFrameToReplay(endFrame);
|
||||
}
|
||||
|
||||
|
56
osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs
Normal file
@ -0,0 +1,56 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.MathUtils;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Skinning
|
||||
{
|
||||
public class LegacySliderBody : PlaySliderBody
|
||||
{
|
||||
protected override DrawableSliderPath CreateSliderPath() => new LegacyDrawableSliderPath();
|
||||
|
||||
private class LegacyDrawableSliderPath : DrawableSliderPath
|
||||
{
|
||||
private const float shadow_portion = 1 - (OsuLegacySkinTransformer.LEGACY_CIRCLE_RADIUS / OsuHitObject.OBJECT_RADIUS);
|
||||
|
||||
public new Color4 AccentColour => new Color4(base.AccentColour.R, base.AccentColour.G, base.AccentColour.B, base.AccentColour.A * 0.70f);
|
||||
|
||||
protected override Color4 ColourAt(float position)
|
||||
{
|
||||
float realBorderPortion = shadow_portion + CalculatedBorderPortion;
|
||||
float realGradientPortion = 1 - realBorderPortion;
|
||||
|
||||
if (position <= shadow_portion)
|
||||
return new Color4(0f, 0f, 0f, 0.25f * position / shadow_portion);
|
||||
|
||||
if (position <= realBorderPortion)
|
||||
return BorderColour;
|
||||
|
||||
position -= realBorderPortion;
|
||||
|
||||
Color4 outerColour = AccentColour.Darken(0.1f);
|
||||
Color4 innerColour = lighten(AccentColour, 0.5f);
|
||||
|
||||
return Interpolation.ValueAt(position / realGradientPortion, outerColour, innerColour, 0, 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lightens a colour in a way more friendly to dark or strong colours.
|
||||
/// </summary>
|
||||
private static Color4 lighten(Color4 color, float amount)
|
||||
{
|
||||
amount *= 0.5f;
|
||||
return new Color4(
|
||||
Math.Min(1, color.R * (1 + 0.5f * amount) + 1 * amount),
|
||||
Math.Min(1, color.G * (1 + 0.5f * amount) + 1 * amount),
|
||||
Math.Min(1, color.B * (1 + 0.5f * amount) + 1 * amount),
|
||||
color.A);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
/// Their hittable area is 128px, but the actual circle portion is 118px.
|
||||
/// We must account for some gameplay elements such as slider bodies, where this padding is not present.
|
||||
/// </summary>
|
||||
private const float legacy_circle_radius = 64 - 5;
|
||||
public const float LEGACY_CIRCLE_RADIUS = 64 - 5;
|
||||
|
||||
public OsuLegacySkinTransformer(ISkinSource source)
|
||||
{
|
||||
@ -73,6 +73,12 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.SliderBody:
|
||||
if (hasHitCircle.Value)
|
||||
return new LegacySliderBody();
|
||||
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.HitCircle:
|
||||
if (hasHitCircle.Value)
|
||||
return new LegacyMainCirclePiece();
|
||||
@ -124,7 +130,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
{
|
||||
case OsuSkinConfiguration.SliderPathRadius:
|
||||
if (hasHitCircle.Value)
|
||||
return SkinUtils.As<TValue>(new BindableFloat(legacy_circle_radius));
|
||||
return SkinUtils.As<TValue>(new BindableFloat(LEGACY_CIRCLE_RADIUS));
|
||||
|
||||
break;
|
||||
}
|
||||
|
@ -227,6 +227,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
shader.Bind();
|
||||
shader.GetUniform<float>("g_FadeClock").UpdateValue(ref time);
|
||||
|
||||
texture.TextureGL.Bind();
|
||||
|
||||
RectangleF textureRect = texture.GetTextureRect();
|
||||
|
||||
foreach (var part in parts)
|
||||
|
@ -14,8 +14,6 @@ using osu.Game.Rulesets.Osu.Configuration;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Replays;
|
||||
using osu.Game.Rulesets.Osu.Scoring;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Play;
|
||||
using osuTK;
|
||||
@ -33,8 +31,6 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; // always show the gameplay cursor
|
||||
|
||||
public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor(Beatmap);
|
||||
|
||||
protected override Playfield CreatePlayfield() => new OsuPlayfield();
|
||||
|
||||
protected override PassThroughInputManager CreateInputManager() => new OsuInputManager(Ruleset.RulesetInfo);
|
||||
|
21
osu.Game.Rulesets.Taiko/Mods/TaikoModCinema.cs
Normal file
@ -0,0 +1,21 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Rulesets.Taiko.Replays;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Mods
|
||||
{
|
||||
public class TaikoModCinema : ModCinema<TaikoHitObject>
|
||||
{
|
||||
public override Score CreateReplayScore(IBeatmap beatmap) => new Score
|
||||
{
|
||||
ScoreInfo = new ScoreInfo { User = new User { Username = "mekkadosu!" } },
|
||||
Replay = new TaikoAutoGenerator(beatmap).Generate(),
|
||||
};
|
||||
}
|
||||
}
|
@ -105,19 +105,19 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class DrawableTaikoHitObject<TaikoHitType> : DrawableTaikoHitObject
|
||||
where TaikoHitType : TaikoHitObject
|
||||
public abstract class DrawableTaikoHitObject<TTaikoHit> : DrawableTaikoHitObject
|
||||
where TTaikoHit : TaikoHitObject
|
||||
{
|
||||
public override Vector2 OriginPosition => new Vector2(DrawHeight / 2);
|
||||
|
||||
public new TaikoHitType HitObject;
|
||||
public new TTaikoHit HitObject;
|
||||
|
||||
protected readonly Vector2 BaseSize;
|
||||
protected readonly TaikoPiece MainPiece;
|
||||
|
||||
private readonly Container<DrawableStrongNestedHit> strongHitContainer;
|
||||
|
||||
protected DrawableTaikoHitObject(TaikoHitType hitObject)
|
||||
protected DrawableTaikoHitObject(TTaikoHit hitObject)
|
||||
: base(hitObject)
|
||||
{
|
||||
HitObject = hitObject;
|
||||
|
@ -15,15 +15,21 @@ using osu.Game.Rulesets.Replays.Types;
|
||||
using osu.Game.Rulesets.Taiko.Replays;
|
||||
using osu.Game.Beatmaps.Legacy;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Taiko.Beatmaps;
|
||||
using osu.Game.Rulesets.Taiko.Difficulty;
|
||||
using osu.Game.Rulesets.Taiko.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
using System;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko
|
||||
{
|
||||
public class TaikoRuleset : Ruleset
|
||||
{
|
||||
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => new DrawableTaikoRuleset(this, beatmap, mods);
|
||||
|
||||
public override ScoreProcessor CreateScoreProcessor(IBeatmap beatmap) => new TaikoScoreProcessor(beatmap);
|
||||
|
||||
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(beatmap);
|
||||
|
||||
public const string SHORT_NAME = "taiko";
|
||||
@ -50,7 +56,9 @@ namespace osu.Game.Rulesets.Taiko
|
||||
else if (mods.HasFlag(LegacyMods.SuddenDeath))
|
||||
yield return new TaikoModSuddenDeath();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.Autoplay))
|
||||
if (mods.HasFlag(LegacyMods.Cinema))
|
||||
yield return new TaikoModCinema();
|
||||
else if (mods.HasFlag(LegacyMods.Autoplay))
|
||||
yield return new TaikoModAutoplay();
|
||||
|
||||
if (mods.HasFlag(LegacyMods.Easy))
|
||||
@ -100,7 +108,7 @@ namespace osu.Game.Rulesets.Taiko
|
||||
case ModType.Automation:
|
||||
return new Mod[]
|
||||
{
|
||||
new MultiMod(new TaikoModAutoplay(), new ModCinema()),
|
||||
new MultiMod(new TaikoModAutoplay(), new TaikoModCinema()),
|
||||
new TaikoModRelax(),
|
||||
};
|
||||
|
||||
@ -111,7 +119,7 @@ namespace osu.Game.Rulesets.Taiko
|
||||
};
|
||||
|
||||
default:
|
||||
return new Mod[] { };
|
||||
return Array.Empty<Mod>();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,10 +5,8 @@ using System.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Rulesets.Taiko.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Taiko.Scoring;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Rulesets.Taiko.Replays;
|
||||
using osu.Framework.Input;
|
||||
@ -40,8 +38,6 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
new BarLineGenerator<BarLine>(Beatmap).BarLines.ForEach(bar => Playfield.Add(bar.Major ? new DrawableBarLineMajor(bar) : new DrawableBarLine(bar)));
|
||||
}
|
||||
|
||||
public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(Beatmap);
|
||||
|
||||
public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new TaikoPlayfieldAdjustmentContainer();
|
||||
|
||||
protected override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo);
|
||||
|
@ -45,7 +45,7 @@ namespace osu.Game.Tests.NonVisual
|
||||
cpi.Add(1000, new DifficultyControlPoint()); // is redundant
|
||||
|
||||
Assert.That(cpi.Groups.Count, Is.EqualTo(0));
|
||||
Assert.That(cpi.TimingPoints.Count, Is.EqualTo(0));
|
||||
Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(0));
|
||||
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(0));
|
||||
|
||||
cpi.Add(1000, new DifficultyControlPoint { SpeedMultiplier = 2 }); // is not redundant
|
||||
@ -60,18 +60,18 @@ namespace osu.Game.Tests.NonVisual
|
||||
{
|
||||
var cpi = new ControlPointInfo();
|
||||
|
||||
cpi.Add(0, new SampleControlPoint()); // is redundant
|
||||
cpi.Add(0, new SampleControlPoint()); // is *not* redundant, special exception for first sample point
|
||||
cpi.Add(1000, new SampleControlPoint()); // is redundant
|
||||
|
||||
Assert.That(cpi.Groups.Count, Is.EqualTo(0));
|
||||
Assert.That(cpi.TimingPoints.Count, Is.EqualTo(0));
|
||||
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(0));
|
||||
|
||||
cpi.Add(1000, new SampleControlPoint { SampleVolume = 50 }); // is not redundant
|
||||
|
||||
Assert.That(cpi.Groups.Count, Is.EqualTo(1));
|
||||
Assert.That(cpi.SamplePoints.Count, Is.EqualTo(1));
|
||||
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(1));
|
||||
|
||||
cpi.Add(1000, new SampleControlPoint { SampleVolume = 50 }); // is not redundant
|
||||
|
||||
Assert.That(cpi.Groups.Count, Is.EqualTo(2));
|
||||
Assert.That(cpi.SamplePoints.Count, Is.EqualTo(2));
|
||||
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -83,7 +83,7 @@ namespace osu.Game.Tests.NonVisual
|
||||
cpi.Add(1000, new EffectControlPoint()); // is redundant
|
||||
|
||||
Assert.That(cpi.Groups.Count, Is.EqualTo(0));
|
||||
Assert.That(cpi.TimingPoints.Count, Is.EqualTo(0));
|
||||
Assert.That(cpi.EffectPoints.Count, Is.EqualTo(0));
|
||||
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(0));
|
||||
|
||||
cpi.Add(1000, new EffectControlPoint { KiaiMode = true }); // is not redundant
|
||||
|
@ -25,7 +25,7 @@ namespace osu.Game.Tests.NonVisual
|
||||
{
|
||||
Assert.AreEqual(0, stack.Count);
|
||||
|
||||
Assert.Throws<IndexOutOfRangeException>(() =>
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() =>
|
||||
{
|
||||
int unused = stack[0];
|
||||
});
|
||||
@ -55,7 +55,7 @@ namespace osu.Game.Tests.NonVisual
|
||||
// e.g. indices 3, 4, 5, 6 (out of range)
|
||||
for (int i = stack.Count; i < stack.Count + capacity; i++)
|
||||
{
|
||||
Assert.Throws<IndexOutOfRangeException>(() =>
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() =>
|
||||
{
|
||||
int unused = stack[i];
|
||||
});
|
||||
@ -80,7 +80,7 @@ namespace osu.Game.Tests.NonVisual
|
||||
// e.g. indices 3, 4, 5, 6 (out of range)
|
||||
for (int i = stack.Count; i < stack.Count + capacity; i++)
|
||||
{
|
||||
Assert.Throws<IndexOutOfRangeException>(() =>
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() =>
|
||||
{
|
||||
int unused = stack[i];
|
||||
});
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@ -10,6 +11,7 @@ using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.IO.Archives;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
@ -154,7 +156,30 @@ namespace osu.Game.Tests.Scores.IO
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<ScoreInfo> loadIntoOsu(OsuGameBase osu, ScoreInfo score)
|
||||
[Test]
|
||||
public async Task TestOnlineScoreIsAvailableLocally()
|
||||
{
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestOnlineScoreIsAvailableLocally"))
|
||||
{
|
||||
try
|
||||
{
|
||||
var osu = await loadOsu(host);
|
||||
|
||||
await loadIntoOsu(osu, new ScoreInfo { OnlineScoreID = 2 }, new TestArchiveReader());
|
||||
|
||||
var scoreManager = osu.Dependencies.Get<ScoreManager>();
|
||||
|
||||
// Note: A new score reference is used here since the import process mutates the original object to set an ID
|
||||
Assert.That(scoreManager.IsAvailableLocally(new ScoreInfo { OnlineScoreID = 2 }));
|
||||
}
|
||||
finally
|
||||
{
|
||||
host.Exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<ScoreInfo> loadIntoOsu(OsuGameBase osu, ScoreInfo score, ArchiveReader archive = null)
|
||||
{
|
||||
var beatmapManager = osu.Dependencies.Get<BeatmapManager>();
|
||||
|
||||
@ -165,7 +190,7 @@ namespace osu.Game.Tests.Scores.IO
|
||||
score.Ruleset = new OsuRuleset().RulesetInfo;
|
||||
|
||||
var scoreManager = osu.Dependencies.Get<ScoreManager>();
|
||||
await scoreManager.Import(score);
|
||||
await scoreManager.Import(score, archive);
|
||||
|
||||
return scoreManager.GetAllUsableScores().FirstOrDefault();
|
||||
}
|
||||
@ -196,5 +221,23 @@ namespace osu.Game.Tests.Scores.IO
|
||||
|
||||
Assert.IsTrue(task.Wait(timeout), failureMessage);
|
||||
}
|
||||
|
||||
private class TestArchiveReader : ArchiveReader
|
||||
{
|
||||
public TestArchiveReader()
|
||||
: base("test_archive")
|
||||
{
|
||||
}
|
||||
|
||||
public override Stream GetStream(string name) => new MemoryStream();
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
public override IEnumerable<string> Filenames => new[] { "test_file.osr" };
|
||||
|
||||
public override Stream GetUnderlyingStream() => new MemoryStream();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -88,6 +88,20 @@ namespace osu.Game.Tests.Visual.Background
|
||||
AddUntilStep("not lightened", () => userDimContainer.DimEqual(test_user_dim));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestIgnoreUserSettings()
|
||||
{
|
||||
AddStep("set dim level 0.6", () => userDimContainer.UserDimLevel.Value = test_user_dim);
|
||||
AddUntilStep("dim reached", () => userDimContainer.DimEqual(test_user_dim));
|
||||
|
||||
AddStep("ignore settings", () => userDimContainer.IgnoreUserSettings.Value = true);
|
||||
AddUntilStep("no dim", () => userDimContainer.DimEqual(0));
|
||||
AddStep("set break", () => isBreakTime.Value = true);
|
||||
AddAssert("no dim", () => userDimContainer.DimEqual(0));
|
||||
AddStep("clear break", () => isBreakTime.Value = false);
|
||||
AddAssert("no dim", () => userDimContainer.DimEqual(0));
|
||||
}
|
||||
|
||||
private class TestUserDimContainer : UserDimContainer
|
||||
{
|
||||
public bool DimEqual(float expectedDimLevel) => Content.Colour == OsuColour.Gray(1f - expectedDimLevel);
|
||||
|
81
osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
Normal file
@ -0,0 +1,81 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Screens.Play;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public class TestSceneHUDOverlay : ManualInputManagerTestScene
|
||||
{
|
||||
private HUDOverlay hudOverlay;
|
||||
|
||||
private Drawable hideTarget => hudOverlay.KeyCounter; // best way of checking hideTargets without exposing.
|
||||
|
||||
[Resolved]
|
||||
private OsuConfigManager config { get; set; }
|
||||
|
||||
[Test]
|
||||
public void TestShownByDefault()
|
||||
{
|
||||
createNew();
|
||||
|
||||
AddAssert("showhud is set", () => hudOverlay.ShowHud.Value);
|
||||
|
||||
AddAssert("hidetarget is visible", () => hideTarget.IsPresent);
|
||||
AddAssert("pause button is visible", () => hudOverlay.HoldToQuit.IsPresent);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFadesInOnLoadComplete()
|
||||
{
|
||||
float? initialAlpha = null;
|
||||
|
||||
createNew(h => h.OnLoadComplete += _ => initialAlpha = hideTarget.Alpha);
|
||||
AddUntilStep("wait for load", () => hudOverlay.IsAlive);
|
||||
AddAssert("initial alpha was less than 1", () => initialAlpha != null && initialAlpha < 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHideExternally()
|
||||
{
|
||||
createNew();
|
||||
|
||||
AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
|
||||
|
||||
AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent);
|
||||
AddAssert("pause button is still visible", () => hudOverlay.HoldToQuit.IsPresent);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestExternalHideDoesntAffectConfig()
|
||||
{
|
||||
bool originalConfigValue = false;
|
||||
|
||||
createNew();
|
||||
|
||||
AddStep("get original config value", () => originalConfigValue = config.Get<bool>(OsuSetting.ShowInterface));
|
||||
|
||||
AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
|
||||
AddAssert("config unchanged", () => originalConfigValue == config.Get<bool>(OsuSetting.ShowInterface));
|
||||
|
||||
AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true);
|
||||
AddAssert("config unchanged", () => originalConfigValue == config.Get<bool>(OsuSetting.ShowInterface));
|
||||
}
|
||||
|
||||
private void createNew(Action<HUDOverlay> action = null)
|
||||
{
|
||||
AddStep("create overlay", () =>
|
||||
{
|
||||
Child = hudOverlay = new HUDOverlay(null, null, Array.Empty<Mod>());
|
||||
|
||||
action?.Invoke(hudOverlay);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
// 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.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
@ -31,7 +32,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
requestCount = 0;
|
||||
increment = skip_time;
|
||||
|
||||
Child = gameplayClockContainer = new GameplayClockContainer(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), new Mod[] { }, 0)
|
||||
Child = gameplayClockContainer = new GameplayClockContainer(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), Array.Empty<Mod>(), 0)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
|
@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
});
|
||||
|
||||
AddStep(@"set max", () => Room.MaxParticipants.Value = 10);
|
||||
AddStep(@"clear users", () => Room.Participants.Value = new User[] { });
|
||||
AddStep(@"clear users", () => Room.Participants.Value = System.Array.Empty<User>());
|
||||
AddStep(@"set max to null", () => Room.MaxParticipants.Value = null);
|
||||
}
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
AddStep("set second set", () => details.BeatmapSet = secondSet);
|
||||
AddAssert("ratings set", () => details.Ratings.Metrics == secondSet.Metrics);
|
||||
|
||||
BeatmapSetInfo createSet() => new BeatmapSetInfo
|
||||
static BeatmapSetInfo createSet() => new BeatmapSetInfo
|
||||
{
|
||||
Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).Select(_ => RNG.Next(10)).ToArray() },
|
||||
Beatmaps = new List<BeatmapInfo>
|
||||
|
@ -1,23 +1,66 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.News;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
public class TestSceneNewsOverlay : OsuTestScene
|
||||
{
|
||||
private NewsOverlay news;
|
||||
private TestNewsOverlay news;
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
Add(news = new NewsOverlay());
|
||||
Add(news = new TestNewsOverlay());
|
||||
AddStep(@"Show", news.Show);
|
||||
AddStep(@"Hide", news.Hide);
|
||||
|
||||
AddStep(@"Show front page", () => news.ShowFrontPage());
|
||||
AddStep(@"Custom article", () => news.Current.Value = "Test Article 101");
|
||||
|
||||
AddStep(@"Article covers", () => news.LoadAndShowContent(new NewsCoverTest()));
|
||||
}
|
||||
|
||||
private class TestNewsOverlay : NewsOverlay
|
||||
{
|
||||
public new void LoadAndShowContent(NewsContent content) => base.LoadAndShowContent(content);
|
||||
}
|
||||
|
||||
private class NewsCoverTest : NewsContent
|
||||
{
|
||||
public NewsCoverTest()
|
||||
{
|
||||
Spacing = new osuTK.Vector2(0, 10);
|
||||
|
||||
var article = new NewsArticleCover.ArticleInfo
|
||||
{
|
||||
Author = "Ephemeral",
|
||||
CoverUrl = "https://assets.ppy.sh/artists/58/header.jpg",
|
||||
Time = new DateTime(2019, 12, 4),
|
||||
Title = "New Featured Artist: Kurokotei"
|
||||
};
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new NewsArticleCover(article)
|
||||
{
|
||||
Height = 200
|
||||
},
|
||||
new NewsArticleCover(article)
|
||||
{
|
||||
Height = 120
|
||||
},
|
||||
new NewsArticleCover(article)
|
||||
{
|
||||
RelativeSizeAxes = Axes.None,
|
||||
Size = new osuTK.Vector2(400, 200),
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
42
osu.Game.Tests/Visual/Online/TestSceneProfileCounterPill.cs
Normal file
@ -0,0 +1,42 @@
|
||||
// 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.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Overlays.Profile.Sections;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
public class TestSceneProfileCounterPill : OsuTestScene
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||
{
|
||||
typeof(CounterPill)
|
||||
};
|
||||
|
||||
private readonly CounterPill pill;
|
||||
private readonly BindableInt value = new BindableInt();
|
||||
|
||||
public TestSceneProfileCounterPill()
|
||||
{
|
||||
Child = pill = new CounterPill
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Current = { BindTarget = value }
|
||||
};
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestVisibility()
|
||||
{
|
||||
AddStep("Set value to 0", () => value.Value = 0);
|
||||
AddAssert("Check hidden", () => !pill.IsPresent);
|
||||
AddStep("Set value to 10", () => value.Value = 10);
|
||||
AddAssert("Check visible", () => pill.IsPresent);
|
||||
}
|
||||
}
|
||||
}
|
@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
},
|
||||
Title = "osu!volunteer",
|
||||
Colour = "ff0000",
|
||||
Achievements = new User.UserAchievement[0],
|
||||
Achievements = Array.Empty<User.UserAchievement>(),
|
||||
};
|
||||
|
||||
public TestSceneUserProfileOverlay()
|
||||
|
@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
new User { PreviousUsernames = new[] { "longusername", "longerusername" } },
|
||||
new User { PreviousUsernames = new[] { "test", "angelsim", "verylongusername" } },
|
||||
new User { PreviousUsernames = new[] { "ihavenoidea", "howcani", "makethistext", "anylonger" } },
|
||||
new User { PreviousUsernames = new string[0] },
|
||||
new User { PreviousUsernames = Array.Empty<string>() },
|
||||
null
|
||||
};
|
||||
|
||||
|
@ -111,7 +111,7 @@ namespace osu.Game.Tests.Visual
|
||||
foreach (var type in requiredGameDependencies)
|
||||
{
|
||||
if (game.Dependencies.Get(type) == null)
|
||||
throw new Exception($"{type} has not been cached");
|
||||
throw new InvalidOperationException($"{type} has not been cached");
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -121,7 +121,7 @@ namespace osu.Game.Tests.Visual
|
||||
foreach (var type in requiredGameBaseDependencies)
|
||||
{
|
||||
if (gameBase.Dependencies.Get(type) == null)
|
||||
throw new Exception($"{type} has not been cached");
|
||||
throw new InvalidOperationException($"{type} has not been cached");
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -157,7 +157,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
|
||||
private TimingControlPoint getNextTimingPoint(TimingControlPoint current)
|
||||
{
|
||||
if (timingPoints[timingPoints.Count - 1] == current)
|
||||
if (timingPoints[^1] == current)
|
||||
return current;
|
||||
|
||||
int index = timingPoints.IndexOf(current); // -1 means that this is a "default beat"
|
||||
@ -169,7 +169,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
if (timingPoints.Count == 0) return 0;
|
||||
|
||||
if (timingPoints[timingPoints.Count - 1] == current)
|
||||
if (timingPoints[^1] == current)
|
||||
return (int)Math.Ceiling((Beatmap.Value.Track.Length - current.Time) / current.BeatLength);
|
||||
|
||||
return (int)Math.Ceiling((getNextTimingPoint(current).Time - current.Time) / current.BeatLength);
|
||||
|
@ -49,7 +49,7 @@ namespace osu.Game.Tournament.Screens.Editors
|
||||
get
|
||||
{
|
||||
if (editorInfo == null)
|
||||
return new MenuItem[0];
|
||||
return Array.Empty<MenuItem>();
|
||||
|
||||
return new MenuItem[]
|
||||
{
|
||||
|
@ -192,7 +192,7 @@ namespace osu.Game.Tournament.Screens.Ladder.Components
|
||||
get
|
||||
{
|
||||
if (editorInfo == null)
|
||||
return new MenuItem[0];
|
||||
return Array.Empty<MenuItem>();
|
||||
|
||||
return new MenuItem[]
|
||||
{
|
||||
|
@ -98,7 +98,7 @@ namespace osu.Game.Beatmaps
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
Bookmarks = new int[0];
|
||||
Bookmarks = Array.Empty<int>();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -111,7 +111,7 @@ namespace osu.Game.Beatmaps
|
||||
}
|
||||
|
||||
[NotMapped]
|
||||
public int[] Bookmarks { get; set; } = new int[0];
|
||||
public int[] Bookmarks { get; set; } = Array.Empty<int>();
|
||||
|
||||
public double DistanceSpacing { get; set; }
|
||||
public int BeatDivisor { get; set; }
|
||||
|
@ -158,7 +158,9 @@ namespace osu.Game.Beatmaps
|
||||
void resetIds() => beatmapSet.Beatmaps.ForEach(b => b.OnlineBeatmapID = null);
|
||||
}
|
||||
|
||||
protected override bool CheckLocalAvailability(BeatmapSetInfo model, IQueryable<BeatmapSetInfo> items) => items.Any(b => b.OnlineBeatmapSetID == model.OnlineBeatmapSetID);
|
||||
protected override bool CheckLocalAvailability(BeatmapSetInfo model, IQueryable<BeatmapSetInfo> items)
|
||||
=> base.CheckLocalAvailability(model, items)
|
||||
|| (model.OnlineBeatmapSetID != null && items.Any(b => b.OnlineBeatmapSetID == model.OnlineBeatmapSetID));
|
||||
|
||||
/// <summary>
|
||||
/// Delete a beatmap difficulty.
|
||||
|
@ -42,7 +42,7 @@ namespace osu.Game.Beatmaps
|
||||
}
|
||||
}
|
||||
|
||||
private string getPathForFile(string filename) => BeatmapSetInfo.Files.FirstOrDefault(f => string.Equals(f.Filename, filename, StringComparison.InvariantCultureIgnoreCase))?.FileInfo.StoragePath;
|
||||
private string getPathForFile(string filename) => BeatmapSetInfo.Files.FirstOrDefault(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath;
|
||||
|
||||
private TextureStore textureStore;
|
||||
|
||||
|
@ -195,8 +195,8 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
if (time < list[0].Time)
|
||||
return null;
|
||||
|
||||
if (time >= list[list.Count - 1].Time)
|
||||
return list[list.Count - 1];
|
||||
if (time >= list[^1].Time)
|
||||
return list[^1];
|
||||
|
||||
int l = 0;
|
||||
int r = list.Count - 2;
|
||||
@ -239,7 +239,7 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
break;
|
||||
|
||||
case SampleControlPoint _:
|
||||
existing = SamplePointAt(time);
|
||||
existing = binarySearch(SamplePoints, time);
|
||||
break;
|
||||
|
||||
case DifficultyControlPoint _:
|
||||
|
@ -70,6 +70,6 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
|
||||
public override bool EquivalentTo(ControlPoint other) =>
|
||||
other is SampleControlPoint otherTyped &&
|
||||
string.Equals(SampleBank, otherTyped.SampleBank) && SampleVolume == otherTyped.SampleVolume;
|
||||
SampleBank == otherTyped.SampleBank && SampleVolume == otherTyped.SampleVolume;
|
||||
}
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
private class DummyRuleset : Ruleset
|
||||
{
|
||||
public override IEnumerable<Mod> GetModsFor(ModType type) => new Mod[] { };
|
||||
public override IEnumerable<Mod> GetModsFor(ModType type) => Array.Empty<Mod>();
|
||||
|
||||
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null)
|
||||
{
|
||||
|
@ -34,7 +34,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
|
||||
if (line.StartsWith(@"[", StringComparison.Ordinal) && line.EndsWith(@"]", StringComparison.Ordinal))
|
||||
{
|
||||
if (!Enum.TryParse(line.Substring(1, line.Length - 2), out section))
|
||||
if (!Enum.TryParse(line[1..^1], out section))
|
||||
{
|
||||
Logger.Log($"Unknown section \"{line}\" in \"{output}\"");
|
||||
section = Section.None;
|
||||
|
@ -176,7 +176,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
{
|
||||
var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
|
||||
var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
|
||||
timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startValue), new Vector2(endValue));
|
||||
timelineGroup?.Scale.Add(easing, startTime, endTime, startValue, endValue);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -186,7 +186,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
var startY = float.Parse(split[5], CultureInfo.InvariantCulture);
|
||||
var endX = split.Length > 6 ? float.Parse(split[6], CultureInfo.InvariantCulture) : startX;
|
||||
var endY = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startY;
|
||||
timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startX, startY), new Vector2(endX, endY));
|
||||
timelineGroup?.VectorScale.Add(easing, startTime, endTime, new Vector2(startX, startY), new Vector2(endX, endY));
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -33,7 +33,8 @@ namespace osu.Game.Database
|
||||
|
||||
private readonly MutableDatabaseBackedStoreWithFileIncludes<TModel, TFileModel> modelStore;
|
||||
|
||||
protected DownloadableArchiveModelManager(Storage storage, IDatabaseContextFactory contextFactory, IAPIProvider api, MutableDatabaseBackedStoreWithFileIncludes<TModel, TFileModel> modelStore, IIpcHost importHost = null)
|
||||
protected DownloadableArchiveModelManager(Storage storage, IDatabaseContextFactory contextFactory, IAPIProvider api, MutableDatabaseBackedStoreWithFileIncludes<TModel, TFileModel> modelStore,
|
||||
IIpcHost importHost = null)
|
||||
: base(storage, contextFactory, modelStore, importHost)
|
||||
{
|
||||
this.api = api;
|
||||
@ -124,7 +125,8 @@ namespace osu.Game.Database
|
||||
/// <param name="model">The <typeparamref name="TModel"/> whose existence needs to be checked.</param>
|
||||
/// <param name="items">The usable items present in the store.</param>
|
||||
/// <returns>Whether the <typeparamref name="TModel"/> exists.</returns>
|
||||
protected abstract bool CheckLocalAvailability(TModel model, IQueryable<TModel> items);
|
||||
protected virtual bool CheckLocalAvailability(TModel model, IQueryable<TModel> items)
|
||||
=> model.ID > 0 && items.Any(i => i.ID == model.ID && i.Files.Any());
|
||||
|
||||
public ArchiveDownloadRequest<TModel> GetExistingDownload(TModel model) => currentDownloads.Find(r => r.Model.Equals(model));
|
||||
|
||||
|
@ -7,9 +7,9 @@ using osu.Framework.Platform;
|
||||
|
||||
namespace osu.Game.Database
|
||||
{
|
||||
public abstract class MutableDatabaseBackedStoreWithFileIncludes<T, U> : MutableDatabaseBackedStore<T>
|
||||
where T : class, IHasPrimaryKey, ISoftDelete, IHasFiles<U>
|
||||
where U : INamedFileInfo
|
||||
public abstract class MutableDatabaseBackedStoreWithFileIncludes<T, TFileInfo> : MutableDatabaseBackedStore<T>
|
||||
where T : class, IHasPrimaryKey, ISoftDelete, IHasFiles<TFileInfo>
|
||||
where TFileInfo : INamedFileInfo
|
||||
{
|
||||
protected MutableDatabaseBackedStoreWithFileIncludes(IDatabaseContextFactory contextFactory, Storage storage = null)
|
||||
: base(contextFactory, storage)
|
||||
|
@ -37,7 +37,7 @@ namespace osu.Game.Graphics.Containers
|
||||
|
||||
foreach (var link in links)
|
||||
{
|
||||
AddText(text.Substring(previousLinkEnd, link.Index - previousLinkEnd));
|
||||
AddText(text[previousLinkEnd..link.Index]);
|
||||
AddLink(text.Substring(link.Index, link.Length), link.Action, link.Argument ?? link.Url);
|
||||
previousLinkEnd = link.Index + link.Length;
|
||||
}
|
||||
|
@ -83,6 +83,15 @@ namespace osu.Game.Graphics.Containers
|
||||
return base.OnDragEnd(e);
|
||||
}
|
||||
|
||||
protected override bool OnScroll(ScrollEvent e)
|
||||
{
|
||||
// allow for controlling volume when alt is held.
|
||||
// mostly for compatibility with osu-stable.
|
||||
if (e.AltPressed) return false;
|
||||
|
||||
return base.OnScroll(e);
|
||||
}
|
||||
|
||||
protected override ScrollbarContainer CreateScrollbar(Direction direction) => new OsuScrollbar(direction);
|
||||
|
||||
protected class OsuScrollbar : ScrollbarContainer
|
||||
|
@ -28,6 +28,11 @@ namespace osu.Game.Graphics.Containers
|
||||
/// </summary>
|
||||
public readonly Bindable<bool> EnableUserDim = new Bindable<bool>(true);
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not user-configured settings relating to brightness of elements should be ignored
|
||||
/// </summary>
|
||||
public readonly Bindable<bool> IgnoreUserSettings = new Bindable<bool>();
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the storyboard loaded should completely hide the background behind it.
|
||||
/// </summary>
|
||||
@ -53,7 +58,8 @@ namespace osu.Game.Graphics.Containers
|
||||
protected Bindable<bool> ShowVideo { get; private set; }
|
||||
|
||||
private float breakLightening => LightenDuringBreaks.Value && IsBreakTime.Value ? BREAK_LIGHTEN_AMOUNT : 0;
|
||||
protected float DimLevel => Math.Max(EnableUserDim.Value ? (float)UserDimLevel.Value - breakLightening : 0, 0);
|
||||
|
||||
protected float DimLevel => Math.Max(EnableUserDim.Value && !IgnoreUserSettings.Value ? (float)UserDimLevel.Value - breakLightening : 0, 0);
|
||||
|
||||
protected override Container<Drawable> Content => dimContent;
|
||||
|
||||
@ -82,6 +88,7 @@ namespace osu.Game.Graphics.Containers
|
||||
ShowStoryboard.ValueChanged += _ => UpdateVisuals();
|
||||
ShowVideo.ValueChanged += _ => UpdateVisuals();
|
||||
StoryboardReplacesBackground.ValueChanged += _ => UpdateVisuals();
|
||||
IgnoreUserSettings.ValueChanged += _ => UpdateVisuals();
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
|
@ -119,7 +119,7 @@ namespace osu.Game.Graphics
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(screenshotFormat));
|
||||
throw new InvalidOperationException($"Unknown enum member {nameof(ScreenshotFormat)} {screenshotFormat.Value}.");
|
||||
}
|
||||
|
||||
notificationOverlay.Post(new SimpleNotification
|
||||
|
@ -7,15 +7,15 @@ using osu.Framework.Graphics.UserInterface;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterfaceV2
|
||||
{
|
||||
public abstract class LabelledComponent<T, U> : LabelledDrawable<T>, IHasCurrentValue<U>
|
||||
where T : Drawable, IHasCurrentValue<U>
|
||||
public abstract class LabelledComponent<TDrawable, TValue> : LabelledDrawable<TDrawable>, IHasCurrentValue<TValue>
|
||||
where TDrawable : Drawable, IHasCurrentValue<TValue>
|
||||
{
|
||||
protected LabelledComponent(bool padded)
|
||||
: base(padded)
|
||||
{
|
||||
}
|
||||
|
||||
public Bindable<U> Current
|
||||
public Bindable<TValue> Current
|
||||
{
|
||||
get => Component.Current;
|
||||
set => Component.Current = value;
|
||||
|
@ -116,13 +116,13 @@ namespace osu.Game.IO.Legacy
|
||||
}
|
||||
|
||||
/// <summary> Reads a generic Dictionary from the buffer. </summary>
|
||||
public IDictionary<T, U> ReadDictionary<T, U>()
|
||||
public IDictionary<TKey, TValue> ReadDictionary<TKey, TValue>()
|
||||
{
|
||||
int count = ReadInt32();
|
||||
if (count < 0) return null;
|
||||
|
||||
IDictionary<T, U> d = new Dictionary<T, U>();
|
||||
for (int i = 0; i < count; i++) d[(T)ReadObject()] = (U)ReadObject();
|
||||
IDictionary<TKey, TValue> d = new Dictionary<TKey, TValue>();
|
||||
for (int i = 0; i < count; i++) d[(TKey)ReadObject()] = (TValue)ReadObject();
|
||||
return d;
|
||||
}
|
||||
|
||||
@ -192,7 +192,7 @@ namespace osu.Game.IO.Legacy
|
||||
}
|
||||
}
|
||||
|
||||
public class DynamicDeserializer
|
||||
public static class DynamicDeserializer
|
||||
{
|
||||
private static VersionConfigToNamespaceAssemblyObjectBinder versionBinder;
|
||||
private static BinaryFormatter formatter;
|
||||
|
@ -102,7 +102,7 @@ namespace osu.Game.IO.Legacy
|
||||
}
|
||||
|
||||
/// <summary> Writes a generic IDictionary to the buffer. </summary>
|
||||
public void Write<T, U>(IDictionary<T, U> d)
|
||||
public void Write<TKey, TValue>(IDictionary<TKey, TValue> d)
|
||||
{
|
||||
if (d == null)
|
||||
{
|
||||
@ -112,7 +112,7 @@ namespace osu.Game.IO.Legacy
|
||||
{
|
||||
Write(d.Count);
|
||||
|
||||
foreach (KeyValuePair<T, U> kvp in d)
|
||||
foreach (KeyValuePair<TKey, TValue> kvp in d)
|
||||
{
|
||||
WriteObject(kvp.Key);
|
||||
WriteObject(kvp.Value);
|
||||
|
@ -10,6 +10,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.ExceptionExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Configuration;
|
||||
@ -152,6 +153,10 @@ namespace osu.Game.Online.API
|
||||
userReq.Success += u =>
|
||||
{
|
||||
LocalUser.Value = u;
|
||||
|
||||
// todo: save/pull from settings
|
||||
LocalUser.Value.Status.Value = new UserStatusOnline();
|
||||
|
||||
failureCount = 0;
|
||||
|
||||
//we're connected!
|
||||
@ -249,7 +254,7 @@ namespace osu.Game.Online.API
|
||||
catch
|
||||
{
|
||||
// if we couldn't deserialize the error message let's throw the original exception outwards.
|
||||
throw e;
|
||||
e.Rethrow();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -122,7 +122,7 @@ namespace osu.Game.Online.API
|
||||
// attempt to decode a displayable error string.
|
||||
var error = JsonConvert.DeserializeObject<DisplayableError>(responseString);
|
||||
if (error != null)
|
||||
e = new Exception(error.ErrorMessage, e);
|
||||
e = new APIException(error.ErrorMessage, e);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@ -154,6 +154,14 @@ namespace osu.Game.Online.API
|
||||
}
|
||||
}
|
||||
|
||||
public class APIException : InvalidOperationException
|
||||
{
|
||||
public APIException(string messsage, Exception innerException)
|
||||
: base(messsage, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public delegate void APIFailureHandler(Exception e);
|
||||
|
||||
public delegate void APISuccessHandler();
|
||||
|
@ -19,7 +19,7 @@ using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Online.Leaderboards
|
||||
{
|
||||
public abstract class Leaderboard<TScope, ScoreInfo> : Container, IOnlineComponent
|
||||
public abstract class Leaderboard<TScope, TScoreInfo> : Container, IOnlineComponent
|
||||
{
|
||||
private const double fade_duration = 300;
|
||||
|
||||
@ -39,9 +39,9 @@ namespace osu.Game.Online.Leaderboards
|
||||
|
||||
protected override Container<Drawable> Content => content;
|
||||
|
||||
private IEnumerable<ScoreInfo> scores;
|
||||
private IEnumerable<TScoreInfo> scores;
|
||||
|
||||
public IEnumerable<ScoreInfo> Scores
|
||||
public IEnumerable<TScoreInfo> Scores
|
||||
{
|
||||
get => scores;
|
||||
set
|
||||
@ -288,7 +288,7 @@ namespace osu.Game.Online.Leaderboards
|
||||
/// </summary>
|
||||
/// <param name="scoresCallback">A callback which should be called when fetching is completed. Scheduling is not required.</param>
|
||||
/// <returns>An <see cref="APIRequest"/> responsible for the fetch operation. This will be queued and performed automatically.</returns>
|
||||
protected abstract APIRequest FetchScores(Action<IEnumerable<ScoreInfo>> scoresCallback);
|
||||
protected abstract APIRequest FetchScores(Action<IEnumerable<TScoreInfo>> scoresCallback);
|
||||
|
||||
private Placeholder currentPlaceholder;
|
||||
|
||||
@ -359,6 +359,6 @@ namespace osu.Game.Online.Leaderboards
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract LeaderboardScore CreateDrawableScore(ScoreInfo model, int index);
|
||||
protected abstract LeaderboardScore CreateDrawableScore(TScoreInfo model, int index);
|
||||
}
|
||||
}
|
||||
|
@ -115,11 +115,7 @@ namespace osu.Game.Overlays.Chat.Selection
|
||||
Font = OsuFont.GetFont(size: 20),
|
||||
Shadow = false,
|
||||
},
|
||||
search = new HeaderSearchTextBox
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
PlaceholderText = @"Search",
|
||||
},
|
||||
search = new HeaderSearchTextBox { RelativeSizeAxes = Axes.X },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -116,7 +116,7 @@ namespace osu.Game.Overlays
|
||||
|
||||
Filter.Search.Current.ValueChanged += text =>
|
||||
{
|
||||
if (text.NewValue != string.Empty)
|
||||
if (!string.IsNullOrEmpty(text.NewValue))
|
||||
{
|
||||
Header.Tabs.Current.Value = DirectTab.Search;
|
||||
|
||||
|
@ -81,7 +81,7 @@ namespace osu.Game.Overlays.Mods
|
||||
}
|
||||
}
|
||||
|
||||
private ModButton[] buttons = { };
|
||||
private ModButton[] buttons = Array.Empty<ModButton>();
|
||||
|
||||
protected override bool OnKeyDown(KeyDownEvent e)
|
||||
{
|
||||
|
@ -239,7 +239,7 @@ namespace osu.Game.Overlays.Music
|
||||
|
||||
private class ItemSearchContainer : FillFlowContainer<PlaylistItem>, IHasFilterableChildren
|
||||
{
|
||||
public IEnumerable<string> FilterTerms => new string[] { };
|
||||
public IEnumerable<string> FilterTerms => Array.Empty<string>();
|
||||
|
||||
public bool MatchingFilter
|
||||
{
|
||||
|