1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-11 10:33:30 +08:00

Merge remote-tracking branch 'refs/remotes/origin/master' into nightcore-beats

This commit is contained in:
smoogipoo 2019-12-18 17:58:37 +09:00
commit 2a128340d0
146 changed files with 1622 additions and 403 deletions

View File

@ -176,8 +176,8 @@ dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
#Style - C# 8 features #Style - C# 8 features
csharp_prefer_static_local_function = true:warning csharp_prefer_static_local_function = true:warning
csharp_prefer_simple_using_statement = true:silent csharp_prefer_simple_using_statement = true:silent
csharp_style_prefer_index_operator = false:none csharp_style_prefer_index_operator = true:warning
csharp_style_prefer_range_operator = false:none csharp_style_prefer_range_operator = true:warning
csharp_style_prefer_switch_expression = false:none csharp_style_prefer_switch_expression = false:none
#Supressing roslyn built-in analyzers #Supressing roslyn built-in analyzers

58
CodeAnalysis/osu.ruleset Normal file
View 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>

View File

@ -18,7 +18,11 @@
<ItemGroup Label="Code Analysis"> <ItemGroup Label="Code Analysis">
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="2.9.8" PrivateAssets="All" /> <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="2.9.8" PrivateAssets="All" />
<AdditionalFiles Include="$(MSBuildThisFileDirectory)CodeAnalysis\BannedSymbols.txt" /> <AdditionalFiles Include="$(MSBuildThisFileDirectory)CodeAnalysis\BannedSymbols.txt" />
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
</ItemGroup> </ItemGroup>
<PropertyGroup Label="Code Analysis">
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)CodeAnalysis\osu.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Label="Documentation"> <PropertyGroup Label="Documentation">
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);CS1591</NoWarn> <NoWarn>$(NoWarn);CS1591</NoWarn>

View File

@ -1,5 +1,5 @@
#addin "nuget:?package=CodeFileSanity&version=0.0.33" #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" #tool "nuget:?package=NVika.MSBuild&version=1.0.1"
var nVikaToolPath = GetFiles("./tools/NVika.MSBuild.*/tools/NVika.exe").First(); var nVikaToolPath = GetFiles("./tools/NVika.MSBuild.*/tools/NVika.exe").First();

View File

@ -1,4 +1,9 @@
{ {
"sdk": {
"allowPrerelease": false,
"rollForward": "minor",
"version": "3.1.100"
},
"msbuild-sdks": { "msbuild-sdks": {
"Microsoft.Build.Traversal": "2.0.24" "Microsoft.Build.Traversal": "2.0.24"
} }

View 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);
}
}
}

View File

@ -60,6 +60,8 @@ namespace osu.Desktop
else else
Add(new SimpleUpdateManager()); Add(new SimpleUpdateManager());
} }
LoadComponentAsync(new DiscordRichPresence(), Add);
} }
protected override void ScreenChanged(IScreen lastScreen, IScreen newScreen) protected override void ScreenChanged(IScreen lastScreen, IScreen newScreen)

View File

@ -29,6 +29,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.2.6" />
<PackageReference Include="Microsoft.Win32.Registry" Version="4.7.0" /> <PackageReference Include="Microsoft.Win32.Registry" Version="4.7.0" />
<PackageReference Include="DiscordRichPresence" Version="1.0.121" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Resources"> <ItemGroup Label="Resources">
<EmbeddedResource Include="lazer.ico" /> <EmbeddedResource Include="lazer.ico" />

View File

@ -16,14 +16,20 @@ using osu.Game.Rulesets.Replays.Types;
using osu.Game.Beatmaps.Legacy; using osu.Game.Beatmaps.Legacy;
using osu.Game.Rulesets.Catch.Beatmaps; using osu.Game.Rulesets.Catch.Beatmaps;
using osu.Game.Rulesets.Catch.Difficulty; using osu.Game.Rulesets.Catch.Difficulty;
using osu.Game.Rulesets.Catch.Scoring;
using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring; using osu.Game.Scoring;
using System;
namespace osu.Game.Rulesets.Catch namespace osu.Game.Rulesets.Catch
{ {
public class CatchRuleset : Ruleset public class CatchRuleset : Ruleset
{ {
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => new DrawableCatchRuleset(this, beatmap, mods); public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => new DrawableCatchRuleset(this, beatmap, mods);
public override ScoreProcessor CreateScoreProcessor(IBeatmap beatmap) => new CatchScoreProcessor(beatmap);
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap);
public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new CatchBeatmapProcessor(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)) else if (mods.HasFlag(LegacyMods.SuddenDeath))
yield return new CatchModSuddenDeath(); 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(); yield return new CatchModAutoplay();
if (mods.HasFlag(LegacyMods.Easy)) if (mods.HasFlag(LegacyMods.Easy))
@ -101,7 +109,7 @@ namespace osu.Game.Rulesets.Catch
case ModType.Automation: case ModType.Automation:
return new Mod[] return new Mod[]
{ {
new MultiMod(new CatchModAutoplay(), new ModCinema()), new MultiMod(new CatchModAutoplay(), new CatchModCinema()),
new CatchModRelax(), new CatchModRelax(),
}; };
@ -112,7 +120,7 @@ namespace osu.Game.Rulesets.Catch
}; };
default: default:
return new Mod[] { }; return Array.Empty<Mod>();
} }
} }

View 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(),
};
}
}

View File

@ -10,10 +10,8 @@ using osu.Game.Replays;
using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawable; using osu.Game.Rulesets.Catch.Objects.Drawable;
using osu.Game.Rulesets.Catch.Replays; using osu.Game.Rulesets.Catch.Replays;
using osu.Game.Rulesets.Catch.Scoring;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling; 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); 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 ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay);
protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty, CreateDrawableRepresentation); protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty, CreateDrawableRepresentation);

View File

@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
prevNoteTimes.RemoveAt(0); prevNoteTimes.RemoveAt(0);
prevNoteTimes.Add(newNoteTime); prevNoteTimes.Add(newNoteTime);
density = (prevNoteTimes[prevNoteTimes.Count - 1] - prevNoteTimes[0]) / prevNoteTimes.Count; density = (prevNoteTimes[^1] - prevNoteTimes[0]) / prevNoteTimes.Count;
} }
private double lastTime; private double lastTime;

View File

