mirror of
https://github.com/osukey/osukey.git
synced 2025-05-25 23:47:30 +09:00
Merge branch 'master' into fix-mania-long-note-regression
This commit is contained in:
commit
99b78c63a0
@ -17,7 +17,7 @@
|
|||||||
<EmbeddedResource Include="Resources\**\*.*" />
|
<EmbeddedResource Include="Resources\**\*.*" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Code Analysis">
|
<ItemGroup Label="Code Analysis">
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3" PrivateAssets="All" />
|
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.4" PrivateAssets="All" />
|
||||||
<AdditionalFiles Include="$(MSBuildThisFileDirectory)CodeAnalysis\BannedSymbols.txt" />
|
<AdditionalFiles Include="$(MSBuildThisFileDirectory)CodeAnalysis\BannedSymbols.txt" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup Label="Code Analysis">
|
<PropertyGroup Label="Code Analysis">
|
||||||
|
@ -9,9 +9,9 @@
|
|||||||
<GenerateProgramFile>false</GenerateProgramFile>
|
<GenerateProgramFile>false</GenerateProgramFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\osu.Game.Rulesets.EmptyFreeform\osu.Game.Rulesets.EmptyFreeform.csproj" />
|
<ProjectReference Include="..\osu.Game.Rulesets.EmptyFreeform\osu.Game.Rulesets.EmptyFreeform.csproj" />
|
||||||
|
@ -9,9 +9,9 @@
|
|||||||
<GenerateProgramFile>false</GenerateProgramFile>
|
<GenerateProgramFile>false</GenerateProgramFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\osu.Game.Rulesets.Pippidon\osu.Game.Rulesets.Pippidon.csproj" />
|
<ProjectReference Include="..\osu.Game.Rulesets.Pippidon\osu.Game.Rulesets.Pippidon.csproj" />
|
||||||
|
@ -9,9 +9,9 @@
|
|||||||
<GenerateProgramFile>false</GenerateProgramFile>
|
<GenerateProgramFile>false</GenerateProgramFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\osu.Game.Rulesets.EmptyScrolling\osu.Game.Rulesets.EmptyScrolling.csproj" />
|
<ProjectReference Include="..\osu.Game.Rulesets.EmptyScrolling\osu.Game.Rulesets.EmptyScrolling.csproj" />
|
||||||
|
@ -9,9 +9,9 @@
|
|||||||
<GenerateProgramFile>false</GenerateProgramFile>
|
<GenerateProgramFile>false</GenerateProgramFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\osu.Game.Rulesets.Pippidon\osu.Game.Rulesets.Pippidon.csproj" />
|
<ProjectReference Include="..\osu.Game.Rulesets.Pippidon\osu.Game.Rulesets.Pippidon.csproj" />
|
||||||
|
@ -98,7 +98,7 @@ namespace osu.Desktop
|
|||||||
|
|
||||||
if (status.Value is UserStatusOnline && activity.Value != null)
|
if (status.Value is UserStatusOnline && activity.Value != null)
|
||||||
{
|
{
|
||||||
presence.State = truncate(activity.Value.Status);
|
presence.State = truncate(activity.Value.GetStatus(privacyMode.Value == DiscordRichPresenceMode.Limited));
|
||||||
presence.Details = truncate(getDetails(activity.Value));
|
presence.Details = truncate(getDetails(activity.Value));
|
||||||
|
|
||||||
if (getBeatmap(activity.Value) is IBeatmapInfo beatmap && beatmap.OnlineID > 0)
|
if (getBeatmap(activity.Value) is IBeatmapInfo beatmap && beatmap.OnlineID > 0)
|
||||||
@ -169,7 +169,7 @@ namespace osu.Desktop
|
|||||||
case UserActivity.InGame game:
|
case UserActivity.InGame game:
|
||||||
return game.BeatmapInfo;
|
return game.BeatmapInfo;
|
||||||
|
|
||||||
case UserActivity.Editing edit:
|
case UserActivity.EditingBeatmap edit:
|
||||||
return edit.BeatmapInfo;
|
return edit.BeatmapInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,9 +183,12 @@ namespace osu.Desktop
|
|||||||
case UserActivity.InGame game:
|
case UserActivity.InGame game:
|
||||||
return game.BeatmapInfo.ToString() ?? string.Empty;
|
return game.BeatmapInfo.ToString() ?? string.Empty;
|
||||||
|
|
||||||
case UserActivity.Editing edit:
|
case UserActivity.EditingBeatmap edit:
|
||||||
return edit.BeatmapInfo.ToString() ?? string.Empty;
|
return edit.BeatmapInfo.ToString() ?? string.Empty;
|
||||||
|
|
||||||
|
case UserActivity.WatchingReplay watching:
|
||||||
|
return watching.BeatmapInfo.ToString();
|
||||||
|
|
||||||
case UserActivity.InLobby lobby:
|
case UserActivity.InLobby lobby:
|
||||||
return privacyMode.Value == DiscordRichPresenceMode.Limited ? string.Empty : lobby.Room.Name.Value;
|
return privacyMode.Value == DiscordRichPresenceMode.Limited ? string.Empty : lobby.Room.Name.Value;
|
||||||
}
|
}
|
||||||
|
@ -26,8 +26,8 @@
|
|||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Clowd.Squirrel" Version="2.9.42" />
|
<PackageReference Include="Clowd.Squirrel" Version="2.9.42" />
|
||||||
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
|
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
|
||||||
<PackageReference Include="System.IO.Packaging" Version="6.0.0" />
|
<PackageReference Include="System.IO.Packaging" Version="7.0.0" />
|
||||||
<PackageReference Include="DiscordRichPresence" Version="1.1.1.14" />
|
<PackageReference Include="DiscordRichPresence" Version="1.1.3.18" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Resources">
|
<ItemGroup Label="Resources">
|
||||||
<EmbeddedResource Include="lazer.ico" />
|
<EmbeddedResource Include="lazer.ico" />
|
||||||
|
@ -7,9 +7,9 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.2" />
|
<PackageReference Include="BenchmarkDotNet" Version="0.13.4" />
|
||||||
<PackageReference Include="nunit" Version="3.13.3" />
|
<PackageReference Include="nunit" Version="3.13.3" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<Import Project="..\osu.TestProject.props" />
|
<Import Project="..\osu.TestProject.props" />
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup Label="Project">
|
<PropertyGroup Label="Project">
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<Import Project="..\osu.TestProject.props" />
|
<Import Project="..\osu.TestProject.props" />
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup Label="Project">
|
<PropertyGroup Label="Project">
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<Import Project="..\osu.TestProject.props" />
|
<Import Project="..\osu.TestProject.props" />
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
||||||
<PackageReference Include="Moq" Version="4.18.2" />
|
<PackageReference Include="Moq" Version="4.18.4" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup Label="Project">
|
<PropertyGroup Label="Project">
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
|
@ -25,6 +25,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
|||||||
[TestCase("slider-conversion-v6")]
|
[TestCase("slider-conversion-v6")]
|
||||||
[TestCase("slider-conversion-v14")]
|
[TestCase("slider-conversion-v14")]
|
||||||
[TestCase("slider-generating-drumroll-2")]
|
[TestCase("slider-generating-drumroll-2")]
|
||||||
|
[TestCase("file-hitsamples")]
|
||||||
public void Test(string name) => base.Test(name);
|
public void Test(string name) => base.Test(name);
|
||||||
|
|
||||||
protected override IEnumerable<ConvertValue> CreateConvertValue(HitObject hitObject)
|
protected override IEnumerable<ConvertValue> CreateConvertValue(HitObject hitObject)
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<Import Project="..\osu.TestProject.props" />
|
<Import Project="..\osu.TestProject.props" />
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup Label="Project">
|
<PropertyGroup Label="Project">
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
|
@ -2,13 +2,15 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Taiko.Objects;
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
|
using osu.Game.Rulesets.Taiko.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Taiko.UI;
|
using osu.Game.Rulesets.Taiko.UI;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Mods
|
namespace osu.Game.Rulesets.Taiko.Mods
|
||||||
{
|
{
|
||||||
public class TaikoModClassic : ModClassic, IApplicableToDrawableRuleset<TaikoHitObject>
|
public class TaikoModClassic : ModClassic, IApplicableToDrawableRuleset<TaikoHitObject>, IApplicableToDrawableHitObject
|
||||||
{
|
{
|
||||||
public void ApplyToDrawableRuleset(DrawableRuleset<TaikoHitObject> drawableRuleset)
|
public void ApplyToDrawableRuleset(DrawableRuleset<TaikoHitObject> drawableRuleset)
|
||||||
{
|
{
|
||||||
@ -18,5 +20,11 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
|||||||
var playfield = (TaikoPlayfield)drawableRuleset.Playfield;
|
var playfield = (TaikoPlayfield)drawableRuleset.Playfield;
|
||||||
playfield.ClassicHitTargetPosition.Value = true;
|
playfield.ClassicHitTargetPosition.Value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ApplyToDrawableHitObject(DrawableHitObject drawable)
|
||||||
|
{
|
||||||
|
if (drawable is DrawableTaikoHitObject hit)
|
||||||
|
hit.SnapJudgementLocation = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -207,6 +207,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
const float gravity_time = 300;
|
const float gravity_time = 300;
|
||||||
const float gravity_travel_height = 200;
|
const float gravity_travel_height = 200;
|
||||||
|
|
||||||
|
if (SnapJudgementLocation)
|
||||||
|
MainPiece.MoveToX(-X);
|
||||||
|
|
||||||
this.ScaleTo(0.8f, gravity_time * 2, Easing.OutQuad);
|
this.ScaleTo(0.8f, gravity_time * 2, Easing.OutQuad);
|
||||||
|
|
||||||
this.MoveToY(-gravity_travel_height, gravity_time, Easing.Out)
|
this.MoveToY(-gravity_travel_height, gravity_time, Easing.Out)
|
||||||
|
@ -25,6 +25,15 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
|
|
||||||
private readonly Container nonProxiedContent;
|
private readonly Container nonProxiedContent;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the location of the hit should be snapped to the hit target before animating.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This is how osu-stable worked, but notably is not how TnT works.
|
||||||
|
/// Not snapping results in less visual feedback on hit accuracy.
|
||||||
|
/// </remarks>
|
||||||
|
public bool SnapJudgementLocation { get; set; }
|
||||||
|
|
||||||
protected DrawableTaikoHitObject([CanBeNull] TaikoHitObject hitObject)
|
protected DrawableTaikoHitObject([CanBeNull] TaikoHitObject hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
{
|
{
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
{"Mappings":[{"StartTime":500.0,"Objects":[{"StartTime":500.0,"EndTime":500.0,"IsRim":false,"IsCentre":true,"IsDrumRoll":false,"IsSwell":false,"IsStrong":false}]},{"StartTime":1000.0,"Objects":[{"StartTime":1000.0,"EndTime":1000.0,"IsRim":true,"IsCentre":false,"IsDrumRoll":false,"IsSwell":false,"IsStrong":false}]},{"StartTime":1500.0,"Objects":[{"StartTime":1500.0,"EndTime":1500.0,"IsRim":true,"IsCentre":false,"IsDrumRoll":false,"IsSwell":false,"IsStrong":false}]},{"StartTime":2000.0,"Objects":[{"StartTime":2000.0,"EndTime":2000.0,"IsRim":true,"IsCentre":false,"IsDrumRoll":false,"IsSwell":false,"IsStrong":false}]},{"StartTime":2500.0,"Objects":[{"StartTime":2500.0,"EndTime":2500.0,"IsRim":false,"IsCentre":true,"IsDrumRoll":false,"IsSwell":false,"IsStrong":true}]},{"StartTime":3000.0,"Objects":[{"StartTime":3000.0,"EndTime":3000.0,"IsRim":true,"IsCentre":false,"IsDrumRoll":false,"IsSwell":false,"IsStrong":true}]},{"StartTime":3500.0,"Objects":[{"StartTime":3500.0,"EndTime":3500.0,"IsRim":true,"IsCentre":false,"IsDrumRoll":false,"IsSwell":false,"IsStrong":true}]},{"StartTime":4000.0,"Objects":[{"StartTime":4000.0,"EndTime":4000.0,"IsRim":true,"IsCentre":false,"IsDrumRoll":false,"IsSwell":false,"IsStrong":true}]}]}
|
@ -0,0 +1,22 @@
|
|||||||
|
osu file format v14
|
||||||
|
|
||||||
|
[Difficulty]
|
||||||
|
HPDrainRate:5
|
||||||
|
CircleSize:7
|
||||||
|
OverallDifficulty:6.5
|
||||||
|
ApproachRate:10
|
||||||
|
SliderMultiplier:1.9
|
||||||
|
SliderTickRate:1
|
||||||
|
|
||||||
|
[TimingPoints]
|
||||||
|
500,500,4,2,1,50,1,0
|
||||||
|
|
||||||
|
[HitObjects]
|
||||||
|
256,192,500,1,0,0:0:0:0:sample.ogg
|
||||||
|
256,192,1000,1,8,0:0:0:0:sample.ogg
|
||||||
|
256,192,1500,1,2,0:0:0:0:sample.ogg
|
||||||
|
256,192,2000,1,10,0:0:0:0:sample.ogg
|
||||||
|
256,192,2500,1,4,0:0:0:0:sample.ogg
|
||||||
|
256,192,3000,1,12,0:0:0:0:sample.ogg
|
||||||
|
256,192,3500,1,6,0:0:0:0:sample.ogg
|
||||||
|
256,192,4000,1,14,0:0:0:0:sample.ogg
|
@ -209,9 +209,8 @@ namespace osu.Game.Rulesets.Taiko
|
|||||||
HitResult.Great,
|
HitResult.Great,
|
||||||
HitResult.Ok,
|
HitResult.Ok,
|
||||||
|
|
||||||
HitResult.SmallTickHit,
|
|
||||||
|
|
||||||
HitResult.SmallBonus,
|
HitResult.SmallBonus,
|
||||||
|
HitResult.LargeBonus,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,6 +219,9 @@ namespace osu.Game.Rulesets.Taiko
|
|||||||
switch (result)
|
switch (result)
|
||||||
{
|
{
|
||||||
case HitResult.SmallBonus:
|
case HitResult.SmallBonus:
|
||||||
|
return "drum tick";
|
||||||
|
|
||||||
|
case HitResult.LargeBonus:
|
||||||
return "bonus";
|
return "bonus";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
24
osu.Game.Tests/Visual/Gameplay/TestSceneLetterboxOverlay.cs
Normal file
24
osu.Game.Tests/Visual/Gameplay/TestSceneLetterboxOverlay.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Screens.Play.Break;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Gameplay
|
||||||
|
{
|
||||||
|
public partial class TestSceneLetterboxOverlay : OsuTestScene
|
||||||
|
{
|
||||||
|
public TestSceneLetterboxOverlay()
|
||||||
|
{
|
||||||
|
AddRange(new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
|
},
|
||||||
|
new LetterboxOverlay()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,32 +1,64 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
#nullable disable
|
using System.Linq;
|
||||||
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Screens.Play;
|
||||||
|
using osu.Game.Tests.Beatmaps;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Gameplay
|
namespace osu.Game.Tests.Visual.Gameplay
|
||||||
{
|
{
|
||||||
public partial class TestSceneReplayPlayer : RateAdjustedBeatmapTestScene
|
public partial class TestSceneReplayPlayer : RateAdjustedBeatmapTestScene
|
||||||
{
|
{
|
||||||
protected TestReplayPlayer Player;
|
protected TestReplayPlayer Player = null!;
|
||||||
|
|
||||||
public override void SetUpSteps()
|
|
||||||
{
|
|
||||||
base.SetUpSteps();
|
|
||||||
|
|
||||||
AddStep("Initialise player", () => Player = CreatePlayer(new OsuRuleset()));
|
|
||||||
AddStep("Load player", () => LoadScreen(Player));
|
|
||||||
AddUntilStep("player loaded", () => Player.IsLoaded);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestPauseViaSpace()
|
public void TestPauseViaSpace()
|
||||||
{
|
{
|
||||||
|
loadPlayerWithBeatmap();
|
||||||
|
|
||||||
|
double? lastTime = null;
|
||||||
|
|
||||||
|
AddUntilStep("wait for first hit", () => Player.ScoreProcessor.TotalScore.Value > 0);
|
||||||
|
|
||||||
|
AddStep("Pause playback with space", () => InputManager.Key(Key.Space));
|
||||||
|
|
||||||
|
AddAssert("player not exited", () => Player.IsCurrentScreen());
|
||||||
|
|
||||||
|
AddUntilStep("Time stopped progressing", () =>
|
||||||
|
{
|
||||||
|
double current = Player.GameplayClockContainer.CurrentTime;
|
||||||
|
bool changed = lastTime != current;
|
||||||
|
lastTime = current;
|
||||||
|
|
||||||
|
return !changed;
|
||||||
|
});
|
||||||
|
|
||||||
|
AddWaitStep("wait some", 10);
|
||||||
|
|
||||||
|
AddAssert("Time still stopped", () => lastTime == Player.GameplayClockContainer.CurrentTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPauseViaSpaceWithSkip()
|
||||||
|
{
|
||||||
|
loadPlayerWithBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo)
|
||||||
|
{
|
||||||
|
BeatmapInfo = { AudioLeadIn = 60000 }
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("wait for skip overlay", () => Player.ChildrenOfType<SkipOverlay>().First().IsButtonVisible);
|
||||||
|
|
||||||
|
AddStep("Skip with space", () => InputManager.Key(Key.Space));
|
||||||
|
|
||||||
|
AddAssert("Player not paused", () => !Player.DrawableRuleset.IsPaused.Value);
|
||||||
|
|
||||||
double? lastTime = null;
|
double? lastTime = null;
|
||||||
|
|
||||||
AddUntilStep("wait for first hit", () => Player.ScoreProcessor.TotalScore.Value > 0);
|
AddUntilStep("wait for first hit", () => Player.ScoreProcessor.TotalScore.Value > 0);
|
||||||
@ -52,6 +84,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestPauseViaMiddleMouse()
|
public void TestPauseViaMiddleMouse()
|
||||||
{
|
{
|
||||||
|
loadPlayerWithBeatmap();
|
||||||
|
|
||||||
double? lastTime = null;
|
double? lastTime = null;
|
||||||
|
|
||||||
AddUntilStep("wait for first hit", () => Player.ScoreProcessor.TotalScore.Value > 0);
|
AddUntilStep("wait for first hit", () => Player.ScoreProcessor.TotalScore.Value > 0);
|
||||||
@ -77,6 +111,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestSeekBackwards()
|
public void TestSeekBackwards()
|
||||||
{
|
{
|
||||||
|
loadPlayerWithBeatmap();
|
||||||
|
|
||||||
double? lastTime = null;
|
double? lastTime = null;
|
||||||
|
|
||||||
AddUntilStep("wait for first hit", () => Player.ScoreProcessor.TotalScore.Value > 0);
|
AddUntilStep("wait for first hit", () => Player.ScoreProcessor.TotalScore.Value > 0);
|
||||||
@ -93,6 +129,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestSeekForwards()
|
public void TestSeekForwards()
|
||||||
{
|
{
|
||||||
|
loadPlayerWithBeatmap();
|
||||||
|
|
||||||
double? lastTime = null;
|
double? lastTime = null;
|
||||||
|
|
||||||
AddUntilStep("wait for first hit", () => Player.ScoreProcessor.TotalScore.Value > 0);
|
AddUntilStep("wait for first hit", () => Player.ScoreProcessor.TotalScore.Value > 0);
|
||||||
@ -106,12 +144,26 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddAssert("Jumped forwards", () => Player.GameplayClockContainer.CurrentTime - lastTime > 500);
|
AddAssert("Jumped forwards", () => Player.GameplayClockContainer.CurrentTime - lastTime > 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected TestReplayPlayer CreatePlayer(Ruleset ruleset)
|
private void loadPlayerWithBeatmap(IBeatmap? beatmap = null)
|
||||||
{
|
{
|
||||||
Beatmap.Value = CreateWorkingBeatmap(ruleset.RulesetInfo);
|
AddStep("create player", () =>
|
||||||
|
{
|
||||||
|
CreatePlayer(new OsuRuleset(), beatmap);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("Load player", () => LoadScreen(Player));
|
||||||
|
AddUntilStep("player loaded", () => Player.IsLoaded);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void CreatePlayer(Ruleset ruleset, IBeatmap? beatmap = null)
|
||||||
|
{
|
||||||
|
Beatmap.Value = beatmap != null
|
||||||
|
? CreateWorkingBeatmap(beatmap)
|
||||||
|
: CreateWorkingBeatmap(ruleset.RulesetInfo);
|
||||||
|
|
||||||
SelectedMods.Value = new[] { ruleset.GetAutoplayMod() };
|
SelectedMods.Value = new[] { ruleset.GetAutoplayMod() };
|
||||||
|
|
||||||
return new TestReplayPlayer(false);
|
Player = new TestReplayPlayer(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestEditActivity()
|
public void TestEditActivity()
|
||||||
{
|
{
|
||||||
AddStep("Set activity", () => api.Activity.Value = new UserActivity.Editing(new BeatmapInfo()));
|
AddStep("Set activity", () => api.Activity.Value = new UserActivity.EditingBeatmap(new BeatmapInfo()));
|
||||||
|
|
||||||
AddStep("Run command", () => Add(new NowPlayingCommand(new Channel())));
|
AddStep("Run command", () => Add(new NowPlayingCommand(new Channel())));
|
||||||
|
|
||||||
|
@ -11,6 +11,8 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Tests.Beatmaps;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@ -107,14 +109,16 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
AddStep("set online status", () => status.Value = new UserStatusOnline());
|
AddStep("set online status", () => status.Value = new UserStatusOnline());
|
||||||
|
|
||||||
AddStep("idle", () => activity.Value = null);
|
AddStep("idle", () => activity.Value = null);
|
||||||
AddStep("spectating", () => activity.Value = new UserActivity.Spectating());
|
AddStep("watching replay", () => activity.Value = new UserActivity.WatchingReplay(createScore(@"nats")));
|
||||||
|
AddStep("spectating user", () => activity.Value = new UserActivity.SpectatingUser(createScore(@"mrekk")));
|
||||||
AddStep("solo (osu!)", () => activity.Value = soloGameStatusForRuleset(0));
|
AddStep("solo (osu!)", () => activity.Value = soloGameStatusForRuleset(0));
|
||||||
AddStep("solo (osu!taiko)", () => activity.Value = soloGameStatusForRuleset(1));
|
AddStep("solo (osu!taiko)", () => activity.Value = soloGameStatusForRuleset(1));
|
||||||
AddStep("solo (osu!catch)", () => activity.Value = soloGameStatusForRuleset(2));
|
AddStep("solo (osu!catch)", () => activity.Value = soloGameStatusForRuleset(2));
|
||||||
AddStep("solo (osu!mania)", () => activity.Value = soloGameStatusForRuleset(3));
|
AddStep("solo (osu!mania)", () => activity.Value = soloGameStatusForRuleset(3));
|
||||||
AddStep("choosing", () => activity.Value = new UserActivity.ChoosingBeatmap());
|
AddStep("choosing", () => activity.Value = new UserActivity.ChoosingBeatmap());
|
||||||
AddStep("editing", () => activity.Value = new UserActivity.Editing(null));
|
AddStep("editing beatmap", () => activity.Value = new UserActivity.EditingBeatmap(null));
|
||||||
AddStep("modding", () => activity.Value = new UserActivity.Modding());
|
AddStep("modding beatmap", () => activity.Value = new UserActivity.ModdingBeatmap(null));
|
||||||
|
AddStep("testing beatmap", () => activity.Value = new UserActivity.TestingBeatmap(null, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -132,6 +136,14 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
|
|
||||||
private UserActivity soloGameStatusForRuleset(int rulesetId) => new UserActivity.InSoloGame(null, rulesetStore.GetRuleset(rulesetId));
|
private UserActivity soloGameStatusForRuleset(int rulesetId) => new UserActivity.InSoloGame(null, rulesetStore.GetRuleset(rulesetId));
|
||||||
|
|
||||||
|
private ScoreInfo createScore(string name) => new ScoreInfo(new TestBeatmap(Ruleset.Value).BeatmapInfo)
|
||||||
|
{
|
||||||
|
User = new APIUser
|
||||||
|
{
|
||||||
|
Username = name,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private partial class TestUserListPanel : UserListPanel
|
private partial class TestUserListPanel : UserListPanel
|
||||||
{
|
{
|
||||||
public TestUserListPanel(APIUser user)
|
public TestUserListPanel(APIUser user)
|
||||||
|
@ -24,17 +24,26 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
{
|
{
|
||||||
public partial class TestSceneAccuracyCircle : OsuTestScene
|
public partial class TestSceneAccuracyCircle : OsuTestScene
|
||||||
{
|
{
|
||||||
[TestCase(0.2, ScoreRank.D)]
|
[TestCase(0)]
|
||||||
[TestCase(0.5, ScoreRank.D)]
|
[TestCase(0.2)]
|
||||||
[TestCase(0.75, ScoreRank.C)]
|
[TestCase(0.5)]
|
||||||
[TestCase(0.85, ScoreRank.B)]
|
[TestCase(0.6999)]
|
||||||
[TestCase(0.925, ScoreRank.A)]
|
[TestCase(0.7)]
|
||||||
[TestCase(0.975, ScoreRank.S)]
|
[TestCase(0.75)]
|
||||||
[TestCase(0.9999, ScoreRank.S)]
|
[TestCase(0.7999)]
|
||||||
[TestCase(1, ScoreRank.X)]
|
[TestCase(0.8)]
|
||||||
public void TestRank(double accuracy, ScoreRank rank)
|
[TestCase(0.85)]
|
||||||
|
[TestCase(0.8999)]
|
||||||
|
[TestCase(0.9)]
|
||||||
|
[TestCase(0.925)]
|
||||||
|
[TestCase(0.9499)]
|
||||||
|
[TestCase(0.95)]
|
||||||
|
[TestCase(0.975)]
|
||||||
|
[TestCase(0.9999)]
|
||||||
|
[TestCase(1)]
|
||||||
|
public void TestRank(double accuracy)
|
||||||
{
|
{
|
||||||
var score = createScore(accuracy, rank);
|
var score = createScore(accuracy, ScoreProcessor.RankFromAccuracy(accuracy));
|
||||||
|
|
||||||
addCircleStep(score);
|
addCircleStep(score);
|
||||||
}
|
}
|
||||||
|
146
osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectFooterV2.cs
Normal file
146
osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectFooterV2.cs
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Overlays.Mods;
|
||||||
|
using osu.Game.Screens.Select.FooterV2;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.SongSelect
|
||||||
|
{
|
||||||
|
public partial class TestSceneSongSelectFooterV2 : OsuManualInputManagerTestScene
|
||||||
|
{
|
||||||
|
private FooterButtonRandomV2 randomButton = null!;
|
||||||
|
private FooterButtonModsV2 modsButton = null!;
|
||||||
|
|
||||||
|
private bool nextRandomCalled;
|
||||||
|
private bool previousRandomCalled;
|
||||||
|
|
||||||
|
private DummyOverlay overlay = null!;
|
||||||
|
|
||||||
|
[Cached]
|
||||||
|
private OverlayColourProvider colourProvider { get; set; } = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp() => Schedule(() =>
|
||||||
|
{
|
||||||
|
nextRandomCalled = false;
|
||||||
|
previousRandomCalled = false;
|
||||||
|
|
||||||
|
FooterV2 footer;
|
||||||
|
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
footer = new FooterV2
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre
|
||||||
|
},
|
||||||
|
overlay = new DummyOverlay()
|
||||||
|
};
|
||||||
|
|
||||||
|
footer.AddButton(modsButton = new FooterButtonModsV2(), overlay);
|
||||||
|
footer.AddButton(randomButton = new FooterButtonRandomV2
|
||||||
|
{
|
||||||
|
NextRandom = () => nextRandomCalled = true,
|
||||||
|
PreviousRandom = () => previousRandomCalled = true
|
||||||
|
});
|
||||||
|
footer.AddButton(new FooterButtonOptionsV2());
|
||||||
|
|
||||||
|
overlay.Hide();
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestState()
|
||||||
|
{
|
||||||
|
AddToggleStep("set options enabled state", state => this.ChildrenOfType<FooterButtonV2>().Last().Enabled.Value = state);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestFooterRandom()
|
||||||
|
{
|
||||||
|
AddStep("press F2", () => InputManager.Key(Key.F2));
|
||||||
|
AddAssert("next random invoked", () => nextRandomCalled && !previousRandomCalled);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestFooterRandomViaMouse()
|
||||||
|
{
|
||||||
|
AddStep("click button", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(randomButton);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
AddAssert("next random invoked", () => nextRandomCalled && !previousRandomCalled);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestFooterRewind()
|
||||||
|
{
|
||||||
|
AddStep("press Shift+F2", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.LShift);
|
||||||
|
InputManager.PressKey(Key.F2);
|
||||||
|
InputManager.ReleaseKey(Key.F2);
|
||||||
|
InputManager.ReleaseKey(Key.LShift);
|
||||||
|
});
|
||||||
|
AddAssert("previous random invoked", () => previousRandomCalled && !nextRandomCalled);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestFooterRewindViaShiftMouseLeft()
|
||||||
|
{
|
||||||
|
AddStep("shift + click button", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.LShift);
|
||||||
|
InputManager.MoveMouseTo(randomButton);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
InputManager.ReleaseKey(Key.LShift);
|
||||||
|
});
|
||||||
|
AddAssert("previous random invoked", () => previousRandomCalled && !nextRandomCalled);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestFooterRewindViaMouseRight()
|
||||||
|
{
|
||||||
|
AddStep("right click button", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(randomButton);
|
||||||
|
InputManager.Click(MouseButton.Right);
|
||||||
|
});
|
||||||
|
AddAssert("previous random invoked", () => previousRandomCalled && !nextRandomCalled);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestOverlayPresent()
|
||||||
|
{
|
||||||
|
AddStep("Press F1", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(modsButton);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
AddAssert("Overlay visible", () => overlay.State.Value == Visibility.Visible);
|
||||||
|
AddStep("Hide", () => overlay.Hide());
|
||||||
|
}
|
||||||
|
|
||||||
|
private partial class DummyOverlay : ShearedOverlayContainer
|
||||||
|
{
|
||||||
|
public DummyOverlay()
|
||||||
|
: base(OverlayColourScheme.Green)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Header.Title = "An overlay";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,11 +2,11 @@
|
|||||||
<Import Project="..\osu.TestProject.props" />
|
<Import Project="..\osu.TestProject.props" />
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="DeepEqual" Version="4.2.1" />
|
<PackageReference Include="DeepEqual" Version="4.2.1" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
||||||
<PackageReference Include="Nito.AsyncEx" Version="5.1.2" />
|
<PackageReference Include="Nito.AsyncEx" Version="5.1.2" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
|
||||||
<PackageReference Include="Moq" Version="4.18.2" />
|
<PackageReference Include="Moq" Version="4.18.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup Label="Project">
|
<PropertyGroup Label="Project">
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
|
@ -4,9 +4,9 @@
|
|||||||
<StartupObject>osu.Game.Tournament.Tests.TournamentTestRunner</StartupObject>
|
<StartupObject>osu.Game.Tournament.Tests.TournamentTestRunner</StartupObject>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup Label="Project">
|
<PropertyGroup Label="Project">
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
|
@ -98,6 +98,9 @@ namespace osu.Game.Audio
|
|||||||
|
|
||||||
Track.Stop();
|
Track.Stop();
|
||||||
|
|
||||||
|
// Ensure the track is reset immediately on stopping, so the next time it is started it has a correct time value.
|
||||||
|
Track.Seek(0);
|
||||||
|
|
||||||
Stopped?.Invoke();
|
Stopped?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,9 +100,9 @@ namespace osu.Game.Graphics.Containers
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Abort any ongoing confirmation. Should be called when the container's interaction is no longer valid (ie. the user releases a key).
|
/// Abort any ongoing confirmation. Should be called when the container's interaction is no longer valid (ie. the user releases a key).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected void AbortConfirm()
|
protected virtual void AbortConfirm()
|
||||||
{
|
{
|
||||||
if (!AllowMultipleFires && Fired) return;
|
if (!confirming || (!AllowMultipleFires && Fired)) return;
|
||||||
|
|
||||||
confirming = false;
|
confirming = false;
|
||||||
Fired = false;
|
Fired = false;
|
||||||
|
@ -46,8 +46,8 @@ namespace osu.Game.Graphics.Containers
|
|||||||
|
|
||||||
AddRangeInternal(new Drawable[]
|
AddRangeInternal(new Drawable[]
|
||||||
{
|
{
|
||||||
|
CreateHoverSounds(sampleSet),
|
||||||
content,
|
content,
|
||||||
CreateHoverSounds(sampleSet)
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,8 +35,8 @@ namespace osu.Game.Input.Bindings
|
|||||||
// It is used to decide the order of precedence, with the earlier items having higher precedence.
|
// It is used to decide the order of precedence, with the earlier items having higher precedence.
|
||||||
public override IEnumerable<IKeyBinding> DefaultKeyBindings => GlobalKeyBindings
|
public override IEnumerable<IKeyBinding> DefaultKeyBindings => GlobalKeyBindings
|
||||||
.Concat(EditorKeyBindings)
|
.Concat(EditorKeyBindings)
|
||||||
.Concat(ReplayKeyBindings)
|
|
||||||
.Concat(InGameKeyBindings)
|
.Concat(InGameKeyBindings)
|
||||||
|
.Concat(ReplayKeyBindings)
|
||||||
.Concat(SongSelectKeyBindings)
|
.Concat(SongSelectKeyBindings)
|
||||||
.Concat(AudioControlKeyBindings)
|
.Concat(AudioControlKeyBindings)
|
||||||
// Overlay bindings may conflict with more local cases like the editor so they are checked last.
|
// Overlay bindings may conflict with more local cases like the editor so they are checked last.
|
||||||
|
@ -95,7 +95,7 @@ namespace osu.Game.Online.Chat
|
|||||||
{
|
{
|
||||||
connector.ChannelJoined += ch => Schedule(() => joinChannel(ch));
|
connector.ChannelJoined += ch => Schedule(() => joinChannel(ch));
|
||||||
|
|
||||||
connector.ChannelParted += ch => Schedule(() => LeaveChannel(getChannel(ch)));
|
connector.ChannelParted += ch => Schedule(() => leaveChannel(getChannel(ch), false));
|
||||||
|
|
||||||
connector.NewMessages += msgs => Schedule(() => addMessages(msgs));
|
connector.NewMessages += msgs => Schedule(() => addMessages(msgs));
|
||||||
|
|
||||||
@ -558,7 +558,9 @@ namespace osu.Game.Online.Chat
|
|||||||
/// Leave the specified channel. Can be called from any thread.
|
/// Leave the specified channel. Can be called from any thread.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="channel">The channel to leave.</param>
|
/// <param name="channel">The channel to leave.</param>
|
||||||
public void LeaveChannel(Channel channel) => Schedule(() =>
|
public void LeaveChannel(Channel channel) => Schedule(() => leaveChannel(channel, true));
|
||||||
|
|
||||||
|
private void leaveChannel(Channel channel, bool sendLeaveRequest)
|
||||||
{
|
{
|
||||||
if (channel == null) return;
|
if (channel == null) return;
|
||||||
|
|
||||||
@ -581,10 +583,11 @@ namespace osu.Game.Online.Chat
|
|||||||
|
|
||||||
if (channel.Joined.Value)
|
if (channel.Joined.Value)
|
||||||
{
|
{
|
||||||
api.Queue(new LeaveChannelRequest(channel));
|
if (sendLeaveRequest)
|
||||||
|
api.Queue(new LeaveChannelRequest(channel));
|
||||||
channel.Joined.Value = false;
|
channel.Joined.Value = false;
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Opens the most recently closed channel that has not already been reopened,
|
/// Opens the most recently closed channel that has not already been reopened,
|
||||||
|
@ -61,7 +61,7 @@ namespace osu.Game.Online.Chat
|
|||||||
beatmapInfo = game.BeatmapInfo;
|
beatmapInfo = game.BeatmapInfo;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case UserActivity.Editing edit:
|
case UserActivity.EditingBeatmap edit:
|
||||||
verb = "editing";
|
verb = "editing";
|
||||||
beatmapInfo = edit.BeatmapInfo;
|
beatmapInfo = edit.BeatmapInfo;
|
||||||
break;
|
break;
|
||||||
|
@ -60,6 +60,7 @@ using osu.Game.Screens.Menu;
|
|||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Screens.Ranking;
|
using osu.Game.Screens.Ranking;
|
||||||
using osu.Game.Screens.Select;
|
using osu.Game.Screens.Select;
|
||||||
|
using osu.Game.Skinning;
|
||||||
using osu.Game.Updater;
|
using osu.Game.Updater;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
using osu.Game.Utils;
|
using osu.Game.Utils;
|
||||||
@ -501,6 +502,23 @@ namespace osu.Game
|
|||||||
/// <param name="version">The build version of the update stream</param>
|
/// <param name="version">The build version of the update stream</param>
|
||||||
public void ShowChangelogBuild(string updateStream, string version) => waitForReady(() => changelogOverlay, _ => changelogOverlay.ShowBuild(updateStream, version));
|
public void ShowChangelogBuild(string updateStream, string version) => waitForReady(() => changelogOverlay, _ => changelogOverlay.ShowBuild(updateStream, version));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Present a skin select immediately.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="skin">The skin to select.</param>
|
||||||
|
public void PresentSkin(SkinInfo skin)
|
||||||
|
{
|
||||||
|
var databasedSkin = SkinManager.Query(s => s.ID == skin.ID);
|
||||||
|
|
||||||
|
if (databasedSkin == null)
|
||||||
|
{
|
||||||
|
Logger.Log("The requested skin could not be loaded.", LoggingTarget.Information);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SkinManager.CurrentSkinInfo.Value = databasedSkin;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Present a beatmap at song select immediately.
|
/// Present a beatmap at song select immediately.
|
||||||
/// The user should have already requested this interactively.
|
/// The user should have already requested this interactively.
|
||||||
@ -777,6 +795,7 @@ namespace osu.Game
|
|||||||
|
|
||||||
// todo: all archive managers should be able to be looped here.
|
// todo: all archive managers should be able to be looped here.
|
||||||
SkinManager.PostNotification = n => Notifications.Post(n);
|
SkinManager.PostNotification = n => Notifications.Post(n);
|
||||||
|
SkinManager.PresentImport = items => PresentSkin(items.First().Value);
|
||||||
|
|
||||||
BeatmapManager.PostNotification = n => Notifications.Post(n);
|
BeatmapManager.PostNotification = n => Notifications.Post(n);
|
||||||
BeatmapManager.PresentImport = items => PresentBeatmap(items.First().Value);
|
BeatmapManager.PresentImport = items => PresentBeatmap(items.First().Value);
|
||||||
|
@ -57,6 +57,7 @@ namespace osu.Game.Overlays.Dialog
|
|||||||
private Sample confirmSample;
|
private Sample confirmSample;
|
||||||
private double lastTickPlaybackTime;
|
private double lastTickPlaybackTime;
|
||||||
private AudioFilter lowPassFilter = null!;
|
private AudioFilter lowPassFilter = null!;
|
||||||
|
private bool mouseDown;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(AudioManager audio)
|
private void load(AudioManager audio)
|
||||||
@ -73,6 +74,12 @@ namespace osu.Game.Overlays.Dialog
|
|||||||
Progress.BindValueChanged(progressChanged);
|
Progress.BindValueChanged(progressChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void AbortConfirm()
|
||||||
|
{
|
||||||
|
lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF);
|
||||||
|
base.AbortConfirm();
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Confirm()
|
protected override void Confirm()
|
||||||
{
|
{
|
||||||
lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF);
|
lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF);
|
||||||
@ -83,6 +90,7 @@ namespace osu.Game.Overlays.Dialog
|
|||||||
protected override bool OnMouseDown(MouseDownEvent e)
|
protected override bool OnMouseDown(MouseDownEvent e)
|
||||||
{
|
{
|
||||||
BeginConfirm();
|
BeginConfirm();
|
||||||
|
mouseDown = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,11 +98,28 @@ namespace osu.Game.Overlays.Dialog
|
|||||||
{
|
{
|
||||||
if (!e.HasAnyButtonPressed)
|
if (!e.HasAnyButtonPressed)
|
||||||
{
|
{
|
||||||
lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF);
|
|
||||||
AbortConfirm();
|
AbortConfirm();
|
||||||
|
mouseDown = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override bool OnHover(HoverEvent e)
|
||||||
|
{
|
||||||
|
if (mouseDown)
|
||||||
|
BeginConfirm();
|
||||||
|
|
||||||
|
return base.OnHover(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnHoverLost(HoverLostEvent e)
|
||||||
|
{
|
||||||
|
base.OnHoverLost(e);
|
||||||
|
|
||||||
|
if (!mouseDown) return;
|
||||||
|
|
||||||
|
AbortConfirm();
|
||||||
|
}
|
||||||
|
|
||||||
private void progressChanged(ValueChangedEvent<double> progress)
|
private void progressChanged(ValueChangedEvent<double> progress)
|
||||||
{
|
{
|
||||||
if (progress.NewValue < progress.OldValue) return;
|
if (progress.NewValue < progress.OldValue) return;
|
||||||
|
@ -148,9 +148,9 @@ namespace osu.Game.Overlays.SkinEditor
|
|||||||
component.Origin = Anchor.Centre;
|
component.Origin = Anchor.Centre;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void UpdateAfterChildren()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.UpdateAfterChildren();
|
||||||
|
|
||||||
if (component.DrawSize != Vector2.Zero)
|
if (component.DrawSize != Vector2.Zero)
|
||||||
{
|
{
|
||||||
|
@ -439,19 +439,20 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
|||||||
|
|
||||||
private List<HitSampleInfo> convertSoundType(LegacyHitSoundType type, SampleBankInfo bankInfo)
|
private List<HitSampleInfo> convertSoundType(LegacyHitSoundType type, SampleBankInfo bankInfo)
|
||||||
{
|
{
|
||||||
// Todo: This should return the normal SampleInfos if the specified sample file isn't found, but that's a pretty edge-case scenario
|
var soundTypes = new List<HitSampleInfo>();
|
||||||
if (!string.IsNullOrEmpty(bankInfo.Filename))
|
|
||||||
{
|
|
||||||
return new List<HitSampleInfo> { new FileHitSampleInfo(bankInfo.Filename, bankInfo.Volume) };
|
|
||||||
}
|
|
||||||
|
|
||||||
var soundTypes = new List<HitSampleInfo>
|
if (string.IsNullOrEmpty(bankInfo.Filename))
|
||||||
{
|
{
|
||||||
new LegacyHitSampleInfo(HitSampleInfo.HIT_NORMAL, bankInfo.BankForNormal, bankInfo.Volume, bankInfo.CustomSampleBank,
|
soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_NORMAL, bankInfo.BankForNormal, bankInfo.Volume, bankInfo.CustomSampleBank,
|
||||||
// if the sound type doesn't have the Normal flag set, attach it anyway as a layered sample.
|
// if the sound type doesn't have the Normal flag set, attach it anyway as a layered sample.
|
||||||
// None also counts as a normal non-layered sample: https://osu.ppy.sh/help/wiki/osu!_File_Formats/Osu_(file_format)#hitsounds
|
// None also counts as a normal non-layered sample: https://osu.ppy.sh/help/wiki/osu!_File_Formats/Osu_(file_format)#hitsounds
|
||||||
type != LegacyHitSoundType.None && !type.HasFlagFast(LegacyHitSoundType.Normal))
|
type != LegacyHitSoundType.None && !type.HasFlagFast(LegacyHitSoundType.Normal)));
|
||||||
};
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Todo: This should set the normal SampleInfo if the specified sample file isn't found, but that's a pretty edge-case scenario
|
||||||
|
soundTypes.Add(new FileHitSampleInfo(bankInfo.Filename, bankInfo.Volume));
|
||||||
|
}
|
||||||
|
|
||||||
if (type.HasFlagFast(LegacyHitSoundType.Finish))
|
if (type.HasFlagFast(LegacyHitSoundType.Finish))
|
||||||
soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_FINISH, bankInfo.BankForAdditions, bankInfo.Volume, bankInfo.CustomSampleBank));
|
soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_FINISH, bankInfo.BankForAdditions, bankInfo.Volume, bankInfo.CustomSampleBank));
|
||||||
|
@ -7,21 +7,28 @@ using System.Diagnostics;
|
|||||||
using System.Diagnostics.Contracts;
|
using System.Diagnostics.Contracts;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Extensions;
|
using osu.Game.Extensions;
|
||||||
|
using osu.Game.Localisation;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Replays;
|
using osu.Game.Rulesets.Replays;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Framework.Localisation;
|
|
||||||
using osu.Game.Localisation;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Scoring
|
namespace osu.Game.Rulesets.Scoring
|
||||||
{
|
{
|
||||||
public partial class ScoreProcessor : JudgementProcessor
|
public partial class ScoreProcessor : JudgementProcessor
|
||||||
{
|
{
|
||||||
|
private const double accuracy_cutoff_x = 1;
|
||||||
|
private const double accuracy_cutoff_s = 0.95;
|
||||||
|
private const double accuracy_cutoff_a = 0.9;
|
||||||
|
private const double accuracy_cutoff_b = 0.8;
|
||||||
|
private const double accuracy_cutoff_c = 0.7;
|
||||||
|
private const double accuracy_cutoff_d = 0;
|
||||||
|
|
||||||
private const double max_score = 1000000;
|
private const double max_score = 1000000;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -160,7 +167,7 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
Combo.ValueChanged += combo => HighestCombo.Value = Math.Max(HighestCombo.Value, combo.NewValue);
|
Combo.ValueChanged += combo => HighestCombo.Value = Math.Max(HighestCombo.Value, combo.NewValue);
|
||||||
Accuracy.ValueChanged += accuracy =>
|
Accuracy.ValueChanged += accuracy =>
|
||||||
{
|
{
|
||||||
Rank.Value = rankFrom(accuracy.NewValue);
|
Rank.Value = RankFromAccuracy(accuracy.NewValue);
|
||||||
foreach (var mod in Mods.Value.OfType<IApplicableToScoreProcessor>())
|
foreach (var mod in Mods.Value.OfType<IApplicableToScoreProcessor>())
|
||||||
Rank.Value = mod.AdjustRank(Rank.Value, accuracy.NewValue);
|
Rank.Value = mod.AdjustRank(Rank.Value, accuracy.NewValue);
|
||||||
};
|
};
|
||||||
@ -369,22 +376,6 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ScoreRank rankFrom(double acc)
|
|
||||||
{
|
|
||||||
if (acc == 1)
|
|
||||||
return ScoreRank.X;
|
|
||||||
if (acc >= 0.95)
|
|
||||||
return ScoreRank.S;
|
|
||||||
if (acc >= 0.9)
|
|
||||||
return ScoreRank.A;
|
|
||||||
if (acc >= 0.8)
|
|
||||||
return ScoreRank.B;
|
|
||||||
if (acc >= 0.7)
|
|
||||||
return ScoreRank.C;
|
|
||||||
|
|
||||||
return ScoreRank.D;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Resets this ScoreProcessor to a default state.
|
/// Resets this ScoreProcessor to a default state.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -583,6 +574,62 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
hitEvents.Clear();
|
hitEvents.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region Static helper methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Given an accuracy (0..1), return the correct <see cref="ScoreRank"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static ScoreRank RankFromAccuracy(double accuracy)
|
||||||
|
{
|
||||||
|
if (accuracy == accuracy_cutoff_x)
|
||||||
|
return ScoreRank.X;
|
||||||
|
if (accuracy >= accuracy_cutoff_s)
|
||||||
|
return ScoreRank.S;
|
||||||
|
if (accuracy >= accuracy_cutoff_a)
|
||||||
|
return ScoreRank.A;
|
||||||
|
if (accuracy >= accuracy_cutoff_b)
|
||||||
|
return ScoreRank.B;
|
||||||
|
if (accuracy >= accuracy_cutoff_c)
|
||||||
|
return ScoreRank.C;
|
||||||
|
|
||||||
|
return ScoreRank.D;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Given a <see cref="ScoreRank"/>, return the cutoff accuracy (0..1).
|
||||||
|
/// Accuracy must be greater than or equal to the cutoff to qualify for the provided rank.
|
||||||
|
/// </summary>
|
||||||
|
public static double AccuracyCutoffFromRank(ScoreRank rank)
|
||||||
|
{
|
||||||
|
switch (rank)
|
||||||
|
{
|
||||||
|
case ScoreRank.X:
|
||||||
|
case ScoreRank.XH:
|
||||||
|
return accuracy_cutoff_x;
|
||||||
|
|
||||||
|
case ScoreRank.S:
|
||||||
|
case ScoreRank.SH:
|
||||||
|
return accuracy_cutoff_s;
|
||||||
|
|
||||||
|
case ScoreRank.A:
|
||||||
|
return accuracy_cutoff_a;
|
||||||
|
|
||||||
|
case ScoreRank.B:
|
||||||
|
return accuracy_cutoff_b;
|
||||||
|
|
||||||
|
case ScoreRank.C:
|
||||||
|
return accuracy_cutoff_c;
|
||||||
|
|
||||||
|
case ScoreRank.D:
|
||||||
|
return accuracy_cutoff_d;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(rank), rank, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stores the required scoring data that fulfils the minimum requirements for a <see cref="ScoreProcessor"/> to calculate score.
|
/// Stores the required scoring data that fulfils the minimum requirements for a <see cref="ScoreProcessor"/> to calculate score.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -157,7 +157,16 @@ namespace osu.Game.Screens.Edit
|
|||||||
|
|
||||||
private bool isNewBeatmap;
|
private bool isNewBeatmap;
|
||||||
|
|
||||||
protected override UserActivity InitialActivity => new UserActivity.Editing(Beatmap.Value.BeatmapInfo);
|
protected override UserActivity InitialActivity
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (Beatmap.Value.Metadata.Author.OnlineID == api.LocalUser.Value.OnlineID)
|
||||||
|
return new UserActivity.EditingBeatmap(Beatmap.Value.BeatmapInfo);
|
||||||
|
|
||||||
|
return new UserActivity.ModdingBeatmap(Beatmap.Value.BeatmapInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||||
=> dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
=> dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
||||||
|
@ -7,6 +7,7 @@ using osu.Framework.Screens;
|
|||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
|
using osu.Game.Users;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.GameplayTest
|
namespace osu.Game.Screens.Edit.GameplayTest
|
||||||
{
|
{
|
||||||
@ -15,6 +16,8 @@ namespace osu.Game.Screens.Edit.GameplayTest
|
|||||||
private readonly Editor editor;
|
private readonly Editor editor;
|
||||||
private readonly EditorState editorState;
|
private readonly EditorState editorState;
|
||||||
|
|
||||||
|
protected override UserActivity InitialActivity => new UserActivity.TestingBeatmap(Beatmap.Value.BeatmapInfo, Ruleset.Value);
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private MusicController musicController { get; set; } = null!;
|
private MusicController musicController { get; set; } = null!;
|
||||||
|
|
||||||
|
209
osu.Game/Screens/Edit/Timing/ControlPointList.cs
Normal file
209
osu.Game/Screens/Edit/Timing/ControlPointList.cs
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
// 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.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Edit.Timing
|
||||||
|
{
|
||||||
|
public partial class ControlPointList : CompositeDrawable
|
||||||
|
{
|
||||||
|
private OsuButton deleteButton = null!;
|
||||||
|
private ControlPointTable table = null!;
|
||||||
|
private OsuScrollContainer scroll = null!;
|
||||||
|
private RoundedButton addButton = null!;
|
||||||
|
|
||||||
|
private readonly IBindableList<ControlPointGroup> controlPointGroups = new BindableList<ControlPointGroup>();
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private EditorClock clock { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
protected EditorBeatmap Beatmap { get; private set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private Bindable<ControlPointGroup?> selectedGroup { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private IEditorChangeHandler? changeHandler { get; set; }
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OverlayColourProvider colours)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
const float margins = 10;
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Colour = colours.Background4,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Colour = colours.Background3,
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
Width = ControlPointTable.TIMING_COLUMN_WIDTH + margins,
|
||||||
|
},
|
||||||
|
scroll = new OsuScrollContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Child = table = new ControlPointTable(),
|
||||||
|
},
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.BottomRight,
|
||||||
|
Origin = Anchor.BottomRight,
|
||||||
|
Direction = FillDirection.Horizontal,
|
||||||
|
Margin = new MarginPadding(margins),
|
||||||
|
Spacing = new Vector2(5),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
deleteButton = new RoundedButton
|
||||||
|
{
|
||||||
|
Text = "-",
|
||||||
|
Size = new Vector2(30, 30),
|
||||||
|
Action = delete,
|
||||||
|
Anchor = Anchor.BottomRight,
|
||||||
|
Origin = Anchor.BottomRight,
|
||||||
|
},
|
||||||
|
addButton = new RoundedButton
|
||||||
|
{
|
||||||
|
Action = addNew,
|
||||||
|
Size = new Vector2(160, 30),
|
||||||
|
Anchor = Anchor.BottomRight,
|
||||||
|
Origin = Anchor.BottomRight,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
selectedGroup.BindValueChanged(selected =>
|
||||||
|
{
|
||||||
|
deleteButton.Enabled.Value = selected.NewValue != null;
|
||||||
|
|
||||||
|
addButton.Text = selected.NewValue != null
|
||||||
|
? "+ Clone to current time"
|
||||||
|
: "+ Add at current time";
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
controlPointGroups.BindTo(Beatmap.ControlPointInfo.Groups);
|
||||||
|
controlPointGroups.BindCollectionChanged((_, _) =>
|
||||||
|
{
|
||||||
|
table.ControlGroups = controlPointGroups;
|
||||||
|
changeHandler?.SaveState();
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
table.OnRowSelected += drawable => scroll.ScrollIntoView(drawable);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnClick(ClickEvent e)
|
||||||
|
{
|
||||||
|
selectedGroup.Value = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
trackActivePoint();
|
||||||
|
|
||||||
|
addButton.Enabled.Value = clock.CurrentTimeAccurate != selectedGroup.Value?.Time;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Type? trackedType;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Given the user has selected a control point group, we want to track any group which is
|
||||||
|
/// active at the current point in time which matches the type the user has selected.
|
||||||
|
///
|
||||||
|
/// So if the user is currently looking at a timing point and seeks into the future, a
|
||||||
|
/// future timing point would be automatically selected if it is now the new "current" point.
|
||||||
|
/// </summary>
|
||||||
|
private void trackActivePoint()
|
||||||
|
{
|
||||||
|
// For simplicity only match on the first type of the active control point.
|
||||||
|
if (selectedGroup.Value == null)
|
||||||
|
trackedType = null;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If the selected group only has one control point, update the tracking type.
|
||||||
|
if (selectedGroup.Value.ControlPoints.Count == 1)
|
||||||
|
trackedType = selectedGroup.Value?.ControlPoints.Single().GetType();
|
||||||
|
// If the selected group has more than one control point, choose the first as the tracking type
|
||||||
|
// if we don't already have a singular tracked type.
|
||||||
|
else if (trackedType == null)
|
||||||
|
trackedType = selectedGroup.Value?.ControlPoints.FirstOrDefault()?.GetType();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trackedType != null)
|
||||||
|
{
|
||||||
|
// We don't have an efficient way of looking up groups currently, only individual point types.
|
||||||
|
// To improve the efficiency of this in the future, we should reconsider the overall structure of ControlPointInfo.
|
||||||
|
|
||||||
|
// Find the next group which has the same type as the selected one.
|
||||||
|
var found = Beatmap.ControlPointInfo.Groups
|
||||||
|
.Where(g => g.ControlPoints.Any(cp => cp.GetType() == trackedType))
|
||||||
|
.LastOrDefault(g => g.Time <= clock.CurrentTimeAccurate);
|
||||||
|
|
||||||
|
if (found != null)
|
||||||
|
selectedGroup.Value = found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void delete()
|
||||||
|
{
|
||||||
|
if (selectedGroup.Value == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Beatmap.ControlPointInfo.RemoveGroup(selectedGroup.Value);
|
||||||
|
|
||||||
|
selectedGroup.Value = Beatmap.ControlPointInfo.Groups.FirstOrDefault(g => g.Time >= clock.CurrentTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addNew()
|
||||||
|
{
|
||||||
|
bool isFirstControlPoint = !Beatmap.ControlPointInfo.TimingPoints.Any();
|
||||||
|
|
||||||
|
var group = Beatmap.ControlPointInfo.GroupAt(clock.CurrentTime, true);
|
||||||
|
|
||||||
|
if (isFirstControlPoint)
|
||||||
|
group.Add(new TimingControlPoint());
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Try and create matching types from the currently selected control point.
|
||||||
|
var selected = selectedGroup.Value;
|
||||||
|
|
||||||
|
if (selected != null && !ReferenceEquals(selected, group))
|
||||||
|
{
|
||||||
|
foreach (var controlPoint in selected.ControlPoints)
|
||||||
|
{
|
||||||
|
group.Add(controlPoint.DeepClone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedGroup.Value = group;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,20 +1,11 @@
|
|||||||
// 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 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.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
|
||||||
using osu.Framework.Input.Events;
|
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Graphics.Containers;
|
|
||||||
using osu.Game.Graphics.UserInterface;
|
|
||||||
using osu.Game.Graphics.UserInterfaceV2;
|
|
||||||
using osu.Game.Overlays;
|
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Timing
|
namespace osu.Game.Screens.Edit.Timing
|
||||||
{
|
{
|
||||||
@ -23,6 +14,9 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
[Cached]
|
[Cached]
|
||||||
public readonly Bindable<ControlPointGroup> SelectedGroup = new Bindable<ControlPointGroup>();
|
public readonly Bindable<ControlPointGroup> SelectedGroup = new Bindable<ControlPointGroup>();
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private EditorClock? editorClock { get; set; }
|
||||||
|
|
||||||
public TimingScreen()
|
public TimingScreen()
|
||||||
: base(EditorScreenMode.Timing)
|
: base(EditorScreenMode.Timing)
|
||||||
{
|
{
|
||||||
@ -46,192 +40,17 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public partial class ControlPointList : CompositeDrawable
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
private OsuButton deleteButton = null!;
|
base.LoadComplete();
|
||||||
private ControlPointTable table = null!;
|
|
||||||
private OsuScrollContainer scroll = null!;
|
|
||||||
private RoundedButton addButton = null!;
|
|
||||||
|
|
||||||
private readonly IBindableList<ControlPointGroup> controlPointGroups = new BindableList<ControlPointGroup>();
|
if (editorClock != null)
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private EditorClock clock { get; set; } = null!;
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
protected EditorBeatmap Beatmap { get; private set; } = null!;
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private Bindable<ControlPointGroup?> selectedGroup { get; set; } = null!;
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private IEditorChangeHandler? changeHandler { get; set; }
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(OverlayColourProvider colours)
|
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both;
|
// When entering the timing screen, let's choose the closest valid timing point.
|
||||||
|
// This will emulate the osu-stable behaviour where a metronome and timing information
|
||||||
const float margins = 10;
|
// are presented on entering the screen.
|
||||||
InternalChildren = new Drawable[]
|
var nearestTimingPoint = EditorBeatmap.ControlPointInfo.TimingPointAt(editorClock.CurrentTime);
|
||||||
{
|
SelectedGroup.Value = EditorBeatmap.ControlPointInfo.GroupAt(nearestTimingPoint.Time);
|
||||||
new Box
|
|
||||||
{
|
|
||||||
Colour = colours.Background4,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
},
|
|
||||||
new Box
|
|
||||||
{
|
|
||||||
Colour = colours.Background3,
|
|
||||||
RelativeSizeAxes = Axes.Y,
|
|
||||||
Width = ControlPointTable.TIMING_COLUMN_WIDTH + margins,
|
|
||||||
},
|
|
||||||
scroll = new OsuScrollContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Child = table = new ControlPointTable(),
|
|
||||||
},
|
|
||||||
new FillFlowContainer
|
|
||||||
{
|
|
||||||
AutoSizeAxes = Axes.Both,
|
|
||||||
Anchor = Anchor.BottomRight,
|
|
||||||
Origin = Anchor.BottomRight,
|
|
||||||
Direction = FillDirection.Horizontal,
|
|
||||||
Margin = new MarginPadding(margins),
|
|
||||||
Spacing = new Vector2(5),
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
deleteButton = new RoundedButton
|
|
||||||
{
|
|
||||||
Text = "-",
|
|
||||||
Size = new Vector2(30, 30),
|
|
||||||
Action = delete,
|
|
||||||
Anchor = Anchor.BottomRight,
|
|
||||||
Origin = Anchor.BottomRight,
|
|
||||||
},
|
|
||||||
addButton = new RoundedButton
|
|
||||||
{
|
|
||||||
Action = addNew,
|
|
||||||
Size = new Vector2(160, 30),
|
|
||||||
Anchor = Anchor.BottomRight,
|
|
||||||
Origin = Anchor.BottomRight,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void LoadComplete()
|
|
||||||
{
|
|
||||||
base.LoadComplete();
|
|
||||||
|
|
||||||
selectedGroup.BindValueChanged(selected =>
|
|
||||||
{
|
|
||||||
deleteButton.Enabled.Value = selected.NewValue != null;
|
|
||||||
|
|
||||||
addButton.Text = selected.NewValue != null
|
|
||||||
? "+ Clone to current time"
|
|
||||||
: "+ Add at current time";
|
|
||||||
}, true);
|
|
||||||
|
|
||||||
controlPointGroups.BindTo(Beatmap.ControlPointInfo.Groups);
|
|
||||||
controlPointGroups.BindCollectionChanged((_, _) =>
|
|
||||||
{
|
|
||||||
table.ControlGroups = controlPointGroups;
|
|
||||||
changeHandler?.SaveState();
|
|
||||||
}, true);
|
|
||||||
|
|
||||||
table.OnRowSelected += drawable => scroll.ScrollIntoView(drawable);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool OnClick(ClickEvent e)
|
|
||||||
{
|
|
||||||
selectedGroup.Value = null;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Update()
|
|
||||||
{
|
|
||||||
base.Update();
|
|
||||||
|
|
||||||
trackActivePoint();
|
|
||||||
|
|
||||||
addButton.Enabled.Value = clock.CurrentTimeAccurate != selectedGroup.Value?.Time;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Type? trackedType;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Given the user has selected a control point group, we want to track any group which is
|
|
||||||
/// active at the current point in time which matches the type the user has selected.
|
|
||||||
///
|
|
||||||
/// So if the user is currently looking at a timing point and seeks into the future, a
|
|
||||||
/// future timing point would be automatically selected if it is now the new "current" point.
|
|
||||||
/// </summary>
|
|
||||||
private void trackActivePoint()
|
|
||||||
{
|
|
||||||
// For simplicity only match on the first type of the active control point.
|
|
||||||
if (selectedGroup.Value == null)
|
|
||||||
trackedType = null;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// If the selected group only has one control point, update the tracking type.
|
|
||||||
if (selectedGroup.Value.ControlPoints.Count == 1)
|
|
||||||
trackedType = selectedGroup.Value?.ControlPoints.Single().GetType();
|
|
||||||
// If the selected group has more than one control point, choose the first as the tracking type
|
|
||||||
// if we don't already have a singular tracked type.
|
|
||||||
else if (trackedType == null)
|
|
||||||
trackedType = selectedGroup.Value?.ControlPoints.FirstOrDefault()?.GetType();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (trackedType != null)
|
|
||||||
{
|
|
||||||
// We don't have an efficient way of looking up groups currently, only individual point types.
|
|
||||||
// To improve the efficiency of this in the future, we should reconsider the overall structure of ControlPointInfo.
|
|
||||||
|
|
||||||
// Find the next group which has the same type as the selected one.
|
|
||||||
var found = Beatmap.ControlPointInfo.Groups
|
|
||||||
.Where(g => g.ControlPoints.Any(cp => cp.GetType() == trackedType))
|
|
||||||
.LastOrDefault(g => g.Time <= clock.CurrentTimeAccurate);
|
|
||||||
|
|
||||||
if (found != null)
|
|
||||||
selectedGroup.Value = found;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void delete()
|
|
||||||
{
|
|
||||||
if (selectedGroup.Value == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Beatmap.ControlPointInfo.RemoveGroup(selectedGroup.Value);
|
|
||||||
|
|
||||||
selectedGroup.Value = Beatmap.ControlPointInfo.Groups.FirstOrDefault(g => g.Time >= clock.CurrentTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addNew()
|
|
||||||
{
|
|
||||||
bool isFirstControlPoint = !Beatmap.ControlPointInfo.TimingPoints.Any();
|
|
||||||
|
|
||||||
var group = Beatmap.ControlPointInfo.GroupAt(clock.CurrentTime, true);
|
|
||||||
|
|
||||||
if (isFirstControlPoint)
|
|
||||||
group.Add(new TimingControlPoint());
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Try and create matching types from the currently selected control point.
|
|
||||||
var selected = selectedGroup.Value;
|
|
||||||
|
|
||||||
if (selected != null && !ReferenceEquals(selected, group))
|
|
||||||
{
|
|
||||||
foreach (var controlPoint in selected.ControlPoints)
|
|
||||||
{
|
|
||||||
group.Add(controlPoint.DeepClone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
selectedGroup.Value = group;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Colour;
|
using osu.Framework.Graphics.Colour;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -22,29 +20,21 @@ namespace osu.Game.Screens.Play.Break
|
|||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
new Container
|
new Box
|
||||||
{
|
{
|
||||||
Anchor = Anchor.TopLeft,
|
Anchor = Anchor.TopLeft,
|
||||||
Origin = Anchor.TopLeft,
|
Origin = Anchor.TopLeft,
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Height = height,
|
Height = height,
|
||||||
Child = new Box
|
Colour = ColourInfo.GradientVertical(Color4.Black, transparent_black),
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Colour = ColourInfo.GradientVertical(Color4.Black, transparent_black),
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
new Container
|
new Box
|
||||||
{
|
{
|
||||||
Anchor = Anchor.BottomLeft,
|
Anchor = Anchor.BottomLeft,
|
||||||
Origin = Anchor.BottomLeft,
|
Origin = Anchor.BottomLeft,
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Height = height,
|
Height = height,
|
||||||
Child = new Box
|
Colour = ColourInfo.GradientVertical(transparent_black, Color4.Black),
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Colour = ColourInfo.GradientVertical(transparent_black, Color4.Black),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -121,8 +121,8 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
AutoSizeAxes = Axes.Both,
|
AutoSizeAxes = Axes.Both,
|
||||||
Child = new UprightAspectMaintainingContainer
|
Child = new UprightAspectMaintainingContainer
|
||||||
{
|
{
|
||||||
Origin = Anchor.CentreRight,
|
Origin = Anchor.Centre,
|
||||||
Anchor = Anchor.CentreRight,
|
Anchor = Anchor.Centre,
|
||||||
AutoSizeAxes = Axes.Both,
|
AutoSizeAxes = Axes.Both,
|
||||||
Scaling = ScaleMode.Vertical,
|
Scaling = ScaleMode.Vertical,
|
||||||
ScalingFactor = 0.5f,
|
ScalingFactor = 0.5f,
|
||||||
|
@ -15,6 +15,7 @@ using osu.Game.Rulesets.Mods;
|
|||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Screens.Play.HUD;
|
using osu.Game.Screens.Play.HUD;
|
||||||
using osu.Game.Screens.Ranking;
|
using osu.Game.Screens.Ranking;
|
||||||
|
using osu.Game.Users;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Play
|
namespace osu.Game.Screens.Play
|
||||||
{
|
{
|
||||||
@ -24,6 +25,8 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
private readonly bool replayIsFailedScore;
|
private readonly bool replayIsFailedScore;
|
||||||
|
|
||||||
|
protected override UserActivity InitialActivity => new UserActivity.WatchingReplay(Score.ScoreInfo);
|
||||||
|
|
||||||
// Disallow replays from failing. (see https://github.com/ppy/osu/issues/6108)
|
// Disallow replays from failing. (see https://github.com/ppy/osu/issues/6108)
|
||||||
protected override bool CheckModsAllowFailure()
|
protected override bool CheckModsAllowFailure()
|
||||||
{
|
{
|
||||||
|
@ -7,6 +7,7 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Game.Online.Spectator;
|
using osu.Game.Online.Spectator;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Users;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Play
|
namespace osu.Game.Screens.Play
|
||||||
{
|
{
|
||||||
@ -14,6 +15,8 @@ namespace osu.Game.Screens.Play
|
|||||||
{
|
{
|
||||||
private readonly Score score;
|
private readonly Score score;
|
||||||
|
|
||||||
|
protected override UserActivity InitialActivity => new UserActivity.SpectatingUser(Score.ScoreInfo);
|
||||||
|
|
||||||
public SoloSpectatorPlayer(Score score, PlayerConfiguration configuration = null)
|
public SoloSpectatorPlayer(Score score, PlayerConfiguration configuration = null)
|
||||||
: base(score, configuration)
|
: base(score, configuration)
|
||||||
{
|
{
|
||||||
|
@ -17,6 +17,7 @@ using osu.Framework.Utils;
|
|||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -28,6 +29,13 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class AccuracyCircle : CompositeDrawable
|
public partial class AccuracyCircle : CompositeDrawable
|
||||||
{
|
{
|
||||||
|
private static readonly double accuracy_x = ScoreProcessor.AccuracyCutoffFromRank(ScoreRank.X);
|
||||||
|
private static readonly double accuracy_s = ScoreProcessor.AccuracyCutoffFromRank(ScoreRank.S);
|
||||||
|
private static readonly double accuracy_a = ScoreProcessor.AccuracyCutoffFromRank(ScoreRank.A);
|
||||||
|
private static readonly double accuracy_b = ScoreProcessor.AccuracyCutoffFromRank(ScoreRank.B);
|
||||||
|
private static readonly double accuracy_c = ScoreProcessor.AccuracyCutoffFromRank(ScoreRank.C);
|
||||||
|
private static readonly double accuracy_d = ScoreProcessor.AccuracyCutoffFromRank(ScoreRank.D);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Duration for the transforms causing this component to appear.
|
/// Duration for the transforms causing this component to appear.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -73,6 +81,11 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private const double virtual_ss_percentage = 0.01;
|
private const double virtual_ss_percentage = 0.01;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The width of a <see cref="RankNotch"/> in terms of accuracy.
|
||||||
|
/// </summary>
|
||||||
|
public const double NOTCH_WIDTH_PERCENTAGE = 1.0 / 360;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The easing for the circle filling transforms.
|
/// The easing for the circle filling transforms.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -145,49 +158,49 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = OsuColour.ForRank(ScoreRank.X),
|
Colour = OsuColour.ForRank(ScoreRank.X),
|
||||||
InnerRadius = RANK_CIRCLE_RADIUS,
|
InnerRadius = RANK_CIRCLE_RADIUS,
|
||||||
Current = { Value = 1 }
|
Current = { Value = accuracy_x }
|
||||||
},
|
},
|
||||||
new CircularProgress
|
new CircularProgress
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = OsuColour.ForRank(ScoreRank.S),
|
Colour = OsuColour.ForRank(ScoreRank.S),
|
||||||
InnerRadius = RANK_CIRCLE_RADIUS,
|
InnerRadius = RANK_CIRCLE_RADIUS,
|
||||||
Current = { Value = 1 - virtual_ss_percentage }
|
Current = { Value = accuracy_x - virtual_ss_percentage }
|
||||||
},
|
},
|
||||||
new CircularProgress
|
new CircularProgress
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = OsuColour.ForRank(ScoreRank.A),
|
Colour = OsuColour.ForRank(ScoreRank.A),
|
||||||
InnerRadius = RANK_CIRCLE_RADIUS,
|
InnerRadius = RANK_CIRCLE_RADIUS,
|
||||||
Current = { Value = 0.95f }
|
Current = { Value = accuracy_s }
|
||||||
},
|
},
|
||||||
new CircularProgress
|
new CircularProgress
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = OsuColour.ForRank(ScoreRank.B),
|
Colour = OsuColour.ForRank(ScoreRank.B),
|
||||||
InnerRadius = RANK_CIRCLE_RADIUS,
|
InnerRadius = RANK_CIRCLE_RADIUS,
|
||||||
Current = { Value = 0.9f }
|
Current = { Value = accuracy_a }
|
||||||
},
|
},
|
||||||
new CircularProgress
|
new CircularProgress
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = OsuColour.ForRank(ScoreRank.C),
|
Colour = OsuColour.ForRank(ScoreRank.C),
|
||||||
InnerRadius = RANK_CIRCLE_RADIUS,
|
InnerRadius = RANK_CIRCLE_RADIUS,
|
||||||
Current = { Value = 0.8f }
|
Current = { Value = accuracy_b }
|
||||||
},
|
},
|
||||||
new CircularProgress
|
new CircularProgress
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = OsuColour.ForRank(ScoreRank.D),
|
Colour = OsuColour.ForRank(ScoreRank.D),
|
||||||
InnerRadius = RANK_CIRCLE_RADIUS,
|
InnerRadius = RANK_CIRCLE_RADIUS,
|
||||||
Current = { Value = 0.7f }
|
Current = { Value = accuracy_c }
|
||||||
},
|
},
|
||||||
new RankNotch(0),
|
new RankNotch((float)accuracy_x),
|
||||||
new RankNotch((float)(1 - virtual_ss_percentage)),
|
new RankNotch((float)(accuracy_x - virtual_ss_percentage)),
|
||||||
new RankNotch(0.95f),
|
new RankNotch((float)accuracy_s),
|
||||||
new RankNotch(0.9f),
|
new RankNotch((float)accuracy_a),
|
||||||
new RankNotch(0.8f),
|
new RankNotch((float)accuracy_b),
|
||||||
new RankNotch(0.7f),
|
new RankNotch((float)accuracy_c),
|
||||||
new BufferedContainer
|
new BufferedContainer
|
||||||
{
|
{
|
||||||
Name = "Graded circle mask",
|
Name = "Graded circle mask",
|
||||||
@ -215,12 +228,13 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
|||||||
Padding = new MarginPadding { Vertical = -15, Horizontal = -20 },
|
Padding = new MarginPadding { Vertical = -15, Horizontal = -20 },
|
||||||
Children = new[]
|
Children = new[]
|
||||||
{
|
{
|
||||||
new RankBadge(1, getRank(ScoreRank.X)),
|
// The S and A badges are moved down slightly to prevent collision with the SS badge.
|
||||||
new RankBadge(0.95, getRank(ScoreRank.S)),
|
new RankBadge(accuracy_x, accuracy_x, getRank(ScoreRank.X)),
|
||||||
new RankBadge(0.9, getRank(ScoreRank.A)),
|
new RankBadge(accuracy_s, Interpolation.Lerp(accuracy_s, (accuracy_x - virtual_ss_percentage), 0.25), getRank(ScoreRank.S)),
|
||||||
new RankBadge(0.8, getRank(ScoreRank.B)),
|
new RankBadge(accuracy_a, Interpolation.Lerp(accuracy_a, accuracy_s, 0.25), getRank(ScoreRank.A)),
|
||||||
new RankBadge(0.7, getRank(ScoreRank.C)),
|
new RankBadge(accuracy_b, Interpolation.Lerp(accuracy_b, accuracy_a, 0.5), getRank(ScoreRank.B)),
|
||||||
new RankBadge(0.35, getRank(ScoreRank.D)),
|
new RankBadge(accuracy_c, Interpolation.Lerp(accuracy_c, accuracy_b, 0.5), getRank(ScoreRank.C)),
|
||||||
|
new RankBadge(accuracy_d, Interpolation.Lerp(accuracy_d, accuracy_c, 0.5), getRank(ScoreRank.D)),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
rankText = new RankText(score.Rank)
|
rankText = new RankText(score.Rank)
|
||||||
@ -263,7 +277,39 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
|||||||
|
|
||||||
using (BeginDelayedSequence(ACCURACY_TRANSFORM_DELAY))
|
using (BeginDelayedSequence(ACCURACY_TRANSFORM_DELAY))
|
||||||
{
|
{
|
||||||
double targetAccuracy = score.Rank == ScoreRank.X || score.Rank == ScoreRank.XH ? 1 : Math.Min(1 - virtual_ss_percentage, score.Accuracy);
|
double targetAccuracy = score.Accuracy;
|
||||||
|
double[] notchPercentages =
|
||||||
|
{
|
||||||
|
accuracy_s,
|
||||||
|
accuracy_a,
|
||||||
|
accuracy_b,
|
||||||
|
accuracy_c,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Ensure the gauge overshoots or undershoots a bit so it doesn't land in the gaps of the inner graded circle (caused by `RankNotch`es),
|
||||||
|
// to prevent ambiguity on what grade it's pointing at.
|
||||||
|
foreach (double p in notchPercentages)
|
||||||
|
{
|
||||||
|
if (Precision.AlmostEquals(p, targetAccuracy, NOTCH_WIDTH_PERCENTAGE / 2))
|
||||||
|
{
|
||||||
|
int tippingDirection = targetAccuracy - p >= 0 ? 1 : -1; // We "round up" here to match rank criteria
|
||||||
|
targetAccuracy = p + tippingDirection * (NOTCH_WIDTH_PERCENTAGE / 2);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The final gap between 99.999...% (S) and 100% (SS) is exaggerated by `virtual_ss_percentage`. We don't want to land there either.
|
||||||
|
if (score.Rank == ScoreRank.X || score.Rank == ScoreRank.XH)
|
||||||
|
targetAccuracy = 1;
|
||||||
|
else
|
||||||
|
targetAccuracy = Math.Min(accuracy_x - virtual_ss_percentage - NOTCH_WIDTH_PERCENTAGE / 2, targetAccuracy);
|
||||||
|
|
||||||
|
// The accuracy circle gauge visually fills up a bit too much.
|
||||||
|
// This wouldn't normally matter but we want it to align properly with the inner graded circle in the above cases.
|
||||||
|
const double visual_alignment_offset = 0.001;
|
||||||
|
|
||||||
|
if (targetAccuracy < 1 && targetAccuracy >= visual_alignment_offset)
|
||||||
|
targetAccuracy -= visual_alignment_offset;
|
||||||
|
|
||||||
accuracyCircle.FillTo(targetAccuracy, ACCURACY_TRANSFORM_DURATION, ACCURACY_TRANSFORM_EASING);
|
accuracyCircle.FillTo(targetAccuracy, ACCURACY_TRANSFORM_DURATION, ACCURACY_TRANSFORM_EASING);
|
||||||
|
|
||||||
@ -293,7 +339,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
|||||||
if (badge.Accuracy > score.Accuracy)
|
if (badge.Accuracy > score.Accuracy)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
using (BeginDelayedSequence(inverseEasing(ACCURACY_TRANSFORM_EASING, Math.Min(1 - virtual_ss_percentage, badge.Accuracy) / targetAccuracy) * ACCURACY_TRANSFORM_DURATION))
|
using (BeginDelayedSequence(inverseEasing(ACCURACY_TRANSFORM_EASING, Math.Min(accuracy_x - virtual_ss_percentage, badge.Accuracy) / targetAccuracy) * ACCURACY_TRANSFORM_DURATION))
|
||||||
{
|
{
|
||||||
badge.Appear();
|
badge.Appear();
|
||||||
|
|
||||||
|
@ -27,6 +27,11 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly double Accuracy;
|
public readonly double Accuracy;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The position around the <see cref="AccuracyCircle"/> to display this badge.
|
||||||
|
/// </summary>
|
||||||
|
private readonly double displayPosition;
|
||||||
|
|
||||||
private readonly ScoreRank rank;
|
private readonly ScoreRank rank;
|
||||||
|
|
||||||
private Drawable rankContainer;
|
private Drawable rankContainer;
|
||||||
@ -36,10 +41,12 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
|||||||
/// Creates a new <see cref="RankBadge"/>.
|
/// Creates a new <see cref="RankBadge"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="accuracy">The accuracy value corresponding to <paramref name="rank"/>.</param>
|
/// <param name="accuracy">The accuracy value corresponding to <paramref name="rank"/>.</param>
|
||||||
|
/// <param name="position">The position around the <see cref="AccuracyCircle"/> to display this badge.</param>
|
||||||
/// <param name="rank">The <see cref="ScoreRank"/> to be displayed in this <see cref="RankBadge"/>.</param>
|
/// <param name="rank">The <see cref="ScoreRank"/> to be displayed in this <see cref="RankBadge"/>.</param>
|
||||||
public RankBadge(double accuracy, ScoreRank rank)
|
public RankBadge(double accuracy, double position, ScoreRank rank)
|
||||||
{
|
{
|
||||||
Accuracy = accuracy;
|
Accuracy = accuracy;
|
||||||
|
displayPosition = position;
|
||||||
this.rank = rank;
|
this.rank = rank;
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
@ -92,7 +99,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
|||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
// Starts at -90deg (top) and moves counter-clockwise by the accuracy
|
// Starts at -90deg (top) and moves counter-clockwise by the accuracy
|
||||||
rankContainer.Position = circlePosition(-MathF.PI / 2 - (1 - (float)Accuracy) * MathF.PI * 2);
|
rankContainer.Position = circlePosition(-MathF.PI / 2 - (1 - (float)displayPosition) * MathF.PI * 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Vector2 circlePosition(float t)
|
private Vector2 circlePosition(float t)
|
||||||
|
@ -41,7 +41,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
|
|||||||
Origin = Anchor.TopCentre,
|
Origin = Anchor.TopCentre,
|
||||||
RelativeSizeAxes = Axes.Y,
|
RelativeSizeAxes = Axes.Y,
|
||||||
Height = AccuracyCircle.RANK_CIRCLE_RADIUS,
|
Height = AccuracyCircle.RANK_CIRCLE_RADIUS,
|
||||||
Width = 1f,
|
Width = (float)AccuracyCircle.NOTCH_WIDTH_PERCENTAGE * 360f,
|
||||||
Colour = OsuColour.Gray(0.3f),
|
Colour = OsuColour.Gray(0.3f),
|
||||||
EdgeSmoothness = new Vector2(1f)
|
EdgeSmoothness = new Vector2(1f)
|
||||||
}
|
}
|
||||||
|
20
osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs
Normal file
20
osu.Game/Screens/Select/FooterV2/FooterButtonModsV2.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Select.FooterV2
|
||||||
|
{
|
||||||
|
public partial class FooterButtonModsV2 : FooterButtonV2
|
||||||
|
{
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colour)
|
||||||
|
{
|
||||||
|
Text = "Mods";
|
||||||
|
Icon = FontAwesome.Solid.ExchangeAlt;
|
||||||
|
AccentColour = colour.Lime1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
osu.Game/Screens/Select/FooterV2/FooterButtonOptionsV2.cs
Normal file
22
osu.Game/Screens/Select/FooterV2/FooterButtonOptionsV2.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Input.Bindings;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Select.FooterV2
|
||||||
|
{
|
||||||
|
public partial class FooterButtonOptionsV2 : FooterButtonV2
|
||||||
|
{
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colour)
|
||||||
|
{
|
||||||
|
Text = "Options";
|
||||||
|
Icon = FontAwesome.Solid.Cog;
|
||||||
|
AccentColour = colour.Purple1;
|
||||||
|
Hotkey = GlobalAction.ToggleBeatmapOptions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
161
osu.Game/Screens/Select/FooterV2/FooterButtonRandomV2.cs
Normal file
161
osu.Game/Screens/Select/FooterV2/FooterButtonRandomV2.cs
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
// 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.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Input.Bindings;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Select.FooterV2
|
||||||
|
{
|
||||||
|
public partial class FooterButtonRandomV2 : FooterButtonV2
|
||||||
|
{
|
||||||
|
public Action? NextRandom { get; set; }
|
||||||
|
public Action? PreviousRandom { get; set; }
|
||||||
|
|
||||||
|
private Container persistentText = null!;
|
||||||
|
private OsuSpriteText randomSpriteText = null!;
|
||||||
|
private OsuSpriteText rewindSpriteText = null!;
|
||||||
|
private bool rewindSearch;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colour)
|
||||||
|
{
|
||||||
|
//TODO: use https://fontawesome.com/icons/shuffle?s=solid&f=classic when local Fontawesome is updated
|
||||||
|
Icon = FontAwesome.Solid.Random;
|
||||||
|
AccentColour = colour.Blue1;
|
||||||
|
TextContainer.Add(persistentText = new Container
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
AlwaysPresent = true,
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Children = new[]
|
||||||
|
{
|
||||||
|
randomSpriteText = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Font = OsuFont.TorusAlternate.With(size: 19),
|
||||||
|
AlwaysPresent = true,
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Text = "Random",
|
||||||
|
},
|
||||||
|
rewindSpriteText = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Font = OsuFont.TorusAlternate.With(size: 19),
|
||||||
|
AlwaysPresent = true,
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Text = "Rewind",
|
||||||
|
Alpha = 0f,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Action = () =>
|
||||||
|
{
|
||||||
|
if (rewindSearch)
|
||||||
|
{
|
||||||
|
const double fade_time = 500;
|
||||||
|
|
||||||
|
OsuSpriteText fallingRewind;
|
||||||
|
|
||||||
|
TextContainer.Add(fallingRewind = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Alpha = 0,
|
||||||
|
Text = rewindSpriteText.Text,
|
||||||
|
AlwaysPresent = true, // make sure the button is sized large enough to always show this
|
||||||
|
Anchor = Anchor.BottomCentre,
|
||||||
|
Origin = Anchor.BottomCentre,
|
||||||
|
Font = OsuFont.TorusAlternate.With(size: 19),
|
||||||
|
});
|
||||||
|
|
||||||
|
fallingRewind.FadeOutFromOne(fade_time, Easing.In);
|
||||||
|
fallingRewind.MoveTo(Vector2.Zero).MoveTo(new Vector2(0, 10), fade_time, Easing.In);
|
||||||
|
fallingRewind.Expire();
|
||||||
|
|
||||||
|
persistentText.FadeInFromZero(fade_time, Easing.In);
|
||||||
|
|
||||||
|
PreviousRandom?.Invoke();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NextRandom?.Invoke();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnKeyDown(KeyDownEvent e)
|
||||||
|
{
|
||||||
|
updateText(e.ShiftPressed);
|
||||||
|
return base.OnKeyDown(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnKeyUp(KeyUpEvent e)
|
||||||
|
{
|
||||||
|
updateText(e.ShiftPressed);
|
||||||
|
base.OnKeyUp(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnClick(ClickEvent e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// this uses OR to handle rewinding when clicks are triggered by other sources (i.e. right button in OnMouseUp).
|
||||||
|
rewindSearch |= e.ShiftPressed;
|
||||||
|
return base.OnClick(e);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
rewindSearch = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnMouseUp(MouseUpEvent e)
|
||||||
|
{
|
||||||
|
if (e.Button == MouseButton.Right)
|
||||||
|
{
|
||||||
|
rewindSearch = true;
|
||||||
|
TriggerClick();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
base.OnMouseUp(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||||
|
{
|
||||||
|
rewindSearch = e.Action == GlobalAction.SelectPreviousRandom;
|
||||||
|
|
||||||
|
if (e.Action != GlobalAction.SelectNextRandom && e.Action != GlobalAction.SelectPreviousRandom)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!e.Repeat)
|
||||||
|
TriggerClick();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnReleased(KeyBindingReleaseEvent<GlobalAction> e)
|
||||||
|
{
|
||||||
|
if (e.Action == GlobalAction.SelectPreviousRandom)
|
||||||
|
{
|
||||||
|
rewindSearch = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateText(bool rewind = false)
|
||||||
|
{
|
||||||
|
randomSpriteText.Alpha = rewind ? 0 : 1;
|
||||||
|
rewindSpriteText.Alpha = rewind ? 1 : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
211
osu.Game/Screens/Select/FooterV2/FooterButtonV2.cs
Normal file
211
osu.Game/Screens/Select/FooterV2/FooterButtonV2.cs
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
// 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.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Effects;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Input.Bindings;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Input.Bindings;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Select.FooterV2
|
||||||
|
{
|
||||||
|
public partial class FooterButtonV2 : OsuClickableContainer, IKeyBindingHandler<GlobalAction>
|
||||||
|
{
|
||||||
|
private const int button_height = 90;
|
||||||
|
private const int button_width = 140;
|
||||||
|
private const int corner_radius = 10;
|
||||||
|
private const int transition_length = 500;
|
||||||
|
|
||||||
|
// This should be 12 by design, but an extra allowance is added due to the corner radius specification.
|
||||||
|
public const float SHEAR_WIDTH = 13.5f;
|
||||||
|
|
||||||
|
public Bindable<Visibility> OverlayState = new Bindable<Visibility>();
|
||||||
|
|
||||||
|
protected static readonly Vector2 SHEAR = new Vector2(SHEAR_WIDTH / button_height, 0);
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||||
|
|
||||||
|
private Colour4 buttonAccentColour;
|
||||||
|
|
||||||
|
protected Colour4 AccentColour
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
buttonAccentColour = value;
|
||||||
|
bar.Colour = buttonAccentColour;
|
||||||
|
icon.Colour = buttonAccentColour;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected IconUsage Icon
|
||||||
|
{
|
||||||
|
set => icon.Icon = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected LocalisableString Text
|
||||||
|
{
|
||||||
|
set => text.Text = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly SpriteText text;
|
||||||
|
private readonly SpriteIcon icon;
|
||||||
|
|
||||||
|
protected Container TextContainer;
|
||||||
|
private readonly Box bar;
|
||||||
|
private readonly Box backgroundBox;
|
||||||
|
|
||||||
|
public FooterButtonV2()
|
||||||
|
{
|
||||||
|
EdgeEffect = new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Type = EdgeEffectType.Shadow,
|
||||||
|
Radius = 4,
|
||||||
|
// Figma says 50% opacity, but it does not match up visually if taken at face value, and looks bad.
|
||||||
|
Colour = Colour4.Black.Opacity(0.25f),
|
||||||
|
Offset = new Vector2(0, 2),
|
||||||
|
};
|
||||||
|
Shear = SHEAR;
|
||||||
|
Size = new Vector2(button_width, button_height);
|
||||||
|
Masking = true;
|
||||||
|
CornerRadius = corner_radius;
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
backgroundBox = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
|
},
|
||||||
|
|
||||||
|
// For elements that should not be sheared.
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Shear = -SHEAR,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
TextContainer = new Container
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Y = 42,
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Child = text = new OsuSpriteText
|
||||||
|
{
|
||||||
|
// figma design says the size is 16, but due to the issues with font sizes 19 matches better
|
||||||
|
Font = OsuFont.TorusAlternate.With(size: 19),
|
||||||
|
AlwaysPresent = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
icon = new SpriteIcon
|
||||||
|
{
|
||||||
|
Y = 12,
|
||||||
|
Size = new Vector2(20),
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
Shear = -SHEAR,
|
||||||
|
Anchor = Anchor.BottomCentre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Y = -corner_radius,
|
||||||
|
Size = new Vector2(120, 6),
|
||||||
|
Masking = true,
|
||||||
|
CornerRadius = 3,
|
||||||
|
Child = bar = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
OverlayState.BindValueChanged(_ => updateDisplay());
|
||||||
|
Enabled.BindValueChanged(_ => updateDisplay(), true);
|
||||||
|
|
||||||
|
FinishTransforms(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GlobalAction? Hotkey;
|
||||||
|
|
||||||
|
private bool handlingMouse;
|
||||||
|
|
||||||
|
protected override bool OnHover(HoverEvent e)
|
||||||
|
{
|
||||||
|
updateDisplay();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnMouseDown(MouseDownEvent e)
|
||||||
|
{
|
||||||
|
handlingMouse = true;
|
||||||
|
updateDisplay();
|
||||||
|
return base.OnMouseDown(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnMouseUp(MouseUpEvent e)
|
||||||
|
{
|
||||||
|
handlingMouse = false;
|
||||||
|
updateDisplay();
|
||||||
|
base.OnMouseUp(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnHoverLost(HoverLostEvent e) => updateDisplay();
|
||||||
|
|
||||||
|
public virtual bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||||
|
{
|
||||||
|
if (e.Action != Hotkey || e.Repeat) return false;
|
||||||
|
|
||||||
|
TriggerClick();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void OnReleased(KeyBindingReleaseEvent<GlobalAction> e) { }
|
||||||
|
|
||||||
|
private void updateDisplay()
|
||||||
|
{
|
||||||
|
Color4 backgroundColour = colourProvider.Background3;
|
||||||
|
|
||||||
|
if (!Enabled.Value)
|
||||||
|
{
|
||||||
|
backgroundColour = colourProvider.Background3.Darken(0.4f);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (OverlayState.Value == Visibility.Visible)
|
||||||
|
backgroundColour = buttonAccentColour.Darken(0.5f);
|
||||||
|
|
||||||
|
if (IsHovered)
|
||||||
|
{
|
||||||
|
backgroundColour = backgroundColour.Lighten(0.3f);
|
||||||
|
|
||||||
|
if (handlingMouse)
|
||||||
|
backgroundColour = backgroundColour.Lighten(0.3f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
backgroundBox.FadeColour(backgroundColour, transition_length, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
75
osu.Game/Screens/Select/FooterV2/FooterV2.cs
Normal file
75
osu.Game/Screens/Select/FooterV2/FooterV2.cs
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Select.FooterV2
|
||||||
|
{
|
||||||
|
public partial class FooterV2 : InputBlockingContainer
|
||||||
|
{
|
||||||
|
//Should be 60, setting to 50 for now for the sake of matching the current BackButton height.
|
||||||
|
private const int height = 50;
|
||||||
|
private const int padding = 80;
|
||||||
|
|
||||||
|
private readonly List<OverlayContainer> overlays = new List<OverlayContainer>();
|
||||||
|
|
||||||
|
/// <param name="button">The button to be added.</param>
|
||||||
|
/// <param name="overlay">The <see cref="OverlayContainer"/> to be toggled by this button.</param>
|
||||||
|
public void AddButton(FooterButtonV2 button, OverlayContainer? overlay = null)
|
||||||
|
{
|
||||||
|
if (overlay != null)
|
||||||
|
{
|
||||||
|
overlays.Add(overlay);
|
||||||
|
button.Action = () => showOverlay(overlay);
|
||||||
|
button.OverlayState.BindTo(overlay.State);
|
||||||
|
}
|
||||||
|
|
||||||
|
buttons.Add(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showOverlay(OverlayContainer overlay)
|
||||||
|
{
|
||||||
|
foreach (var o in overlays)
|
||||||
|
{
|
||||||
|
if (o == overlay)
|
||||||
|
o.ToggleVisibility();
|
||||||
|
else
|
||||||
|
o.Hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private FillFlowContainer<FooterButtonV2> buttons = null!;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OverlayColourProvider colourProvider)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
Height = height;
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = colourProvider.Background5
|
||||||
|
},
|
||||||
|
buttons = new FillFlowContainer<FooterButtonV2>
|
||||||
|
{
|
||||||
|
Position = new Vector2(TwoLayerButton.SIZE_EXTENDED.X + padding, 10),
|
||||||
|
Anchor = Anchor.BottomLeft,
|
||||||
|
Origin = Anchor.BottomLeft,
|
||||||
|
Direction = FillDirection.Horizontal,
|
||||||
|
Spacing = new Vector2(-FooterButtonV2.SHEAR_WIDTH + 7, 0),
|
||||||
|
AutoSizeAxes = Axes.Both
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,6 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual
|
namespace osu.Game.Tests.Visual
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1,11 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
using System;
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
@ -13,56 +10,49 @@ using osu.Game.Online.API.Requests.Responses;
|
|||||||
|
|
||||||
namespace osu.Game.Users.Drawables
|
namespace osu.Game.Users.Drawables
|
||||||
{
|
{
|
||||||
public partial class ClickableAvatar : Container
|
public partial class ClickableAvatar : OsuClickableContainer
|
||||||
{
|
{
|
||||||
private const string default_tooltip_text = "view profile";
|
private const string default_tooltip_text = "view profile";
|
||||||
|
|
||||||
/// <summary>
|
public override LocalisableString TooltipText
|
||||||
/// Whether to open the user's profile when clicked.
|
|
||||||
/// </summary>
|
|
||||||
public bool OpenOnClick
|
|
||||||
{
|
{
|
||||||
set => clickableArea.Enabled.Value = clickableArea.Action != null && value;
|
get
|
||||||
|
{
|
||||||
|
if (!Enabled.Value)
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
return ShowUsernameTooltip ? (user?.Username ?? string.Empty) : default_tooltip_text;
|
||||||
|
}
|
||||||
|
set => throw new NotSupportedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// By default, the tooltip will show "view profile" as avatars are usually displayed next to a username.
|
/// By default, the tooltip will show "view profile" as avatars are usually displayed next to a username.
|
||||||
/// Setting this to <c>true</c> exposes the username via tooltip for special cases where this is not true.
|
/// Setting this to <c>true</c> exposes the username via tooltip for special cases where this is not true.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool ShowUsernameTooltip
|
public bool ShowUsernameTooltip { get; set; }
|
||||||
{
|
|
||||||
set => clickableArea.TooltipText = value ? (user?.Username ?? string.Empty) : default_tooltip_text;
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly APIUser user;
|
private readonly APIUser? user;
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved]
|
||||||
private OsuGame game { get; set; }
|
private OsuGame? game { get; set; }
|
||||||
|
|
||||||
private readonly ClickableArea clickableArea;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A clickable avatar for the specified user, with UI sounds included.
|
/// A clickable avatar for the specified user, with UI sounds included.
|
||||||
/// If <see cref="OpenOnClick"/> is <c>true</c>, clicking will open the user's profile.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="user">The user. A null value will get a placeholder avatar.</param>
|
/// <param name="user">The user. A null value will get a placeholder avatar.</param>
|
||||||
public ClickableAvatar(APIUser user = null)
|
public ClickableAvatar(APIUser? user = null)
|
||||||
{
|
{
|
||||||
this.user = user;
|
this.user = user;
|
||||||
|
|
||||||
Add(clickableArea = new ClickableArea
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (user?.Id != APIUser.SYSTEM_USER_ID)
|
if (user?.Id != APIUser.SYSTEM_USER_ID)
|
||||||
clickableArea.Action = openProfile;
|
Action = openProfile;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
LoadComponentAsync(new DrawableAvatar(user), clickableArea.Add);
|
LoadComponentAsync(new DrawableAvatar(user), Add);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void openProfile()
|
private void openProfile()
|
||||||
@ -71,23 +61,12 @@ namespace osu.Game.Users.Drawables
|
|||||||
game?.ShowUser(user);
|
game?.ShowUser(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
private partial class ClickableArea : OsuClickableContainer
|
protected override bool OnClick(ClickEvent e)
|
||||||
{
|
{
|
||||||
private LocalisableString tooltip = default_tooltip_text;
|
if (!Enabled.Value)
|
||||||
|
return false;
|
||||||
|
|
||||||
public override LocalisableString TooltipText
|
return base.OnClick(e);
|
||||||
{
|
|
||||||
get => Enabled.Value ? tooltip : default;
|
|
||||||
set => tooltip = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool OnClick(ClickEvent e)
|
|
||||||
{
|
|
||||||
if (!Enabled.Value)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return base.OnClick(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Effects;
|
using osu.Framework.Graphics.Effects;
|
||||||
@ -13,9 +11,9 @@ namespace osu.Game.Users.Drawables
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// An avatar which can update to a new user when needed.
|
/// An avatar which can update to a new user when needed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class UpdateableAvatar : ModelBackedDrawable<APIUser>
|
public partial class UpdateableAvatar : ModelBackedDrawable<APIUser?>
|
||||||
{
|
{
|
||||||
public APIUser User
|
public APIUser? User
|
||||||
{
|
{
|
||||||
get => Model;
|
get => Model;
|
||||||
set => Model = value;
|
set => Model = value;
|
||||||
@ -58,7 +56,7 @@ namespace osu.Game.Users.Drawables
|
|||||||
/// <param name="isInteractive">If set to true, hover/click sounds will play and clicking the avatar will open the user's profile.</param>
|
/// <param name="isInteractive">If set to true, hover/click sounds will play and clicking the avatar will open the user's profile.</param>
|
||||||
/// <param name="showUsernameTooltip">Whether to show the username rather than "view profile" on the tooltip. (note: this only applies if <paramref name="isInteractive"/> is also true)</param>
|
/// <param name="showUsernameTooltip">Whether to show the username rather than "view profile" on the tooltip. (note: this only applies if <paramref name="isInteractive"/> is also true)</param>
|
||||||
/// <param name="showGuestOnNull">Whether to show a default guest representation on null user (as opposed to nothing).</param>
|
/// <param name="showGuestOnNull">Whether to show a default guest representation on null user (as opposed to nothing).</param>
|
||||||
public UpdateableAvatar(APIUser user = null, bool isInteractive = true, bool showUsernameTooltip = false, bool showGuestOnNull = true)
|
public UpdateableAvatar(APIUser? user = null, bool isInteractive = true, bool showUsernameTooltip = false, bool showGuestOnNull = true)
|
||||||
{
|
{
|
||||||
this.isInteractive = isInteractive;
|
this.isInteractive = isInteractive;
|
||||||
this.showUsernameTooltip = showUsernameTooltip;
|
this.showUsernameTooltip = showUsernameTooltip;
|
||||||
@ -67,7 +65,7 @@ namespace osu.Game.Users.Drawables
|
|||||||
User = user;
|
User = user;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Drawable CreateDrawable(APIUser user)
|
protected override Drawable? CreateDrawable(APIUser? user)
|
||||||
{
|
{
|
||||||
if (user == null && !showGuestOnNull)
|
if (user == null && !showGuestOnNull)
|
||||||
return null;
|
return null;
|
||||||
@ -76,7 +74,6 @@ namespace osu.Game.Users.Drawables
|
|||||||
{
|
{
|
||||||
return new ClickableAvatar(user)
|
return new ClickableAvatar(user)
|
||||||
{
|
{
|
||||||
OpenOnClick = true,
|
|
||||||
ShowUsernameTooltip = showUsernameTooltip,
|
ShowUsernameTooltip = showUsernameTooltip,
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
};
|
};
|
||||||
|
@ -106,7 +106,7 @@ namespace osu.Game.Users
|
|||||||
// Set status message based on activity (if we have one) and status is not offline
|
// Set status message based on activity (if we have one) and status is not offline
|
||||||
if (activity != null && !(status is UserStatusOffline))
|
if (activity != null && !(status is UserStatusOffline))
|
||||||
{
|
{
|
||||||
statusMessage.Text = activity.Status;
|
statusMessage.Text = activity.GetStatus();
|
||||||
statusIcon.FadeColour(activity.GetAppropriateColour(Colours), 500, Easing.OutQuint);
|
statusIcon.FadeColour(activity.GetAppropriateColour(Colours), 500, Easing.OutQuint);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -7,24 +7,31 @@ using osu.Game.Beatmaps;
|
|||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Scoring;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Users
|
namespace osu.Game.Users
|
||||||
{
|
{
|
||||||
public abstract class UserActivity
|
public abstract class UserActivity
|
||||||
{
|
{
|
||||||
public abstract string Status { get; }
|
public abstract string GetStatus(bool hideIdentifiableInformation = false);
|
||||||
|
|
||||||
public virtual Color4 GetAppropriateColour(OsuColour colours) => colours.GreenDarker;
|
public virtual Color4 GetAppropriateColour(OsuColour colours) => colours.GreenDarker;
|
||||||
|
|
||||||
public class Modding : UserActivity
|
public class ModdingBeatmap : EditingBeatmap
|
||||||
{
|
{
|
||||||
public override string Status => "Modding a map";
|
public override string GetStatus(bool hideIdentifiableInformation = false) => "Modding a beatmap";
|
||||||
public override Color4 GetAppropriateColour(OsuColour colours) => colours.PurpleDark;
|
public override Color4 GetAppropriateColour(OsuColour colours) => colours.PurpleDark;
|
||||||
|
|
||||||
|
public ModdingBeatmap(IBeatmapInfo info)
|
||||||
|
: base(info)
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ChoosingBeatmap : UserActivity
|
public class ChoosingBeatmap : UserActivity
|
||||||
{
|
{
|
||||||
public override string Status => "Choosing a beatmap";
|
public override string GetStatus(bool hideIdentifiableInformation = false) => "Choosing a beatmap";
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract class InGame : UserActivity
|
public abstract class InGame : UserActivity
|
||||||
@ -39,7 +46,7 @@ namespace osu.Game.Users
|
|||||||
Ruleset = ruleset;
|
Ruleset = ruleset;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string Status => Ruleset.CreateInstance().PlayingVerb;
|
public override string GetStatus(bool hideIdentifiableInformation = false) => Ruleset.CreateInstance().PlayingVerb;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class InMultiplayerGame : InGame
|
public class InMultiplayerGame : InGame
|
||||||
@ -49,7 +56,7 @@ namespace osu.Game.Users
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string Status => $@"{base.Status} with others";
|
public override string GetStatus(bool hideIdentifiableInformation = false) => $@"{base.GetStatus(hideIdentifiableInformation)} with others";
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SpectatingMultiplayerGame : InGame
|
public class SpectatingMultiplayerGame : InGame
|
||||||
@ -59,7 +66,7 @@ namespace osu.Game.Users
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string Status => $"Watching others {base.Status.ToLowerInvariant()}";
|
public override string GetStatus(bool hideIdentifiableInformation = false) => $"Watching others {base.GetStatus(hideIdentifiableInformation).ToLowerInvariant()}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public class InPlaylistGame : InGame
|
public class InPlaylistGame : InGame
|
||||||
@ -78,31 +85,62 @@ namespace osu.Game.Users
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Editing : UserActivity
|
public class TestingBeatmap : InGame
|
||||||
|
{
|
||||||
|
public override string GetStatus(bool hideIdentifiableInformation = false) => "Testing a beatmap";
|
||||||
|
|
||||||
|
public TestingBeatmap(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset)
|
||||||
|
: base(beatmapInfo, ruleset)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class EditingBeatmap : UserActivity
|
||||||
{
|
{
|
||||||
public IBeatmapInfo BeatmapInfo { get; }
|
public IBeatmapInfo BeatmapInfo { get; }
|
||||||
|
|
||||||
public Editing(IBeatmapInfo info)
|
public EditingBeatmap(IBeatmapInfo info)
|
||||||
{
|
{
|
||||||
BeatmapInfo = info;
|
BeatmapInfo = info;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string Status => @"Editing a beatmap";
|
public override string GetStatus(bool hideIdentifiableInformation = false) => @"Editing a beatmap";
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Spectating : UserActivity
|
public class WatchingReplay : UserActivity
|
||||||
{
|
{
|
||||||
public override string Status => @"Spectating a game";
|
private readonly ScoreInfo score;
|
||||||
|
|
||||||
|
protected string Username => score.User.Username;
|
||||||
|
|
||||||
|
public BeatmapInfo BeatmapInfo => score.BeatmapInfo;
|
||||||
|
|
||||||
|
public WatchingReplay(ScoreInfo score)
|
||||||
|
{
|
||||||
|
this.score = score;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string GetStatus(bool hideIdentifiableInformation = false) => hideIdentifiableInformation ? @"Watching a replay" : $@"Watching {Username}'s replay";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SpectatingUser : WatchingReplay
|
||||||
|
{
|
||||||
|
public override string GetStatus(bool hideIdentifiableInformation = false) => hideIdentifiableInformation ? @"Spectating a user" : $@"Spectating {Username}";
|
||||||
|
|
||||||
|
public SpectatingUser(ScoreInfo score)
|
||||||
|
: base(score)
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SearchingForLobby : UserActivity
|
public class SearchingForLobby : UserActivity
|
||||||
{
|
{
|
||||||
public override string Status => @"Looking for a lobby";
|
public override string GetStatus(bool hideIdentifiableInformation = false) => @"Looking for a lobby";
|
||||||
}
|
}
|
||||||
|
|
||||||
public class InLobby : UserActivity
|
public class InLobby : UserActivity
|
||||||
{
|
{
|
||||||
public override string Status => @"In a lobby";
|
public override string GetStatus(bool hideIdentifiableInformation = false) => @"In a lobby";
|
||||||
|
|
||||||
public readonly Room Room;
|
public readonly Room Room;
|
||||||
|
|
||||||
|
@ -18,29 +18,29 @@
|
|||||||
</None>
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="AutoMapper" Version="11.0.1" />
|
<PackageReference Include="AutoMapper" Version="12.0.1" />
|
||||||
<PackageReference Include="DiffPlex" Version="1.7.1" />
|
<PackageReference Include="DiffPlex" Version="1.7.1" />
|
||||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.46" />
|
<PackageReference Include="HtmlAgilityPack" Version="1.11.46" />
|
||||||
<PackageReference Include="Humanizer" Version="2.14.1" />
|
<PackageReference Include="Humanizer" Version="2.14.1" />
|
||||||
<PackageReference Include="MessagePack" Version="2.4.35" />
|
<PackageReference Include="MessagePack" Version="2.4.59" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="6.0.10" />
|
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="7.0.2" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="6.0.10" />
|
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="7.0.2" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="6.0.10" />
|
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="7.0.2" />
|
||||||
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="6.0.10" />
|
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="7.0.2" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="7.0.0" />
|
||||||
<PackageReference Include="Microsoft.Toolkit.HighPerformance" Version="7.1.2" />
|
<PackageReference Include="Microsoft.Toolkit.HighPerformance" Version="7.1.2" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||||
<PackageReference Include="ppy.LocalisationAnalyser" Version="2022.809.0">
|
<PackageReference Include="ppy.LocalisationAnalyser" Version="2022.809.0">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Realm" Version="10.18.0" />
|
<PackageReference Include="Realm" Version="10.20.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2023.131.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2023.131.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2023.202.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2023.202.0" />
|
||||||
<PackageReference Include="Sentry" Version="3.23.1" />
|
<PackageReference Include="Sentry" Version="3.28.1" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.32.2" />
|
<PackageReference Include="SharpCompress" Version="0.32.2" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.2" />
|
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.4" />
|
||||||
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||||
<PackageReference Include="TagLibSharp" Version="2.3.0" />
|
<PackageReference Include="TagLibSharp" Version="2.3.0" />
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user