@ -25,6 +25,8 @@ using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.Difficulty; using osu.Game.Rulesets.Mania.Difficulty;
using osu.Game.Rulesets.Mania.Edit; using osu.Game.Rulesets.Mania.Edit;
using osu.Game.Rulesets.Mania.Scoring;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring; using osu.Game.Scoring;
namespace osu.Game.Rulesets.Mania namespace osu.Game.Rulesets.Mania
@ -32,7 +34,11 @@ namespace osu.Game.Rulesets.Mania
public class ManiaRuleset : Ruleset public class ManiaRuleset : Ruleset
{ {
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => new DrawableManiaRuleset(this, beatmap, mods); 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 IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap);
public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new ManiaPerformanceCalculator(this, beatmap, score); public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new ManiaPerformanceCalculator(this, beatmap, score);
public const string SHORT_NAME = "mania"; public const string SHORT_NAME = "mania";
@ -51,7 +57,9 @@ namespace osu.Game.Rulesets.Mania
else if (mods.HasFlag(LegacyMods.SuddenDeath)) else if (mods.HasFlag(LegacyMods.SuddenDeath))
yield return new ManiaModSuddenDeath(); 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(); yield return new ManiaModAutoplay();
if (mods.HasFlag(LegacyMods.Easy)) if (mods.HasFlag(LegacyMods.Easy))
@ -148,7 +156,7 @@ namespace osu.Game.Rulesets.Mania
case ModType.Automation: case ModType.Automation:
return new Mod[] return new Mod[]
{ {
new MultiMod(new ManiaModAutoplay(), new ModCinema()), new MultiMod(new ManiaModAutoplay(), new ManiaModCinema()),
}; };
case ModType.Fun: case ModType.Fun:
@ -158,7 +166,7 @@ namespace osu.Game.Rulesets.Mania
}; };
default: default:
return new Mod[] { }; return Array.Empty<Mod>();
} }
} }
@ -268,7 +276,7 @@ namespace osu.Game.Rulesets.Mania
return stage1Bindings.Concat(stage2Bindings); return stage1Bindings.Concat(stage2Bindings);
} }
return new KeyBinding[0]; return Array.Empty<KeyBinding>();
} }
public override string GetVariantName(int variant) public override string GetVariantName(int variant)

View 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(),
};
}
}

View File

@ -14,11 +14,9 @@ using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Replays; using osu.Game.Rulesets.Mania.Replays;
using osu.Game.Rulesets.Mania.Scoring;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling;
using osuTK; using osuTK;
@ -67,8 +65,6 @@ namespace osu.Game.Rulesets.Mania.UI
protected override Playfield CreatePlayfield() => new ManiaPlayfield(Beatmap.Stages); 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; public override int Variant => (int)(Beatmap.Stages.Count == 1 ? PlayfieldType.Single : PlayfieldType.Dual) + Beatmap.TotalColumns;
protected override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo, Variant); protected override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo, Variant);

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

@ -0,0 +1,2 @@
[General]
Version: 1.0

View File

@ -19,9 +19,10 @@ namespace osu.Game.Rulesets.Osu.Tests
private Skin metricsSkin; private Skin metricsSkin;
private Skin defaultSkin; private Skin defaultSkin;
private Skin specialSkin; private Skin specialSkin;
private Skin oldSkin;
protected SkinnableTestScene() 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); metricsSkin = new TestLegacySkin(new SkinInfo(), new NamespacedResourceStore<byte[]>(dllStore, "Resources/metrics_skin"), audio, true);
defaultSkin = skinManager.GetSkin(DefaultLegacySkin.Info); defaultSkin = skinManager.GetSkin(DefaultLegacySkin.Info);
specialSkin = new TestLegacySkin(new SkinInfo(), new NamespacedResourceStore<byte[]>(dllStore, "Resources/special_skin"), audio, true); 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) public void SetContents(Func<Drawable> creationFunction)
@ -41,6 +43,7 @@ namespace osu.Game.Rulesets.Osu.Tests
Cell(1).Child = createProvider(metricsSkin, creationFunction); Cell(1).Child = createProvider(metricsSkin, creationFunction);
Cell(2).Child = createProvider(defaultSkin, creationFunction); Cell(2).Child = createProvider(defaultSkin, creationFunction);
Cell(3).Child = createProvider(specialSkin, creationFunction); Cell(3).Child = createProvider(specialSkin, creationFunction);
Cell(4).Child = createProvider(oldSkin, creationFunction);
} }
private Drawable createProvider(Skin skin, Func<Drawable> creationFunction) private Drawable createProvider(Skin skin, Func<Drawable> creationFunction)

View File

@ -286,11 +286,11 @@ namespace osu.Game.Rulesets.Osu.Tests
private bool assertGreatJudge() => judgementResults.Last().Type == HitResult.Great; 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; private ScoreAccessibleReplayPlayer currentPlayer;

View File

@ -70,6 +70,21 @@ namespace osu.Game.Rulesets.Osu.Tests
AddAssert("is rotation absolute almost same", () => Precision.AlmostEquals(drawableSpinner.Disc.RotationAbsolute, estimatedRotation, 100)); 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) private void addSeekStep(double time)
{ {
AddStep($"seek to {time}", () => track.Seek(time)); AddStep($"seek to {time}", () => track.Seek(time));
@ -84,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Tests
new Spinner new Spinner
{ {
Position = new Vector2(256, 192), Position = new Vector2(256, 192),
EndTime = 5000, EndTime = 6000,
}, },
// placeholder object to avoid hitting the results screen // placeholder object to avoid hitting the results screen
new HitObject new HitObject

View File

@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
protected override bool OnDoubleClick(DoubleClickEvent e) protected override bool OnDoubleClick(DoubleClickEvent e)
{ {
// Todo: This should all not occur on double click, but rather if the previous control point is hovered. // 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; segmentStart.Type.Value = PathType.Linear;
currentSegmentLength = 1; currentSegmentLength = 1;

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using JetBrains.Annotations; using JetBrains.Annotations;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Screens.Edit.Compose.Components;
@ -10,7 +11,7 @@ namespace osu.Game.Rulesets.Osu.Edit
public class OsuDistanceSnapGrid : CircularDistanceSnapGrid public class OsuDistanceSnapGrid : CircularDistanceSnapGrid
{ {
public OsuDistanceSnapGrid(OsuHitObject hitObject, [CanBeNull] OsuHitObject nextHitObject = null) public OsuDistanceSnapGrid(OsuHitObject hitObject, [CanBeNull] OsuHitObject nextHitObject = null)
: base(hitObject.StackedPosition, hitObject.StartTime, nextHitObject?.StartTime) : base(hitObject.StackedEndPosition, hitObject.GetEndTime(), nextHitObject?.StartTime)
{ {
Masking = true; Masking = true;
} }

View File

@ -92,7 +92,24 @@ namespace osu.Game.Rulesets.Osu.Edit
return null; return null;
OsuHitObject sourceObject = EditorBeatmap.HitObjects[sourceIndex]; 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); return new OsuDistanceSnapGrid(sourceObject, targetObject);
} }

View 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()
};
}
}

View File

@ -11,6 +11,7 @@ using osu.Game.Configuration;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
namespace osu.Game.Rulesets.Osu.Mods namespace osu.Game.Rulesets.Osu.Mods
{ {
@ -54,13 +55,8 @@ namespace osu.Game.Rulesets.Osu.Mods
break; break;
case DrawableSlider slider: case DrawableSlider slider:
slider.AccentColour.BindValueChanged(_ => slider.Body.OnSkinChanged += () => applySliderState(slider);
{ applySliderState(slider);
//will trigger on skin change.
slider.Body.AccentColour = slider.AccentColour.Value.Opacity(0);
slider.Body.BorderColour = slider.AccentColour.Value;
}, true);
break; break;
case DrawableSpinner spinner: case DrawableSpinner spinner:
@ -69,5 +65,11 @@ namespace osu.Game.Rulesets.Osu.Mods
break; break;
} }
} }
private void applySliderState(DrawableSlider slider)
{
((PlaySliderBody)slider.Body.Drawable).AccentColour = slider.AccentColour.Value.Opacity(0);
((PlaySliderBody)slider.Body.Drawable).BorderColour = slider.AccentColour.Value;
}
} }
} }

View File

@ -9,6 +9,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.MathUtils; using osu.Framework.MathUtils;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osuTK; using osuTK;
using osu.Game.Skinning; using osu.Game.Skinning;
@ -98,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public void UpdateSnakingPosition(Vector2 start, Vector2 end) public void UpdateSnakingPosition(Vector2 start, Vector2 end)
{ {
bool isRepeatAtEnd = repeatPoint.RepeatIndex % 2 == 0; bool isRepeatAtEnd = repeatPoint.RepeatIndex % 2 == 0;
List<Vector2> curve = drawableSlider.Body.CurrentCurve; List<Vector2> curve = ((PlaySliderBody)drawableSlider.Body.Drawable).CurrentCurve;
Position = isRepeatAtEnd ? end : start; Position = isRepeatAtEnd ? end : start;

View File

@ -11,7 +11,6 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Configuration;
using osu.Game.Rulesets.Osu.Skinning; using osu.Game.Rulesets.Osu.Skinning;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osuTK.Graphics; using osuTK.Graphics;
@ -24,8 +23,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public DrawableSliderHead HeadCircle => headContainer.Child; public DrawableSliderHead HeadCircle => headContainer.Child;
public DrawableSliderTail TailCircle => tailContainer.Child; public DrawableSliderTail TailCircle => tailContainer.Child;
public readonly SnakingSliderBody Body;
public readonly SliderBall Ball; public readonly SliderBall Ball;
public readonly SkinnableDrawable Body;
private PlaySliderBody sliderBody => Body.Drawable as PlaySliderBody;
private readonly Container<DrawableSliderHead> headContainer; private readonly Container<DrawableSliderHead> headContainer;
private readonly Container<DrawableSliderTail> tailContainer; 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<Vector2> positionBindable = new Bindable<Vector2>();
private readonly IBindable<int> stackHeightBindable = new Bindable<int>(); private readonly IBindable<int> stackHeightBindable = new Bindable<int>();
private readonly IBindable<float> scaleBindable = new Bindable<float>(); 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) public DrawableSlider(Slider s)
: base(s) : base(s)
@ -51,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
InternalChildren = new Drawable[] 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 }, tickContainer = new Container<DrawableSliderTick> { RelativeSizeAxes = Axes.Both },
repeatContainer = new Container<DrawableRepeatPoint> { RelativeSizeAxes = Axes.Both }, repeatContainer = new Container<DrawableRepeatPoint> { RelativeSizeAxes = Axes.Both },
Ball = new SliderBall(s, this) Ball = new SliderBall(s, this)
@ -70,28 +67,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
config?.BindWith(OsuRulesetSetting.SnakingInSliders, Body.SnakingIn);
config?.BindWith(OsuRulesetSetting.SnakingOutSliders, Body.SnakingOut);
positionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition); positionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
stackHeightBindable.BindValueChanged(_ => Position = HitObject.StackedPosition); stackHeightBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
scaleBindable.BindValueChanged(scale => scaleBindable.BindValueChanged(scale => Ball.Scale = new Vector2(scale.NewValue));
{
updatePathRadius();
Ball.Scale = new Vector2(scale.NewValue);
});
positionBindable.BindTo(HitObject.PositionBindable); positionBindable.BindTo(HitObject.PositionBindable);
stackHeightBindable.BindTo(HitObject.StackHeightBindable); stackHeightBindable.BindTo(HitObject.StackHeightBindable);
scaleBindable.BindTo(HitObject.ScaleBindable); scaleBindable.BindTo(HitObject.ScaleBindable);
pathVersion.BindTo(slider.Path.Version);
pathVersion.BindValueChanged(_ => Body.Refresh());
AccentColour.BindValueChanged(colour => AccentColour.BindValueChanged(colour =>
{ {
Body.AccentColour = colour.NewValue;
foreach (var drawableHitObject in NestedHitObjects) foreach (var drawableHitObject in NestedHitObjects)
drawableHitObject.AccentColour.Value = colour.NewValue; drawableHitObject.AccentColour.Value = colour.NewValue;
}, true); }, true);
@ -169,16 +154,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
double completionProgress = Math.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1); double completionProgress = Math.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1);
Ball.UpdateProgress(completionProgress); Ball.UpdateProgress(completionProgress);
Body.UpdateProgress(completionProgress); sliderBody?.UpdateProgress(completionProgress);
foreach (DrawableHitObject hitObject in NestedHitObjects) 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; if (hitObject is IRequireTracking t) t.Tracking = Ball.Tracking;
} }
Size = Body.Size; Size = sliderBody?.Size ?? Vector2.Zero;
OriginPosition = Body.PathOffset; OriginPosition = sliderBody?.PathOffset ?? Vector2.Zero;
if (DrawSize != Vector2.Zero) if (DrawSize != Vector2.Zero)
{ {
@ -192,28 +177,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public override void OnKilled() public override void OnKilled()
{ {
base.OnKilled(); base.OnKilled();
Body.RecyclePath(); sliderBody?.RecyclePath();
} }
private float sliderPathRadius;
protected override void ApplySkin(ISkinSource skin, bool allowFallback) protected override void ApplySkin(ISkinSource skin, bool allowFallback)
{ {
base.ApplySkin(skin, 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; bool allowBallTint = skin.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.AllowSliderBallTint)?.Value ?? false;
Ball.Colour = allowBallTint ? AccentColour.Value : Color4.White; Ball.Colour = allowBallTint ? AccentColour.Value : Color4.White;
} }
private void updatePathRadius() => Body.PathRadius = slider.Scale * sliderPathRadius;
protected override void CheckForResult(bool userTriggered, double timeOffset) protected override void CheckForResult(bool userTriggered, double timeOffset)
{ {
if (userTriggered || Time.Current < slider.EndTime) if (userTriggered || Time.Current < slider.EndTime)
@ -264,6 +238,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public Drawable ProxiedLayer => HeadCircle.ProxiedLayer; 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
{
}
} }
} }

View File

@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public readonly SpinnerDisc Disc; public readonly SpinnerDisc Disc;
public readonly SpinnerTicks Ticks; public readonly SpinnerTicks Ticks;
private readonly SpinnerSpmCounter spmCounter; public readonly SpinnerSpmCounter SpmCounter;
private readonly Container mainContainer; private readonly Container mainContainer;
@ -110,7 +110,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
}, },
} }
}, },
spmCounter = new SpinnerSpmCounter SpmCounter = new SpinnerSpmCounter
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
@ -177,8 +177,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected override void Update() protected override void Update()
{ {
Disc.Tracking = OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false; Disc.Tracking = OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false;
if (!spmCounter.IsPresent && Disc.Tracking) if (!SpmCounter.IsPresent && Disc.Tracking)
spmCounter.FadeIn(HitObject.TimeFadeIn); SpmCounter.FadeIn(HitObject.TimeFadeIn);
base.Update(); base.Update();
} }
@ -189,7 +189,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
circle.Rotation = Disc.Rotation; circle.Rotation = Disc.Rotation;
Ticks.Rotation = Disc.Rotation; Ticks.Rotation = Disc.Rotation;
spmCounter.SetRotation(Disc.RotationAbsolute); SpmCounter.SetRotation(Disc.RotationAbsolute);
float relativeCircleScale = Spinner.Scale * circle.DrawHeight / mainContainer.DrawHeight; float relativeCircleScale = Spinner.Scale * circle.DrawHeight / mainContainer.DrawHeight;
Disc.ScaleTo(relativeCircleScale + (1 - relativeCircleScale) * Progress, 200, Easing.OutQuint); Disc.ScaleTo(relativeCircleScale + (1 - relativeCircleScale) * Progress, 200, Easing.OutQuint);

View File

@ -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;
}
}

View File

@ -0,0 +1,52 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using 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;
}
}

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Lines; using osu.Framework.Graphics.Lines;
using osuTK; using osuTK;
@ -12,9 +13,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{ {
public abstract class SliderBody : CompositeDrawable public abstract class SliderBody : CompositeDrawable
{ {
public const float DEFAULT_BORDER_SIZE = 1; private DrawableSliderPath path;
private SliderPath path;
protected Path Path => path; protected Path Path => path;
@ -80,19 +79,19 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
} }
/// <summary> /// <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> /// </summary>
public virtual void RecyclePath() public virtual void RecyclePath()
{ {
InternalChild = path = new SliderPath InternalChild = path = CreateSliderPath().With(p =>
{ {
Position = path?.Position ?? Vector2.Zero, p.Position = path?.Position ?? Vector2.Zero;
PathRadius = path?.PathRadius ?? 10, p.PathRadius = path?.PathRadius ?? 10;
AccentColour = path?.AccentColour ?? Color4.White, p.AccentColour = path?.AccentColour ?? Color4.White;
BorderColour = path?.BorderColour ?? Color4.White, p.BorderColour = path?.BorderColour ?? Color4.White;
BorderSize = path?.BorderSize ?? DEFAULT_BORDER_SIZE, p.BorderSize = path?.BorderSize ?? 1;
Vertices = path?.Vertices ?? Array.Empty<Vector2>() p.Vertices = path?.Vertices ?? Array.Empty<Vector2>();
}; });
} }
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => path.ReceivePositionalInputAt(screenSpacePos); 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> /// <param name="vertices">The vertices</param>
protected void SetVertices(IReadOnlyList<Vector2> vertices) => path.Vertices = vertices; 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_centre = 0.3f;
private const float opacity_at_edge = 0.8f; 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) protected override Color4 ColourAt(float position)
{ {
if (calculatedBorderPortion != 0f && position <= calculatedBorderPortion) if (CalculatedBorderPortion != 0f && position <= CalculatedBorderPortion)
return BorderColour; return BorderColour;
position -= calculatedBorderPortion; 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); return new Color4(AccentColour.R, AccentColour.G, AccentColour.B, (opacity_at_edge - (opacity_at_edge - opacity_at_centre) * position / GRADIENT_PORTION) * AccentColour.A);
} }
} }
} }

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osuTK; using osuTK;
@ -50,16 +51,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
/// </summary> /// </summary>
private Vector2 snakedPathOffset; private Vector2 snakedPathOffset;
private readonly Slider slider; private Slider slider;
public SnakingSliderBody(Slider slider)
{
this.slider = slider;
}
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load(DrawableHitObject drawableObject)
{ {
slider = (Slider)drawableObject.HitObject;
Refresh(); Refresh();
} }

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.MathUtils;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
@ -62,6 +63,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
public void SetRotation(float currentRotation) 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 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) if (records.Count > 0 && Time.Current < records.Last().Time)
records.Clear(); records.Clear();
@ -71,6 +76,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
var record = records.Peek(); var record = records.Peek();
while (records.Count > 0 && Time.Current - records.Peek().Time > spm_count_duration) while (records.Count > 0 && Time.Current - records.Peek().Time > spm_count_duration)
record = records.Dequeue(); record = records.Dequeue();
SpinsPerMinute = (currentRotation - record.Rotation) / (Time.Current - record.Time) * 1000 * 60 / 360; SpinsPerMinute = (currentRotation - record.Rotation) / (Time.Current - record.Time) * 1000 * 60 / 360;
} }

View File

@ -23,16 +23,23 @@ using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Configuration; using osu.Game.Rulesets.Osu.Configuration;
using osu.Game.Rulesets.Osu.Difficulty; using osu.Game.Rulesets.Osu.Difficulty;
using osu.Game.Rulesets.Osu.Scoring;
using osu.Game.Rulesets.Osu.Skinning; using osu.Game.Rulesets.Osu.Skinning;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring; using osu.Game.Scoring;
using osu.Game.Skinning; using osu.Game.Skinning;
using System;
namespace osu.Game.Rulesets.Osu namespace osu.Game.Rulesets.Osu
{ {
public class OsuRuleset : Ruleset public class OsuRuleset : Ruleset
{ {
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => new DrawableOsuRuleset(this, beatmap, mods); 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 IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap);
public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new OsuBeatmapProcessor(beatmap); public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new OsuBeatmapProcessor(beatmap);
public const string SHORT_NAME = "osu"; public const string SHORT_NAME = "osu";
@ -60,7 +67,9 @@ namespace osu.Game.Rulesets.Osu
if (mods.HasFlag(LegacyMods.Autopilot)) if (mods.HasFlag(LegacyMods.Autopilot))
yield return new OsuModAutopilot(); 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(); yield return new OsuModAutoplay();
if (mods.HasFlag(LegacyMods.Easy)) if (mods.HasFlag(LegacyMods.Easy))
@ -126,7 +135,7 @@ namespace osu.Game.Rulesets.Osu
case ModType.Automation: case ModType.Automation:
return new Mod[] return new Mod[]
{ {
new MultiMod(new OsuModAutoplay(), new ModCinema()), new MultiMod(new OsuModAutoplay(), new OsuModCinema()),
new OsuModRelax(), new OsuModRelax(),
new OsuModAutopilot(), new OsuModAutopilot(),
}; };
@ -149,7 +158,7 @@ namespace osu.Game.Rulesets.Osu
}; };
default: default:
return new Mod[] { }; return Array.Empty<Mod>();
} }
} }

View File

@ -14,6 +14,7 @@ namespace osu.Game.Rulesets.Osu
ReverseArrow, ReverseArrow,
HitCircleText, HitCircleText,
SliderFollowCircle, SliderFollowCircle,
SliderBall SliderBall,
SliderBody,
} }
} }

View File

@ -156,9 +156,9 @@ namespace osu.Game.Rulesets.Osu.Replays
// TODO: Shouldn't the spinner always spin in the same direction? // TODO: Shouldn't the spinner always spin in the same direction?
if (h is Spinner) 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) if (spinCentreOffset.Length > SPIN_RADIUS)
{ {
@ -230,7 +230,7 @@ namespace osu.Game.Rulesets.Osu.Replays
private void moveToHitObject(OsuHitObject h, Vector2 targetPos, Easing easing) 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. // Wait until Auto could "see and react" to the next note.
double waitTime = h.StartTime - Math.Max(0.0, h.TimePreempt - reactionTime); 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! // 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); AddFrameToReplay(endFrame);
} }

View 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);
}
}
}
}

View File

@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
/// Their hittable area is 128px, but the actual circle portion is 118px. /// 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. /// We must account for some gameplay elements such as slider bodies, where this padding is not present.
/// </summary> /// </summary>
private const float legacy_circle_radius = 64 - 5; public const float LEGACY_CIRCLE_RADIUS = 64 - 5;
public OsuLegacySkinTransformer(ISkinSource source) public OsuLegacySkinTransformer(ISkinSource source)
{ {
@ -73,6 +73,12 @@ namespace osu.Game.Rulesets.Osu.Skinning
return null; return null;
case OsuSkinComponents.SliderBody:
if (hasHitCircle.Value)
return new LegacySliderBody();
return null;
case OsuSkinComponents.HitCircle: case OsuSkinComponents.HitCircle:
if (hasHitCircle.Value) if (hasHitCircle.Value)
return new LegacyMainCirclePiece(); return new LegacyMainCirclePiece();
@ -124,7 +130,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
{ {
case OsuSkinConfiguration.SliderPathRadius: case OsuSkinConfiguration.SliderPathRadius:
if (hasHitCircle.Value) if (hasHitCircle.Value)
return SkinUtils.As<TValue>(new BindableFloat(legacy_circle_radius)); return SkinUtils.As<TValue>(new BindableFloat(LEGACY_CIRCLE_RADIUS));
break; break;
} }

View File

@ -227,6 +227,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
shader.Bind(); shader.Bind();
shader.GetUniform<float>("g_FadeClock").UpdateValue(ref time); shader.GetUniform<float>("g_FadeClock").UpdateValue(ref time);
texture.TextureGL.Bind();
RectangleF textureRect = texture.GetTextureRect(); RectangleF textureRect = texture.GetTextureRect();
foreach (var part in parts) foreach (var part in parts)

View File

@ -14,8 +14,6 @@ using osu.Game.Rulesets.Osu.Configuration;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.Replays; 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.Rulesets.UI;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osuTK; 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 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 Playfield CreatePlayfield() => new OsuPlayfield();
protected override PassThroughInputManager CreateInputManager() => new OsuInputManager(Ruleset.RulesetInfo); protected override PassThroughInputManager CreateInputManager() => new OsuInputManager(Ruleset.RulesetInfo);

View 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(),
};
}
}

View File

@ -105,19 +105,19 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
} }
} }
public abstract class DrawableTaikoHitObject<TaikoHitType> : DrawableTaikoHitObject public abstract class DrawableTaikoHitObject<TTaikoHit> : DrawableTaikoHitObject
where TaikoHitType : TaikoHitObject where TTaikoHit : TaikoHitObject
{ {
public override Vector2 OriginPosition => new Vector2(DrawHeight / 2); public override Vector2 OriginPosition => new Vector2(DrawHeight / 2);
public new TaikoHitType HitObject; public new TTaikoHit HitObject;
protected readonly Vector2 BaseSize; protected readonly Vector2 BaseSize;
protected readonly TaikoPiece MainPiece; protected readonly TaikoPiece MainPiece;
private readonly Container<DrawableStrongNestedHit> strongHitContainer; private readonly Container<DrawableStrongNestedHit> strongHitContainer;
protected DrawableTaikoHitObject(TaikoHitType hitObject) protected DrawableTaikoHitObject(TTaikoHit hitObject)
: base(hitObject) : base(hitObject)
{ {
HitObject = hitObject; HitObject = hitObject;

View File

@ -15,15 +15,21 @@ using osu.Game.Rulesets.Replays.Types;
using osu.Game.Rulesets.Taiko.Replays; using osu.Game.Rulesets.Taiko.Replays;
using osu.Game.Beatmaps.Legacy; using osu.Game.Beatmaps.Legacy;
using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Beatmaps; using osu.Game.Rulesets.Taiko.Beatmaps;
using osu.Game.Rulesets.Taiko.Difficulty; using osu.Game.Rulesets.Taiko.Difficulty;
using osu.Game.Rulesets.Taiko.Scoring;
using osu.Game.Scoring; using osu.Game.Scoring;
using System;
namespace osu.Game.Rulesets.Taiko namespace osu.Game.Rulesets.Taiko
{ {
public class TaikoRuleset : Ruleset public class TaikoRuleset : Ruleset
{ {
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => new DrawableTaikoRuleset(this, beatmap, mods); 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 override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(beatmap);
public const string SHORT_NAME = "taiko"; public const string SHORT_NAME = "taiko";
@ -50,7 +56,9 @@ namespace osu.Game.Rulesets.Taiko
else if (mods.HasFlag(LegacyMods.SuddenDeath)) else if (mods.HasFlag(LegacyMods.SuddenDeath))
yield return new TaikoModSuddenDeath(); 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(); yield return new TaikoModAutoplay();
if (mods.HasFlag(LegacyMods.Easy)) if (mods.HasFlag(LegacyMods.Easy))
@ -100,7 +108,7 @@ namespace osu.Game.Rulesets.Taiko
case ModType.Automation: case ModType.Automation:
return new Mod[] return new Mod[]
{ {
new MultiMod(new TaikoModAutoplay(), new ModCinema()), new MultiMod(new TaikoModAutoplay(), new TaikoModCinema()),
new TaikoModRelax(), new TaikoModRelax(),
}; };
@ -111,7 +119,7 @@ namespace osu.Game.Rulesets.Taiko
}; };
default: default:
return new Mod[] { }; return Array.Empty<Mod>();
} }
} }

View File

@ -5,10 +5,8 @@ using System.Collections.Generic;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Objects.Drawables; using osu.Game.Rulesets.Taiko.Objects.Drawables;
using osu.Game.Rulesets.Taiko.Scoring;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.Taiko.Replays; using osu.Game.Rulesets.Taiko.Replays;
using osu.Framework.Input; 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))); 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(); public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new TaikoPlayfieldAdjustmentContainer();
protected override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo); protected override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo);

View File

@ -45,7 +45,7 @@ namespace osu.Game.Tests.NonVisual
cpi.Add(1000, new DifficultyControlPoint()); // is redundant cpi.Add(1000, new DifficultyControlPoint()); // is redundant
Assert.That(cpi.Groups.Count, Is.EqualTo(0)); 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)); Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(0));
cpi.Add(1000, new DifficultyControlPoint { SpeedMultiplier = 2 }); // is not redundant cpi.Add(1000, new DifficultyControlPoint { SpeedMultiplier = 2 }); // is not redundant
@ -60,18 +60,18 @@ namespace osu.Game.Tests.NonVisual
{ {
var cpi = new ControlPointInfo(); 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 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.Groups.Count, Is.EqualTo(1));
Assert.That(cpi.SamplePoints.Count, Is.EqualTo(1)); Assert.That(cpi.SamplePoints.Count, Is.EqualTo(1));
Assert.That(cpi.AllControlPoints.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] [Test]
@ -83,7 +83,7 @@ namespace osu.Game.Tests.NonVisual
cpi.Add(1000, new EffectControlPoint()); // is redundant cpi.Add(1000, new EffectControlPoint()); // is redundant
Assert.That(cpi.Groups.Count, Is.EqualTo(0)); 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)); Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(0));
cpi.Add(1000, new EffectControlPoint { KiaiMode = true }); // is not redundant cpi.Add(1000, new EffectControlPoint { KiaiMode = true }); // is not redundant

View File

@ -25,7 +25,7 @@ namespace osu.Game.Tests.NonVisual
{ {
Assert.AreEqual(0, stack.Count); Assert.AreEqual(0, stack.Count);
Assert.Throws<IndexOutOfRangeException>(() => Assert.Throws<ArgumentOutOfRangeException>(() =>
{ {
int unused = stack[0]; int unused = stack[0];
}); });
@ -55,7 +55,7 @@ namespace osu.Game.Tests.NonVisual
// e.g. indices 3, 4, 5, 6 (out of range) // e.g. indices 3, 4, 5, 6 (out of range)
for (int i = stack.Count; i < stack.Count + capacity; i++) for (int i = stack.Count; i < stack.Count + capacity; i++)
{ {
Assert.Throws<IndexOutOfRangeException>(() => Assert.Throws<ArgumentOutOfRangeException>(() =>
{ {
int unused = stack[i]; int unused = stack[i];
}); });
@ -80,7 +80,7 @@ namespace osu.Game.Tests.NonVisual
// e.g. indices 3, 4, 5, 6 (out of range) // e.g. indices 3, 4, 5, 6 (out of range)
for (int i = stack.Count; i < stack.Count + capacity; i++) for (int i = stack.Count; i < stack.Count + capacity; i++)
{ {
Assert.Throws<IndexOutOfRangeException>(() => Assert.Throws<ArgumentOutOfRangeException>(() =>
{ {
int unused = stack[i]; int unused = stack[i];
}); });

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -10,6 +11,7 @@ using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.IO.Archives;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods; 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>(); var beatmapManager = osu.Dependencies.Get<BeatmapManager>();
@ -165,7 +190,7 @@ namespace osu.Game.Tests.Scores.IO
score.Ruleset = new OsuRuleset().RulesetInfo; score.Ruleset = new OsuRuleset().RulesetInfo;
var scoreManager = osu.Dependencies.Get<ScoreManager>(); var scoreManager = osu.Dependencies.Get<ScoreManager>();
await scoreManager.Import(score); await scoreManager.Import(score, archive);
return scoreManager.GetAllUsableScores().FirstOrDefault(); return scoreManager.GetAllUsableScores().FirstOrDefault();
} }
@ -196,5 +221,23 @@ namespace osu.Game.Tests.Scores.IO
Assert.IsTrue(task.Wait(timeout), failureMessage); 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();
}
} }
} }

View File

@ -88,6 +88,20 @@ namespace osu.Game.Tests.Visual.Background
AddUntilStep("not lightened", () => userDimContainer.DimEqual(test_user_dim)); 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 private class TestUserDimContainer : UserDimContainer
{ {
public bool DimEqual(float expectedDimLevel) => Content.Colour == OsuColour.Gray(1f - expectedDimLevel); public bool DimEqual(float expectedDimLevel) => Content.Colour == OsuColour.Gray(1f - expectedDimLevel);

View 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);
});
}
}
}

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -31,7 +32,7 @@ namespace osu.Game.Tests.Visual.Gameplay
requestCount = 0; requestCount = 0;
increment = skip_time; 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, RelativeSizeAxes = Axes.Both,
Children = new Drawable[] Children = new Drawable[]

View File

@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
}); });
AddStep(@"set max", () => Room.MaxParticipants.Value = 10); 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); AddStep(@"set max to null", () => Room.MaxParticipants.Value = null);
} }
} }

View File

@ -44,7 +44,7 @@ namespace osu.Game.Tests.Visual.Online
AddStep("set second set", () => details.BeatmapSet = secondSet); AddStep("set second set", () => details.BeatmapSet = secondSet);
AddAssert("ratings set", () => details.Ratings.Metrics == secondSet.Metrics); 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() }, Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).Select(_ => RNG.Next(10)).ToArray() },
Beatmaps = new List<BeatmapInfo> Beatmaps = new List<BeatmapInfo>

View File

@ -1,23 +1,66 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Graphics;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Overlays.News;
namespace osu.Game.Tests.Visual.Online namespace osu.Game.Tests.Visual.Online
{ {
public class TestSceneNewsOverlay : OsuTestScene public class TestSceneNewsOverlay : OsuTestScene
{ {
private NewsOverlay news; private TestNewsOverlay news;
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();
Add(news = new NewsOverlay()); Add(news = new TestNewsOverlay());
AddStep(@"Show", news.Show); AddStep(@"Show", news.Show);
AddStep(@"Hide", news.Hide); AddStep(@"Hide", news.Hide);
AddStep(@"Show front page", () => news.ShowFrontPage()); AddStep(@"Show front page", () => news.ShowFrontPage());
AddStep(@"Custom article", () => news.Current.Value = "Test Article 101"); 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),
}
};
}
} }
} }
} }

View 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);
}
}
}

View File

@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual.Online
}, },
Title = "osu!volunteer", Title = "osu!volunteer",
Colour = "ff0000", Colour = "ff0000",
Achievements = new User.UserAchievement[0], Achievements = Array.Empty<User.UserAchievement>(),
}; };
public TestSceneUserProfileOverlay() public TestSceneUserProfileOverlay()

View File

@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Online
new User { PreviousUsernames = new[] { "longusername", "longerusername" } }, new User { PreviousUsernames = new[] { "longusername", "longerusername" } },
new User { PreviousUsernames = new[] { "test", "angelsim", "verylongusername" } }, new User { PreviousUsernames = new[] { "test", "angelsim", "verylongusername" } },
new User { PreviousUsernames = new[] { "ihavenoidea", "howcani", "makethistext", "anylonger" } }, new User { PreviousUsernames = new[] { "ihavenoidea", "howcani", "makethistext", "anylonger" } },
new User { PreviousUsernames = new string[0] }, new User { PreviousUsernames = Array.Empty<string>() },
null null
}; };

View File

@ -111,7 +111,7 @@ namespace osu.Game.Tests.Visual
foreach (var type in requiredGameDependencies) foreach (var type in requiredGameDependencies)
{ {
if (game.Dependencies.Get(type) == null) if (game.Dependencies.Get(type) == null)
throw new Exception($"{type} has not been cached"); throw new InvalidOperationException($"{type} has not been cached");
} }
return true; return true;
@ -121,7 +121,7 @@ namespace osu.Game.Tests.Visual
foreach (var type in requiredGameBaseDependencies) foreach (var type in requiredGameBaseDependencies)
{ {
if (gameBase.Dependencies.Get(type) == null) if (gameBase.Dependencies.Get(type) == null)
throw new Exception($"{type} has not been cached"); throw new InvalidOperationException($"{type} has not been cached");
} }
return true; return true;

View File

@ -157,7 +157,7 @@ namespace osu.Game.Tests.Visual.UserInterface
private TimingControlPoint getNextTimingPoint(TimingControlPoint current) private TimingControlPoint getNextTimingPoint(TimingControlPoint current)
{ {
if (timingPoints[timingPoints.Count - 1] == current) if (timingPoints[^1] == current)
return current; return current;
int index = timingPoints.IndexOf(current); // -1 means that this is a "default beat" 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.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((Beatmap.Value.Track.Length - current.Time) / current.BeatLength);
return (int)Math.Ceiling((getNextTimingPoint(current).Time - current.Time) / current.BeatLength); return (int)Math.Ceiling((getNextTimingPoint(current).Time - current.Time) / current.BeatLength);

View File

@ -49,7 +49,7 @@ namespace osu.Game.Tournament.Screens.Editors
get get
{ {
if (editorInfo == null) if (editorInfo == null)
return new MenuItem[0]; return Array.Empty<MenuItem>();
return new MenuItem[] return new MenuItem[]
{ {

View File

@ -192,7 +192,7 @@ namespace osu.Game.Tournament.Screens.Ladder.Components
get get
{ {
if (editorInfo == null) if (editorInfo == null)
return new MenuItem[0]; return Array.Empty<MenuItem>();
return new MenuItem[] return new MenuItem[]
{ {

View File

@ -98,7 +98,7 @@ namespace osu.Game.Beatmaps
{ {
if (string.IsNullOrEmpty(value)) if (string.IsNullOrEmpty(value))
{ {
Bookmarks = new int[0]; Bookmarks = Array.Empty<int>();
return; return;
} }
@ -111,7 +111,7 @@ namespace osu.Game.Beatmaps
} }
[NotMapped] [NotMapped]
public int[] Bookmarks { get; set; } = new int[0]; public int[] Bookmarks { get; set; } = Array.Empty<int>();
public double DistanceSpacing { get; set; } public double DistanceSpacing { get; set; }
public int BeatDivisor { get; set; } public int BeatDivisor { get; set; }

View File

@ -158,7 +158,9 @@ namespace osu.Game.Beatmaps
void resetIds() => beatmapSet.Beatmaps.ForEach(b => b.OnlineBeatmapID = null); 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> /// <summary>
/// Delete a beatmap difficulty. /// Delete a beatmap difficulty.

View File

@ -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; private TextureStore textureStore;

View File

@ -195,8 +195,8 @@ namespace osu.Game.Beatmaps.ControlPoints
if (time < list[0].Time) if (time < list[0].Time)
return null; return null;
if (time >= list[list.Count - 1].Time) if (time >= list[^1].Time)
return list[list.Count - 1]; return list[^1];
int l = 0; int l = 0;
int r = list.Count - 2; int r = list.Count - 2;
@ -239,7 +239,7 @@ namespace osu.Game.Beatmaps.ControlPoints
break; break;
case SampleControlPoint _: case SampleControlPoint _:
existing = SamplePointAt(time); existing = binarySearch(SamplePoints, time);
break; break;
case DifficultyControlPoint _: case DifficultyControlPoint _:

View File

@ -70,6 +70,6 @@ namespace osu.Game.Beatmaps.ControlPoints
public override bool EquivalentTo(ControlPoint other) => public override bool EquivalentTo(ControlPoint other) =>
other is SampleControlPoint otherTyped && other is SampleControlPoint otherTyped &&
string.Equals(SampleBank, otherTyped.SampleBank) && SampleVolume == otherTyped.SampleVolume; SampleBank == otherTyped.SampleBank && SampleVolume == otherTyped.SampleVolume;
} }
} }

View File

@ -55,7 +55,7 @@ namespace osu.Game.Beatmaps
private class DummyRuleset : Ruleset 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) public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null)
{ {

View File

@ -34,7 +34,7 @@ namespace osu.Game.Beatmaps.Formats
if (line.StartsWith(@"[", StringComparison.Ordinal) && line.EndsWith(@"]", StringComparison.Ordinal)) 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}\""); Logger.Log($"Unknown section \"{line}\" in \"{output}\"");
section = Section.None; section = Section.None;

View File

@ -176,7 +176,7 @@ namespace osu.Game.Beatmaps.Formats
{ {
var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue; 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; break;
} }
@ -186,7 +186,7 @@ namespace osu.Game.Beatmaps.Formats
var startY = float.Parse(split[5], CultureInfo.InvariantCulture); var startY = float.Parse(split[5], CultureInfo.InvariantCulture);
var endX = split.Length > 6 ? float.Parse(split[6], CultureInfo.InvariantCulture) : startX; var endX = split.Length > 6 ? float.Parse(split[6], CultureInfo.InvariantCulture) : startX;
var endY = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startY; 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; break;
} }

View File

@ -33,7 +33,8 @@ namespace osu.Game.Database
private readonly MutableDatabaseBackedStoreWithFileIncludes<TModel, TFileModel> modelStore; 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) : base(storage, contextFactory, modelStore, importHost)
{ {
this.api = api; 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="model">The <typeparamref name="TModel"/> whose existence needs to be checked.</param>
/// <param name="items">The usable items present in the store.</param> /// <param name="items">The usable items present in the store.</param>
/// <returns>Whether the <typeparamref name="TModel"/> exists.</returns> /// <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)); public ArchiveDownloadRequest<TModel> GetExistingDownload(TModel model) => currentDownloads.Find(r => r.Model.Equals(model));

View File

@ -7,9 +7,9 @@ using osu.Framework.Platform;
namespace osu.Game.Database namespace osu.Game.Database
{ {
public abstract class MutableDatabaseBackedStoreWithFileIncludes<T, U> : MutableDatabaseBackedStore<T> public abstract class MutableDatabaseBackedStoreWithFileIncludes<T, TFileInfo> : MutableDatabaseBackedStore<T>
where T : class, IHasPrimaryKey, ISoftDelete, IHasFiles<U> where T : class, IHasPrimaryKey, ISoftDelete, IHasFiles<TFileInfo>
where U : INamedFileInfo where TFileInfo : INamedFileInfo
{ {
protected MutableDatabaseBackedStoreWithFileIncludes(IDatabaseContextFactory contextFactory, Storage storage = null) protected MutableDatabaseBackedStoreWithFileIncludes(IDatabaseContextFactory contextFactory, Storage storage = null)
: base(contextFactory, storage) : base(contextFactory, storage)

View File

@ -37,7 +37,7 @@ namespace osu.Game.Graphics.Containers
foreach (var link in links) 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); AddLink(text.Substring(link.Index, link.Length), link.Action, link.Argument ?? link.Url);
previousLinkEnd = link.Index + link.Length; previousLinkEnd = link.Index + link.Length;
} }

View File

@ -83,6 +83,15 @@ namespace osu.Game.Graphics.Containers
return base.OnDragEnd(e); 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 override ScrollbarContainer CreateScrollbar(Direction direction) => new OsuScrollbar(direction);
protected class OsuScrollbar : ScrollbarContainer protected class OsuScrollbar : ScrollbarContainer

View File

@ -28,6 +28,11 @@ namespace osu.Game.Graphics.Containers
/// </summary> /// </summary>
public readonly Bindable<bool> EnableUserDim = new Bindable<bool>(true); 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> /// <summary>
/// Whether or not the storyboard loaded should completely hide the background behind it. /// Whether or not the storyboard loaded should completely hide the background behind it.
/// </summary> /// </summary>
@ -53,7 +58,8 @@ namespace osu.Game.Graphics.Containers
protected Bindable<bool> ShowVideo { get; private set; } protected Bindable<bool> ShowVideo { get; private set; }
private float breakLightening => LightenDuringBreaks.Value && IsBreakTime.Value ? BREAK_LIGHTEN_AMOUNT : 0; 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; protected override Container<Drawable> Content => dimContent;
@ -82,6 +88,7 @@ namespace osu.Game.Graphics.Containers
ShowStoryboard.ValueChanged += _ => UpdateVisuals(); ShowStoryboard.ValueChanged += _ => UpdateVisuals();
ShowVideo.ValueChanged += _ => UpdateVisuals(); ShowVideo.ValueChanged += _ => UpdateVisuals();
StoryboardReplacesBackground.ValueChanged += _ => UpdateVisuals(); StoryboardReplacesBackground.ValueChanged += _ => UpdateVisuals();
IgnoreUserSettings.ValueChanged += _ => UpdateVisuals();
} }
protected override void LoadComplete() protected override void LoadComplete()

View File

@ -119,7 +119,7 @@ namespace osu.Game.Graphics
break; break;
default: default:
throw new ArgumentOutOfRangeException(nameof(screenshotFormat)); throw new InvalidOperationException($"Unknown enum member {nameof(ScreenshotFormat)} {screenshotFormat.Value}.");
} }
notificationOverlay.Post(new SimpleNotification notificationOverlay.Post(new SimpleNotification

View File

@ -7,15 +7,15 @@ using osu.Framework.Graphics.UserInterface;
namespace osu.Game.Graphics.UserInterfaceV2 namespace osu.Game.Graphics.UserInterfaceV2
{ {
public abstract class LabelledComponent<T, U> : LabelledDrawable<T>, IHasCurrentValue<U> public abstract class LabelledComponent<TDrawable, TValue> : LabelledDrawable<TDrawable>, IHasCurrentValue<TValue>
where T : Drawable, IHasCurrentValue<U> where TDrawable : Drawable, IHasCurrentValue<TValue>
{ {
protected LabelledComponent(bool padded) protected LabelledComponent(bool padded)
: base(padded) : base(padded)
{ {
} }
public Bindable<U> Current public Bindable<TValue> Current
{ {
get => Component.Current; get => Component.Current;
set => Component.Current = value; set => Component.Current = value;

View File

@ -116,13 +116,13 @@ namespace osu.Game.IO.Legacy
} }
/// <summary> Reads a generic Dictionary from the buffer. </summary> /// <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(); int count = ReadInt32();
if (count < 0) return null; if (count < 0) return null;
IDictionary<T, U> d = new Dictionary<T, U>(); IDictionary<TKey, TValue> d = new Dictionary<TKey, TValue>();
for (int i = 0; i < count; i++) d[(T)ReadObject()] = (U)ReadObject(); for (int i = 0; i < count; i++) d[(TKey)ReadObject()] = (TValue)ReadObject();
return d; return d;
} }
@ -192,7 +192,7 @@ namespace osu.Game.IO.Legacy
} }
} }
public class DynamicDeserializer public static class DynamicDeserializer
{ {
private static VersionConfigToNamespaceAssemblyObjectBinder versionBinder; private static VersionConfigToNamespaceAssemblyObjectBinder versionBinder;
private static BinaryFormatter formatter; private static BinaryFormatter formatter;

View File

@ -102,7 +102,7 @@ namespace osu.Game.IO.Legacy
} }
/// <summary> Writes a generic IDictionary to the buffer. </summary> /// <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) if (d == null)
{ {
@ -112,7 +112,7 @@ namespace osu.Game.IO.Legacy
{ {
Write(d.Count); Write(d.Count);
foreach (KeyValuePair<T, U> kvp in d) foreach (KeyValuePair<TKey, TValue> kvp in d)
{ {
WriteObject(kvp.Key); WriteObject(kvp.Key);
WriteObject(kvp.Value); WriteObject(kvp.Value);

View File

@ -10,6 +10,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions.ExceptionExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Game.Configuration; using osu.Game.Configuration;
@ -152,6 +153,10 @@ namespace osu.Game.Online.API
userReq.Success += u => userReq.Success += u =>
{ {
LocalUser.Value = u; LocalUser.Value = u;
// todo: save/pull from settings
LocalUser.Value.Status.Value = new UserStatusOnline();
failureCount = 0; failureCount = 0;
//we're connected! //we're connected!
@ -249,7 +254,7 @@ namespace osu.Game.Online.API
catch catch
{ {
// if we couldn't deserialize the error message let's throw the original exception outwards. // if we couldn't deserialize the error message let's throw the original exception outwards.
throw e; e.Rethrow();
} }
} }

View File

@ -122,7 +122,7 @@ namespace osu.Game.Online.API
// attempt to decode a displayable error string. // attempt to decode a displayable error string.
var error = JsonConvert.DeserializeObject<DisplayableError>(responseString); var error = JsonConvert.DeserializeObject<DisplayableError>(responseString);
if (error != null) if (error != null)
e = new Exception(error.ErrorMessage, e); e = new APIException(error.ErrorMessage, e);
} }
catch 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 APIFailureHandler(Exception e);
public delegate void APISuccessHandler(); public delegate void APISuccessHandler();

View File

@ -19,7 +19,7 @@ using osuTK.Graphics;
namespace osu.Game.Online.Leaderboards 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; private const double fade_duration = 300;
@ -39,9 +39,9 @@ namespace osu.Game.Online.Leaderboards
protected override Container<Drawable> Content => content; protected override Container<Drawable> Content => content;
private IEnumerable<ScoreInfo> scores; private IEnumerable<TScoreInfo> scores;
public IEnumerable<ScoreInfo> Scores public IEnumerable<TScoreInfo> Scores
{ {
get => scores; get => scores;
set set
@ -288,7 +288,7 @@ namespace osu.Game.Online.Leaderboards
/// </summary> /// </summary>
/// <param name="scoresCallback">A callback which should be called when fetching is completed. Scheduling is not required.</param> /// <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> /// <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; 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);
} }
} }

View File

@ -115,11 +115,7 @@ namespace osu.Game.Overlays.Chat.Selection
Font = OsuFont.GetFont(size: 20), Font = OsuFont.GetFont(size: 20),
Shadow = false, Shadow = false,
}, },
search = new HeaderSearchTextBox search = new HeaderSearchTextBox { RelativeSizeAxes = Axes.X },
{
RelativeSizeAxes = Axes.X,
PlaceholderText = @"Search",
},
}, },
}, },
}, },

View File

@ -116,7 +116,7 @@ namespace osu.Game.Overlays
Filter.Search.Current.ValueChanged += text => Filter.Search.Current.ValueChanged += text =>
{ {
if (text.NewValue != string.Empty) if (!string.IsNullOrEmpty(text.NewValue))
{ {
Header.Tabs.Current.Value = DirectTab.Search; Header.Tabs.Current.Value = DirectTab.Search;

View File

@ -81,7 +81,7 @@ namespace osu.Game.Overlays.Mods
} }
} }
private ModButton[] buttons = { }; private ModButton[] buttons = Array.Empty<ModButton>();
protected override bool OnKeyDown(KeyDownEvent e) protected override bool OnKeyDown(KeyDownEvent e)
{ {

View File

@ -239,7 +239,7 @@ namespace osu.Game.Overlays.Music
private class ItemSearchContainer : FillFlowContainer<PlaylistItem>, IHasFilterableChildren private class ItemSearchContainer : FillFlowContainer<PlaylistItem>, IHasFilterableChildren
{ {
public IEnumerable<string> FilterTerms => new string[] { }; public IEnumerable<string> FilterTerms => Array.Empty<string>();
public bool MatchingFilter public bool MatchingFilter
{ {

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